fix(io): deregister a stopped actor's fd wait on the unwind path
A stopped actor unwinding out of wait_fd's park leaked its waiters entry and the kernel-side EPOLLONESHOT registration; the stale entry then failed every future wait_*() on that fd with AlreadyExists (the defensive bare DEL in epoll_register sits behind the contains_key check, so it never ran). Fix where the invariant breaks: a drop guard in wait_fd, armed after a successful register and forgotten on the normal wake (where FdReady already removed + DEL'd). On unwind it cleans up iff the entry is still this wait's (pid, epoch) — an entry consumed by a racing FdReady means the fd may carry another actor's fresh registration, which must be left alone. Closes the v0.2 fd-hygiene TODO.
This commit is contained in:
@@ -322,3 +322,46 @@ fn wait_writable_on_empty_pipe_returns_quickly() {
|
||||
elapsed
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fd hygiene on actor death (v0.8)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// An actor stopped while parked on an fd must not leak its `waiters` entry:
|
||||
// before the unwind-path guard in wait_fd, the stale entry made every
|
||||
// future wait_*() on that fd fail with AlreadyExists, forever.
|
||||
#[test]
|
||||
fn stopped_waiter_does_not_poison_the_fd() {
|
||||
let outcome = Arc::new(StdMutex::new(None::<u8>));
|
||||
let outcome2 = outcome.clone();
|
||||
run(move || {
|
||||
let p = Pipe::new();
|
||||
let (rfd, wfd) = (p.read, p.write);
|
||||
|
||||
// First waiter parks on the (empty) pipe and is stopped in place.
|
||||
let h = smarm::spawn(move || {
|
||||
wait_readable(rfd).unwrap();
|
||||
unreachable!("the pipe is never written while this actor lives");
|
||||
});
|
||||
yield_now(); // let it reach the park
|
||||
smarm::request_stop(h.pid());
|
||||
h.join().unwrap(); // Ok(()): stopped, not panicked
|
||||
|
||||
// Second waiter on the SAME fd must be able to register...
|
||||
let seen = Arc::new(AtomicU32::new(0));
|
||||
let seen2 = seen.clone();
|
||||
let h2 = smarm::spawn(move || {
|
||||
wait_readable(rfd).unwrap();
|
||||
let mut buf = [0u8; 1];
|
||||
assert_eq!(raw_read(rfd, &mut buf), 1);
|
||||
seen2.store(buf[0] as u32, Ordering::SeqCst);
|
||||
});
|
||||
yield_now(); // ...and park (a failed register would panic the unwrap)
|
||||
|
||||
// ...and actually be woken by readiness.
|
||||
assert_eq!(raw_write(wfd, b"x"), 1);
|
||||
h2.join().unwrap();
|
||||
*outcome2.lock().unwrap() = Some(seen.load(Ordering::SeqCst) as u8);
|
||||
});
|
||||
assert_eq!(*outcome.lock().unwrap(), Some(b'x'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user