Skip to content

fix(kqueue): close watch descriptors on Close()#1

Merged
paralin merged 1 commit into
masterfrom
fix/kqueue-close-fd-leak
Mar 29, 2026
Merged

fix(kqueue): close watch descriptors on Close()#1
paralin merged 1 commit into
masterfrom
fix/kqueue-close-fd-leak

Conversation

@paralin

@paralin paralin commented Mar 29, 2026

Copy link
Copy Markdown
Member

Close() calls shared.close() first, which marks the watcher as closed by closing the done channel. It then iterates calling Remove() for each watched path. But remove() checks isClosed() at entry and short-circuits without closing any file descriptors.

This leaks every file and directory watch descriptor opened by addWatch(). On macOS with kqueue, watching a directory also watches all files within it, so a watcher monitoring N directories with M total files leaks N+M descriptors per Close() call. Long-running processes that recreate watchers (e.g. dev servers with hot reload) accumulate leaked descriptors until hitting EMFILE.

Fix: close all watch descriptors directly by iterating the wd map instead of delegating to Remove().

Upstream issue: fsnotify#720

Close() calls shared.close() first, which marks the watcher as closed
by closing the done channel. It then iterates calling Remove() for
each watched path. But remove() checks isClosed() at entry and
short-circuits without closing any file descriptors.

This leaks every file and directory watch descriptor opened by
addWatch(). On macOS with kqueue, watching a directory also watches
all files within it, so a watcher monitoring N directories with M
total files leaks N+M descriptors per Close() call. Long-running
processes that recreate watchers (e.g. dev servers with hot reload)
accumulate leaked descriptors until hitting EMFILE.

Fix: close all watch descriptors directly by iterating the wd map
instead of delegating to Remove().

Signed-off-by: Christian Stewart <christian@aperture.us>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant