Lifecycle
A broker's states are types: a configuration, a connection and a closed connection, each reached by a call that consumes the one before.
The diagram as text
- Broker: B::new(config) sync, no I/O; B::Connected subscribe, publish; Closed teardown report
- App: The builder registers brokers and handlers; RunningApp connected, loops running; Stopped loops drained, brokers closed
- B::new(config) sync, no I/O → B::Connected subscribe, publish: connect(self)
- B::Connected subscribe, publish → Closed teardown report: shutdown(self)
- The builder registers brokers and handlers → RunningApp connected, loops running: start()
- RunningApp connected, loops running → Stopped loops drained, brokers closed: shutdown()
- A publisher that outlives its broker's connection answers with an error, never with a silent success
A broker is built without I/O, so the #[ruststream::app]
builder stays synchronous whatever the broker. Every network call waits for start().
Subscribing and publishing are methods of the connected form only. Code that holds a broker
before connect has nothing to subscribe with, and shutdown consumes the connection it closes,
so these mistakes do not compile.
start() connects the brokers in the order they
were registered, opens every subscription against its connected broker and runs the
after_startup hooks. A failure on the way closes what is already open, in reverse order.
shutdown() stops the loops, drains them and
closes the brokers in reverse registration order. Where each
hook runs is drawn on Async.
A publisher handed out earlier can outlive the connection. Using it after shutdown returns an error. The conformance suite holds every broker crate to this rule.