docs: record the supervised-bus cycle (v0.8) and retire the OnceLock idiom
ROADMAP gains a v0.8 entry: the smarm 415effb lifetime change as root cause, the description/instantiation split, the tree shape, the accepted per-call resolution cost, and what stays open (RegistryName newtype, PubSub's unprotectable const, dynamic session actors, hammer.sh's missing feature matrix). The v0.7 "unreproduced test failure" open item is closed out and pointed at it — it was this hang, hidden because hammer.sh builds with default features while the failure needs --all-features load. README and the two module headers still taught the v0.5 rules: in-runtime-only construction, the non-static Arc<OnceLock<..>> cell, and "a relay must never hold a PubSub clone". None of those are true any more; they are replaced by what actually holds now, with a note on what changed for anyone who learnt the old shape. session.rs also gains an honest note that its session actors are the last lifetime in urus implied by a drop rather than stated — they are dynamic, so a fixed ChildSpec list cannot hold them, and the trigger is at least a command now rather than a refcount.
This commit is contained in:
@@ -358,10 +358,11 @@ independent of HTTP (it imports only smarm) and built for the WebSocket
|
||||
relay pattern:
|
||||
|
||||
```rust
|
||||
let bus: PubSub<String> = PubSub::new(); // in-runtime only!
|
||||
let rx = bus.subscribe("room:lobby")?; // Receiver<Arc<String>>
|
||||
bus.broadcast("room:lobby", "hi".to_string())?;
|
||||
bus.broadcast_from(smarm::self_pid(), "room:lobby", "no echo".into())?;
|
||||
const BUS: PubSub<String> = PubSub::new("chat"); // a name; spawns nothing
|
||||
// BUS.child() goes in your supervision tree (or serve_with*'s child vec)
|
||||
let rx = BUS.subscribe("room:lobby")?; // Receiver<Arc<String>>
|
||||
BUS.broadcast("room:lobby", "hi".to_string())?;
|
||||
BUS.broadcast_from(smarm::self_pid(), "room:lobby", "no echo".into())?;
|
||||
```
|
||||
|
||||
One generic instance per message domain; payloads broadcast as `Arc<M>`
|
||||
@@ -373,19 +374,25 @@ explicit pid for relay patterns. Mailboxes are unbounded: `broadcast`
|
||||
never blocks the table, and a slow subscriber's memory bill is bounded
|
||||
by the two cleanup paths above.
|
||||
|
||||
Two composition rules that matter (both enforced by
|
||||
`shutdown_with_open_chat_terminates` in the integration suite):
|
||||
Composition (enforced by `shutdown_with_open_chat_terminates` in the
|
||||
integration suite):
|
||||
|
||||
1. `PubSub::new()` spawns an actor, so it must run in-runtime. If your
|
||||
app owns its tree, start the table as a supervised sibling of the
|
||||
endpoint and address it by name — the clean shape. Under `serve*`
|
||||
there is no in-runtime moment before the first request, so build it
|
||||
lazily from a handler via a **non-static** `Arc<OnceLock<PubSub<M>>>`
|
||||
captured by the route closure; a `static` cell pins the table actor
|
||||
forever and graceful shutdown never returns.
|
||||
1. **The handle is an address, not the table.** `PubSub<M>` is a name:
|
||||
`const`, `Copy`, spawns nothing, fine in a `static` or outside the
|
||||
runtime. The actor is `PubSub::child()`, a `ChildSpec` for your
|
||||
supervision tree — or for `serve_with*`'s app-children vec, which puts
|
||||
it ahead of the endpoint so it stops only after the endpoint has
|
||||
drained. Operations resolve the name per call, so a restarted table is
|
||||
reached transparently.
|
||||
2. Relay/producer actors hold the `Receiver` (plus e.g. a `WsSender`
|
||||
clone) — **never a `PubSub` clone**, or relay and table keep each
|
||||
other alive past shutdown.
|
||||
clone). They may hold the handle too — it pins nothing — but usually
|
||||
have no use for one.
|
||||
|
||||
Prior to v0.8 both of these read the other way round: `PubSub::new()`
|
||||
spawned the table, the handle owned its life, and a non-static
|
||||
`Arc<OnceLock<PubSub<M>>>` lazily built from the first handler was the
|
||||
required idiom. smarm 0.7 made a server's lifetime its own, and that
|
||||
whole apparatus went away with it.
|
||||
|
||||
See [`examples/ws_chat.rs`](examples/ws_chat.rs): rooms as topics,
|
||||
`on_open` subscribes + spawns the relay, `on_message` uses
|
||||
@@ -425,8 +432,10 @@ impl Channel<P> for Room {
|
||||
}
|
||||
}
|
||||
|
||||
// in-runtime, non-static — the ws_chat OnceLock pattern applies
|
||||
let hub = ChannelHub::new(PrefixRouter::new().channel_default::<Room>("room:*"));
|
||||
// A description: spawns nothing. `children` are the ChildSpecs it needs
|
||||
// (bus table + one registry per session route) — hand them to serve_with*.
|
||||
let (hub, children) =
|
||||
ChannelHub::new("chat-bus", PrefixRouter::new().channel_default::<Room>("room:*"));
|
||||
// route handler: hub.upgrade(conn)
|
||||
// from anywhere with a hub handle: hub.broadcast("room:lobby", "news", payload)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user