diff --git a/crates/tty7-core/src/host/local.rs b/crates/tty7-core/src/host/local.rs index e49f3cc0..12a7c805 100644 --- a/crates/tty7-core/src/host/local.rs +++ b/crates/tty7-core/src/host/local.rs @@ -398,6 +398,27 @@ impl WatchedDirs { /// A live local watch: the notify watcher plus the set it is following. struct LocalWatch { inner: Mutex, + /// The delivery end, kept solely so dropping this handle can close it. + /// + /// Tearing the watcher down is not instantaneous — the OS backend has its + /// own thread, and on Windows a `ReadDirectoryChangesW` completion can fire + /// *during* teardown, reach the event closure while `raw_tx` is still + /// alive, and be forwarded by a coalescer that has not noticed the + /// disconnect yet. A consumer holding a clone of the receiver would then + /// see an event for a change made after it unsubscribed. + /// + /// Closing the channel here makes "dropped" mean "no further batches" at + /// the instant of the drop, whatever the backend does afterwards. Batches + /// already queued stay readable — `close` stops sends, not receives — which + /// is the one thing a consumer racing its own drop may legitimately still + /// see. + batch_tx: smol::channel::Sender>, +} + +impl Drop for LocalWatch { + fn drop(&mut self) { + self.batch_tx.close(); + } } struct LocalWatchInner { @@ -463,6 +484,7 @@ fn local_watch(dirs: &[PathBuf], gitignore: Arc>) -> io::R watcher, dirs: Arc::clone(&watched), }), + batch_tx: batch_tx.clone(), }; handle.set_dirs(dirs)?;