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:
+17
-2
@@ -662,6 +662,17 @@ pub fn request_shutdown<A>(pid: Pid<A>) {
|
||||
// cold lock (generation-verified), then acts outside the lock: a trap send
|
||||
// may unpark the receiver, and `request_stop_inner` re-takes the lock.
|
||||
pub(crate) fn request_shutdown_inner(inner: &RuntimeInner, pid: Pid, from: Pid) {
|
||||
request_shutdown_inner_probe(inner, pid, from);
|
||||
}
|
||||
|
||||
/// [`request_shutdown_inner`], reporting what it found: `Some(true)` if the
|
||||
/// target was trapping (got the signal), `Some(false)` if it was stopped
|
||||
/// outright, `None` if there was nothing live at `pid`.
|
||||
pub(crate) fn request_shutdown_inner_probe(
|
||||
inner: &RuntimeInner,
|
||||
pid: Pid,
|
||||
from: Pid,
|
||||
) -> Option<bool> {
|
||||
let trap = match inner.slot_at(pid) {
|
||||
Some(slot) => {
|
||||
let cold = slot.cold.lock();
|
||||
@@ -679,9 +690,13 @@ pub(crate) fn request_shutdown_inner(inner: &RuntimeInner, pid: Pid, from: Pid)
|
||||
from,
|
||||
reason: crate::monitor::DownReason::Shutdown,
|
||||
});
|
||||
Some(true)
|
||||
}
|
||||
Some(None) => request_stop_inner(inner, pid),
|
||||
None => {}
|
||||
Some(None) => {
|
||||
request_stop_inner(inner, pid);
|
||||
Some(false)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user