gen_server: drop-guard drains live timers + debug_assert no leak (RFC 015 §4.7)
This commit is contained in:
+33
-13
@@ -708,10 +708,30 @@ fn server_loop<G: GenServer>(
|
||||
mut infos: Vec<Receiver<G::Info>>,
|
||||
) {
|
||||
// terminate() must run on every exit path (clean close, panic, stop), so it
|
||||
// lives in this guard's Drop rather than after the loop.
|
||||
struct Terminate<G: GenServer>(G);
|
||||
// lives in this guard's Drop rather than after the loop. The guard also owns
|
||||
// a registry handle so the *same* drop drains every live timer (RFC 015
|
||||
// §4.7): once the loop is gone no periodic can re-arm anyway, but cancelling
|
||||
// here keeps no armed substrate entry lingering past the server, and the
|
||||
// assert pins the invariant.
|
||||
struct Terminate<G: GenServer>(G, Arc<Mutex<TimerReg<G>>>);
|
||||
impl<G: GenServer> Drop for Terminate<G> {
|
||||
fn drop(&mut self) {
|
||||
{
|
||||
let mut reg = self.1.lock().unwrap();
|
||||
for (_, sub) in reg.oneshots.drain() {
|
||||
cancel_timer(sub);
|
||||
}
|
||||
for (_, p) in reg.periodics.drain() {
|
||||
cancel_timer(p.live);
|
||||
}
|
||||
reg.rearm_tx = None;
|
||||
// A fired-but-gone send already no-ops on the closed Sys channel;
|
||||
// this guards the armed-but-unfired re-arming case specifically.
|
||||
debug_assert!(
|
||||
reg.oneshots.is_empty() && reg.periodics.is_empty(),
|
||||
"an armed timer survived gen_server loop exit"
|
||||
);
|
||||
}
|
||||
self.0.terminate();
|
||||
}
|
||||
}
|
||||
@@ -728,18 +748,18 @@ fn server_loop<G: GenServer>(
|
||||
}
|
||||
}
|
||||
|
||||
let mut guard = Terminate(state);
|
||||
|
||||
// The system intake arm: Watchers feed Monitors and (next chunk) armed
|
||||
// timers feed fires to the loop through it. The ctx (and with it the loop's
|
||||
// own sender) drops right after init — a state that didn't clone the
|
||||
// Watcher closes the arm, the first select observes the closure, and the
|
||||
// loop falls back to the plain-inbox park.
|
||||
let (sys_tx, sys_rx) = channel::<Sys<G>>();
|
||||
// Shared timer bookkeeping: the loop keeps one clone (to retire one-shots
|
||||
// on fire, and to drain on exit); every TimerHandle the state stores holds
|
||||
// another.
|
||||
// Shared timer bookkeeping: the loop keeps one clone (to retire one-shots on
|
||||
// fire, re-arm periodics, and read the idle window); the guard holds another
|
||||
// to drain on exit; every TimerHandle the state stores holds more.
|
||||
let reg = Arc::new(Mutex::new(TimerReg::default()));
|
||||
let mut guard = Terminate(state, reg.clone());
|
||||
|
||||
// The system intake arm: Watchers feed Monitors and armed timers feed fires
|
||||
// to the loop through it. The ctx (and with it the loop's own sender) drops
|
||||
// right after init — a state that cloned no Watcher/TimerHandle closes the
|
||||
// arm, the first select observes the closure, and the loop falls back to the
|
||||
// plain-inbox park.
|
||||
let (sys_tx, sys_rx) = channel::<Sys<G>>();
|
||||
// Bind the ctx so the idle window set during init can be read back, then
|
||||
// drop it — that drops the loop's own Sys sender, so a state that cloned no
|
||||
// Watcher/TimerHandle lets the arm auto-close (the unused-ctx behaviour).
|
||||
|
||||
Reference in New Issue
Block a user