fsevent: coalesce watches nested inside other watches - #975
Merged
Conversation
An FSEvents stream root reports the entire hierarchy below it, and `callback_impl` attributes every event to the longest watch it falls under, so passing both a directory and something below it to `FSEventStreamCreate` adds nothing while costing a slot against the path limit that macOS enforces by corrupting the process descriptor table above roughly RLIMIT_NOFILE/10 paths. Derive the CFArray handed to `FSEventStreamCreate` from the watch map in `run()` instead of maintaining it alongside, dropping any watch whose ancestors include another watch on the same volume. FSEvents does not promise that a stream root reports events from a volume mounted below it, so a covering ancestor across a mount point does not count. Unwatching a covering directory promotes the nested watches back to stream roots on the next run with no separate bookkeeping, and `remove_cf_path` goes away. One behavioural difference: with `kFSEventStreamCreateFlagWatchRoot`, a nested watch no longer receives a root-changed event of its own when an ancestor is renamed. A recursive covering watch still reports the rename; a non-recursive one reports nothing, since `callback_impl` accepts only direct children for those. Covered by unit tests for the stream roots produced by nested and by sibling watches, and by black-box tests that a nested watch still delivers events after its covering watch is removed and that a covering watch still delivers events outside the nested one. Signed-off-by: Daan De Meyer <daan@amutable.com>
JohnTitor
approved these changes
Aug 11, 2026
JohnTitor
left a comment
Member
There was a problem hiding this comment.
Makes sense 馃憤 added a tweak around some coalescing condition.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An FSEvents stream root reports the entire hierarchy below it, and
callback_implattributes every event to the longest watch it fallsunder, so passing both a directory and something below it to
FSEventStreamCreateadds nothing while costing a slot against the pathlimit that macOS enforces by corrupting the process descriptor table
above roughly RLIMIT_NOFILE/10 paths.
Derive the CFArray handed to
FSEventStreamCreatefrom the watch map inrun()instead of maintaining it alongside, dropping any watch whoseancestors include another watch on the same volume. FSEvents does not
promise that a stream root reports events from a volume mounted below
it, so a covering ancestor across a mount point does not count.
Unwatching a covering directory promotes the nested watches back to
stream roots on the next run with no separate bookkeeping, and
remove_cf_pathgoes away.One behavioural difference: with
kFSEventStreamCreateFlagWatchRoot, anested watch no longer receives a root-changed event of its own when an
ancestor is renamed. A recursive covering watch still reports the
rename; a non-recursive one reports nothing, since
callback_implaccepts only direct children for those.
Covered by unit tests for the stream roots produced by nested and by
sibling watches, and by black-box tests that a nested watch still
delivers events after its covering watch is removed and that a covering
watch still delivers events outside the nested one.
Signed-off-by: Daan De Meyer daan@amutable.com