Skip to content

fix(inotify): report subdirectories found by the recursive catch-up walk - #976

Closed
teddytennant wants to merge 1 commit into
notify-rs:mainfrom
teddytennant:inotify-report-nested-creates
Closed

fix(inotify): report subdirectories found by the recursive catch-up walk#976
teddytennant wants to merge 1 commit into
notify-rs:mainfrom
teddytennant:inotify-report-nested-creates

Conversation

@teddytennant

Copy link
Copy Markdown

Description

Watching a directory recursively and then creating a nested tree in one call only reports the topmost directory:

watcher.watch(path, RecursiveMode::Recursive).unwrap();
std::fs::create_dir_all(path.join("1/2/3/4/5/6/7/8/9/10")).unwrap();

Before this change the only create event is Create(Folder) for path/1. The nine directories below it are never reported, even though the watcher demonstrably walks into all of them — with EventKindMask::ALL you can see Access(Open)/Access(Close) for every level.

Cause

The kernel reports the creation of 1 and nothing else: 2 through 10 are created before the watch for 1 exists, so no inotify event is ever generated for them. EventLoop::handle_inotify reacts to the CREATE event by queueing 1 into add_watches and then calling add_watch(path, true, false), which runs a WalkDir catch-up scan and installs a watch on every directory it finds. That scan is the only thing in the process that ever learns those directories exist, and it discarded that knowledge — it registered the watches and reported nothing. This is the TOCTOU window the surrounding comments already acknowledge, just observed from the event side rather than the watch side.

This is the diagnosis @riberk gave in #727 ("I think, the way to solve it may be raising events while scanning the dirs"); the issue has sat unclaimed since.

Fix

add_watches_for_paths now takes a ReportCreated flag. When the walk was triggered by an inotify CREATE/MOVED_TO event, every directory the walk finds below its root is reported as Create(Folder), after its watch is installed so that a handler reacting to the event cannot make a change nothing is watching for yet. The walk root itself is skipped because the kernel already announced it.

This is the right layer because the catch-up walk is the only place that ever observes these paths; nothing downstream can reconstruct them.

Deliberately unchanged:

  • The initial watch() call stays silent. Watching a path is not a change, so a pre-existing tree must not be reported. add_watch passes ReportCreated::Nothing, and a new test pins that.
  • Files are not reported. The walk only yields directories (filter_dir), and widening it would turn moving a large tree into a watched directory into a per-file event storm. Directory count already bounds the work done here, so the event volume stays proportional to the watches being installed.
  • Other backends are untouched.

Verification

Fail-before (fix reverted, tests kept):

test inotify::tests::watching_a_directory_does_not_report_its_existing_contents ... ok
test inotify::tests::recursive_creation ... FAILED
thread 'inotify::tests::recursive_creation' panicked at notify/src/test.rs:70:27:
Recv error: Timeout. Watcher: Inotify. State: ExpectedState {
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 82 filtered out; finished in 1.00s

Pass-after:

test inotify::tests::recursive_creation ... ok
test inotify::tests::watching_a_directory_does_not_report_its_existing_contents ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 82 filtered out

cargo test --workspace --all-features --no-fail-fast, three consecutive runs, all identical: notify lib 85 passed / 0 failed / 1 ignored, everything else green except tests/serialise-events.rs (6 failures, left: String("any"), right: Object {"type": String("any")}) which fails the same way on a clean checkout of d285062 and is unrelated to this change.

cargo fmt --all -- --check and cargo clippy --all-targets --all-features -- -D warnings are clean.

Tests

  • recursive_creation already existed, marked #[ignore = "see .../issues/727"], and now passes — the #[ignore] is removed.
  • watching_a_directory_does_not_report_its_existing_contents is new: it recursively watches a directory that already contains 1/2/3 and asserts no create events are emitted. Making add_watch report its own walk fails this test.

Related Issues

Fixes #727

Note

#970 also refactors add_watches_for_paths, so one of these two will need a trivial rebase over the other.

Creating a nested tree in a single call, `create_dir_all("1/2/.../10")`
inside a recursively watched directory, only produced a create event for
`1`. The kernel reports the creation of the topmost directory alone: by
the time we react to that event and install a watch for it, the rest of
the tree already exists, so no further inotify event will ever arrive.

`add_watch` walks the new directory to install watches on what it finds,
but said nothing about it, so those nine subdirectories were never
reported to the user even though they were newly created from the
watcher's point of view.

Report a `Create(Folder)` event for each directory that walk discovers
below its root. This applies only to the walk triggered by an inotify
CREATE or MOVED_TO event; the walk performed for an explicit `watch()`
call keeps quiet about a tree that was already there.

Un-ignores the existing `recursive_creation` test.
@JohnTitor

Copy link
Copy Markdown
Member

I see you're spamming LLM-generated PRs in many OSS repos, we don't welcome such a contribution.

@JohnTitor JohnTitor closed this Aug 8, 2026
@teddytennant

Copy link
Copy Markdown
Author

Fair enough. You're right on both counts, and sorry for the noise. Opening that many PRs across repos I have no history with was a bad way to show up, and I should have asked on #727 before writing code rather than after. I won't open anything else here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[INotifyWatcher] Fast recursive events in nested paths are not delivered

2 participants