Keep file-watch observation from serializing unrelated IO

Scope open-registration and rename/remove ordering to overlapping paths;
let descriptor mutations and directory creation proceed without that lock.
Prove blocked opens/writes cannot stall unrelated namespaces, including a
queued rename, and retain post-rename descriptor notifications. Document
handler-owned watch lifetimes and narrow paths; test natural idle exit.

Card: SAU-359
This commit is contained in:
SAU-359 implementer
2026-10-30 21:06:10 +00:00
parent ff6fc62f05
commit 0c1ef74f6e
5 changed files with 336 additions and 19 deletions
+35
View File
@@ -63,6 +63,41 @@ impl Probe {
Ok(())
}
/// Poll the watch in the handler, then return idle without a background task.
async fn watch_once(&self, p: WatchPath) -> Result<()> {
use std::{
cell::RefCell,
rc::Rc,
task::{Poll, Waker},
};
let state = Rc::new(RefCell::new((false, None::<Waker>)));
let received = state.clone();
let mut watch = std::pin::pin!(std::future::IntoFuture::into_future(
call("core.watch", json!({"path":p.path})).on_notify(move |name, _| {
if name == "armed" {
let mut state = received.borrow_mut();
state.0 = true;
if let Some(waker) = state.1.take() {
waker.wake();
}
}
})
));
std::future::poll_fn(|cx| {
if let Poll::Ready(result) = watch.as_mut().poll(cx) {
return Poll::Ready(result.map(|_| ()));
}
let mut state = state.borrow_mut();
state.1 = Some(cx.waker().clone());
if state.0 {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
})
.await
}
/// Publish a control endpoint until EOF.
async fn serve(&self) -> Result<()> {
sausage_plugin::listen("/endpoint", std::rc::Rc::new(Control)).await