Notify-rs: Events Triggered After Unwatch - Bug?
Hey guys!
I've run into what seems like a tricky issue using the notify-rs crate on Windows, and I wanted to share it here to see if anyone else has experienced something similar or has any insights.
The Issue: Unexpected Events After Unwatching
The core of the problem is that I'm seeing events being triggered even after I've explicitly unwatched a directory. Here's the sequence of actions that leads to the issue:
- I start by watching a directory, let's call it 
A. This sets up the file system monitoring for changes within directoryA. - Next, I create a subdirectory 
BinsideA. This should correctly trigger aCreateevent, as expected. - Now, the crucial part: I unwatch directory 
A. This should, in theory, stop any further events from being reported forAor its contents. - However, if I then create another directory, 
C, insideA, I sometimes still receive aCreateevent forA/C, even thoughAis no longer being watched. It's like the ghost of the watcher is still lingering! 
Most of the time, I only get the create event for A/B, but every so often, I get the event for A/C despite A no longer being watched. This is unexpected and could lead to problems in applications that rely on the watcher behaving predictably.
Reproducing the Behavior
To better illustrate the issue, I've put together a minimal reproducible example in Rust. This code snippet should allow you to observe the same behavior on your own Windows system. Understanding how to reproduce a bug is the first step in getting it fixed, right?
use notify::{Event, RecursiveMode, Result, Watcher};
use std::{path::PathBuf, str::FromStr, sync::mpsc};
fn main() {
    let test_dir = PathBuf::from_str("test").unwrap();
    let _ = std::fs::create_dir(&test_dir);
    let (tx, rx) = mpsc::channel::<Result<Event>>();
    {
        let mut watcher = notify::recommended_watcher(tx).unwrap();
        let _ = watcher.watch(&test_dir, RecursiveMode::NonRecursive);
        let _ = std::fs::create_dir(test_dir.join("new_dir"));
        let _ = watcher.unwatch(&test_dir);
        let _ = std::fs::create_dir(test_dir.join("should_not_be_seen"));
    }
    for res in rx {
        match res {
            Ok(event) => println!("event: {:?}", event),
            Err(e) => println!("watch error: {:?}", e),
        }
    }
    let _ = std::fs::remove_dir_all(&test_dir);
}
Here's a breakdown of what this code does:
- It sets up a temporary directory named 
test. - It creates a 
notify::recommended_watcherand a channel (tx,rx) to receive events. - It starts watching the 
testdirectory in non-recursive mode. This means we're only interested in events directly withintest, not in its subdirectories (initially). - It creates a directory named 
new_dirinsidetest. This should trigger aCreateevent. - It unwatches the 
testdirectory. This is where we expect the events to stop. - It then creates another directory, 
should_not_be_seen, insidetest. This is the key: we shouldn't see an event for this, but sometimes we do! - Finally, it iterates through the events received on the channel and prints them.
 - The temporary directory is then cleaned up.
 
Cargo.toml
To run this code, you'll need the notify crate. Here's the Cargo.toml file I used:
[package]
name = "notify_test"
version = "0.1.0"
edition = "2024"
[dependencies]
notify = { version = "8.2.0", features = ["serde"] }
This specifies that we're using version 8.2.0 of the notify crate and enabling the serde feature. Serde is a popular crate for serialization and deserialization in Rust, but in this case, it's a feature of the notify crate itself.
Expected vs. Actual Output
When you run cargo run, you'll typically see output like this:
event: Event { kind: Create(Any), paths: ["C:\\<...>\\notify_test\\test\\new_dir"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
This is the expected behavior. We see the Create event for new_dir, which was created while we were watching the directory.
However, every so often (this is the frustrating part!), you might see this:
event: Event { kind: Create(Any), paths: ["C:\\<...>\\notify_test\\test\\new_dir"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
event: Event { kind: Create(Any), paths: ["C:\\<...>\\notify_test\\test\\should_not_be_seen"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
See that second event? That's the unexpected one! We're getting a Create event for should_not_be_seen even after we unwatched the directory. This is the core of the issue.
Possible Causes and Next Steps
So, what could be causing this? Here are a few possibilities that come to mind:
- Race condition: There might be a race condition within the 
notify-rscrate or the underlying file system monitoring APIs on Windows. Perhaps the unwatch operation isn't fully completed before the create operation is processed. - File system caching: The file system itself might be caching events or notifications in some way, leading to delayed or spurious events.
 - Windows-specific behavior: This could be a quirk of how file system monitoring works on Windows. Different operating systems have different implementations, and there might be subtle differences in behavior.
 
Digging Deeper into the Issue
To investigate this further, it would be helpful to:
- Examine the 
notify-rscrate's source code: Understanding the internal workings of the crate, especially how it handles watching and unwatching directories, could shed light on the issue. - Experiment with different watcher configurations: Trying different options, such as recursive vs. non-recursive watching, or different buffering strategies, might reveal patterns or workarounds.
 - Use system-level monitoring tools: Tools like Process Monitor on Windows can provide detailed information about file system activity, which could help pinpoint the source of the extra events.
 
Contributing to the Community
If you're encountering similar issues with notify-rs or have experience with file system monitoring on Windows, I'd love to hear your thoughts! Sharing your insights and experiences can help us all better understand and address these challenges. Let's collaborate to make notify-rs even more robust and reliable!
In the meantime, I'll continue to investigate this issue and share any findings here. Let's crack this nut together, guys!