channel: drop queued messages when the Receiver drops

A queued Envelope::Call was stranded until the last Sender dropped, so a
caller parked in gen_server::call was never released with ServerDown when a
*named* server was request_stop'd — the registry's inbox Sender clone (lazy
prune) kept the channel Arc, and the queued reply_tx, alive indefinitely.

Receiver::Drop now drains the queue (items dropped after releasing the lock,
since a reply_tx drop reaches a different channel's lock + the scheduler),
restoring the documented ServerDown guarantee on every teardown path.

Adds tests/stop_with_queued_call.rs: deterministic pure-smarm reproducer.
This commit is contained in:
smarm-agent
2026-06-24 20:53:27 +00:00
parent 3e9c33377c
commit 6c2b7e91cf
2 changed files with 135 additions and 1 deletions
+15 -1
View File
@@ -132,7 +132,21 @@ impl<T> Drop for Sender<T> {
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.inner.lock().receiver_alive = false;
// The only consumer is gone: queued messages can never be delivered.
// Drop them now instead of stranding them until the last Sender goes
// away (a registry entry under lazy prune can keep a Sender — and thus
// the Arc<Inner> — alive long after the server exits). Dropping a queued
// Envelope::Call drops its reply_tx, waking any caller parked in `call`
// with ServerDown, so the documented guarantee holds on *every* teardown
// path, not only the all-senders-drop one. Drain under the lock, then
// run item destructors after releasing it (a reply_tx drop reaches into
// a *different* channel's lock + the scheduler, so it must not nest).
let drained = {
let mut g = self.inner.lock();
g.receiver_alive = false;
std::mem::take(&mut g.queue)
};
drop(drained);
}
}