mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(host): make unsubscribing a watch take effect at the drop, not after it
CI's Windows job failed `watch_drop_unsubscribes`: an event for a file created *after* the subscription was dropped still reached a consumer holding a clone of the receiver. Tearing the watcher down is not instantaneous. The OS backend runs 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 yet noticed the disconnect. So "dropped" meant "stops delivering shortly", which is not what the subscription promises — and for a remote host it is the difference between releasing a server-side watch and leaking one. The handle now closes the delivery channel in its own `Drop`, before any of that unwinds. 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, and exactly what the conformance test allows for. Not this branch's bug: the change here is to git reads, not watches. But main is flaky in the same family — it failed the sibling `watch_coalesces_within_window` eleven hours ago and was hardened for that one — so this fixes the cause rather than loosening the test. Refs #239.
This commit is contained in:
@@ -398,6 +398,27 @@ impl WatchedDirs {
|
||||
/// A live local watch: the notify watcher plus the set it is following.
|
||||
struct LocalWatch {
|
||||
inner: Mutex<LocalWatchInner>,
|
||||
/// 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<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
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<Mutex<GitignoreChain>>) -> io::R
|
||||
watcher,
|
||||
dirs: Arc::clone(&watched),
|
||||
}),
|
||||
batch_tx: batch_tx.clone(),
|
||||
};
|
||||
handle.set_dirs(dirs)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user