feat(gen_server,gen_statem): lifetime is the actor's — refs are addresses; inline named run
Root cause behind the "pin the endpoint" gotcha and the trapping-wrapper
pattern: a gen_server had two lifetime authorities — its refs (last one
dropped → inbox closes → exit) and, when supervised, its supervisor. OTP has
one: a process lives until it stops, is shut down, or is killed; a pid is an
address. Root exit now shutting down every forest root removes the reason the
ref-governed idiom existed (a forgotten server no longer hangs the run), so
adopt the one rule:
- The server/machine loop holds one inbox sender for its life; the inbox
never closes. GenServerRef / GenStatemRef are addresses. Explicit close is
`shutdown()`; a forgotten one is swept at root exit.
- `NamedGenServerBuilder::run()` / `gen_statem::run_named(name, m)` run the
loop inline as the current actor: a server is a direct ChildSpec child,
gets the supervisor's shutdown as handle_shutdown / a shutdown row, re-binds
its name on restart, and is addressed by name. The wrapper in
examples/graceful_shutdown.rs is gone.
- gen_statem gains GenStatemName + whereis_machine/send/call/shutdown by name
(parity with gen_server); the macro gets `Sm::new`.
- Root-exit sweep records `Event::RootSweep { target, trapping }` under
smarm-trace ("root_sweep shutdown|stopped"): unsupervised leftovers are
visible rather than silently owned-by-refs.
- Named start() name-clash path stops the spawned actor instead of relying
on ref drop.
Tests: tests/gen_server_lifetime.rs, tests/gen_statem_lifetime.rs,
tests/root_sweep_trace.rs (feature-gated); three existing tests that used
drop-closes-inbox now use shutdown(). Docs/README/ROADMAP/Deep Dive updated.
This commit is contained in:
@@ -24,10 +24,11 @@
|
||||
//! just waits for the tree to come down.
|
||||
|
||||
use smarm::gen_server::{
|
||||
GenServer, GenServerBuilder, GenServerCtx, ShutdownAction, StopHandle, TimerHandle,
|
||||
GenServer, GenServerBuilder, GenServerCtx, GenServerName, ShutdownAction, StopHandle,
|
||||
TimerHandle,
|
||||
};
|
||||
use smarm::supervisor::{ChildSpec, OneForOne, Restart, Shutdown};
|
||||
use smarm::{monitor, request_shutdown, self_pid, sleep, spawn, trap_exit, DownReason};
|
||||
use smarm::{sleep, spawn};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -77,28 +78,9 @@ impl GenServer for Drainer {
|
||||
}
|
||||
}
|
||||
|
||||
/// A supervised child wrapping the server. (A gen_server is not yet directly
|
||||
/// usable as a `ChildSpec` start fn; the wrapper traps, forwards the shutdown,
|
||||
/// and waits for the server to finish. See ROADMAP "open items".)
|
||||
fn drainer_child() {
|
||||
let inbox = trap_exit();
|
||||
let srv = GenServerBuilder::new(Drainer {
|
||||
pending: 3,
|
||||
stop: None,
|
||||
timer: None,
|
||||
})
|
||||
.under(self_pid())
|
||||
.start();
|
||||
let mon = monitor(srv.pid());
|
||||
// Wait for our shutdown, forward it, wait for the server.
|
||||
while let Ok(sig) = inbox.recv() {
|
||||
if sig.reason == DownReason::Shutdown {
|
||||
request_shutdown(srv.pid());
|
||||
let _ = mon.rx.recv();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The server's name: how the rest of the app reaches it (and the only handle
|
||||
/// that survives a restart).
|
||||
const DRAINER: GenServerName<Drainer> = GenServerName::new("drainer");
|
||||
|
||||
fn app_tree() -> OneForOne {
|
||||
OneForOne::new()
|
||||
@@ -111,7 +93,22 @@ fn app_tree() -> OneForOne {
|
||||
})
|
||||
.shutdown(Shutdown::Timeout(Duration::from_millis(100))),
|
||||
)
|
||||
.child(ChildSpec::new(Restart::Permanent, drainer_child).shutdown(Shutdown::Infinity))
|
||||
// A gen_server is a direct child: `named(N).run()` runs the loop as
|
||||
// the child actor itself, so the supervisor's shutdown arrives as
|
||||
// `handle_shutdown` and a restart re-binds the name.
|
||||
.child(
|
||||
ChildSpec::new(Restart::Permanent, || {
|
||||
GenServerBuilder::new(Drainer {
|
||||
pending: 3,
|
||||
stop: None,
|
||||
timer: None,
|
||||
})
|
||||
.named(DRAINER)
|
||||
.run()
|
||||
.expect("drainer name is free");
|
||||
})
|
||||
.shutdown(Shutdown::Infinity),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
Reference in New Issue
Block a user