Skip to content

kqueue: Close() leaks all watch file descriptors #732

@paralin

Description

@paralin

Close() in backend_kqueue.go marks the watcher as closed via shared.close() before iterating the remove loop. Then remove() checks isClosed() at entry and returns nil without closing any file descriptors.

On macOS with kqueue, watching a directory also opens individual file descriptors for every file in the directory. A watcher monitoring N directories with M total files leaks N+M descriptors on every Close() call. Long-running processes that recreate watchers (e.g. dev servers with hot reload) accumulate leaked descriptors until hitting EMFILE ("too many open files").

Discovered by inspecting a Go dev server that hit 10,376 open fds after ~9 hot reload cycles, each leaking ~1,400 file/directory descriptors from watcher.Close().

Version: v1.9.0

Repro:

  1. Create a watcher with Add() on a directory containing many files
  2. Close() the watcher
  3. Check /proc/self/fd or lsof: all watched file/directory fds are still open

The fix is to close watch descriptors directly in Close() by iterating the wd map instead of delegating to Remove() which short-circuits on isClosed():

aperturerobotics#1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions