Repository navigation
Fix kqueue descriptor ownership across watch replacement - #65495
Jake Bailey (jakebailey) wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Ancestor renames or removals leave nested watches and descriptors active, allowing later writes to emit updates for nonexistent paths instead of terminating the affected watches.
1 open issue
What changed in this PR
This PR fixes descriptor ownership in the native file watcher's kqueue backend so replacing a watched entry does not cause later cleanup to close an unrelated file descriptor.
Changes:
- Makes each entry own its current descriptor instead of caching descriptor numbers in subscriptions.
- Centralizes descriptor cleanup and holds the backend lock throughout directory comparison.
- Adds regression tests for file replacement, root deletion or rename, overlapping watches, and shutdown.
| File | Description |
|---|---|
tsc/internal/fswatch/kqueue.go |
Updates descriptor ownership, subscription cleanup, and locking. |
tsc/internal/fswatch/kqueue_test.go |
Adds descriptor-lifetime tests for replacement and watch cleanup. |
Review effort: Balanced
4107fff to
b3c767c
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Directory invalidation can incorrectly terminate watches attached to replacement directories and leave unsubscribed entries stale, causing missed delete events.
2 open issues
1 resolved since last review
Review effort: Balanced
Subscriptions can retain stale descriptor numbers after replacement. Closing them can close unrelated files and leak current descriptors. Path-only invalidation also loses failed-open entries and can terminate watches already attached to replacement inodes. Keep entry ownership separate from kernel subscriptions and record file identity when opening descriptors. Clean only affected old trees, including failed-open entries, while preserving replacement watches and unrelated overlaps. Cover ancestor loss, replacement and cleanup.
b3c767c to
611182d
Compare
There was a problem hiding this comment.
🟡 Changes recommended
A critical identity-check mismatch between Fstat and Lstat can incorrectly terminate an unchanged directory symlink watch when an unrelated sibling is created under a watched parent.
1 open issue
2 resolved since last review
Review effort: Balanced
| @@ -636,14 +581,9 @@ func (b *kqueueBackend) compareDir(_ int, path string, touched map[*dirWatch]str | |||
| if unix.Fstat(fd, &fdSt) == nil && unix.Lstat(fullWatchPath, &pathSt) == nil { | |||


Subscriptions can retain descriptor numbers after entries are reopened. Closing a stale number can close an unrelated file and leave the current watch descriptor open.
Fix this.
Caught in some automated code review.