Repository navigation
v0.2.2
Application state must now declare how it drains itself, and the server runs that cleanup inside the shutdown window instead of leaving it to each project to remember.
The problem
A project holding a ReqwestTelegram had no wired way to flush it. send returns immediately, so whatever was queued at exit went away with the runtime — typically including the notification about whatever caused the exit. Nothing reported it, either: the queue has no Drop, and the "N message(s) abandoned" error lives inside flush, which was never called. The loss was completely silent.
AppShutdown
WebServer::runnow requiresS: AppShutdown. Adding app state without an implementation is a compile error — forgetting cleanup is otherwise invisible until the deploy where the notification you needed never arrives. A server with no state gets the only blanket implementation and writes nothing.- The library owns the sequencing, so there is no ordering to get wrong and no result to hold across it.
impl AppShutdown for AppState {
async fn on_shutdown(&self) {
self.telegram.flush(Duration::from_secs(5)).await;
}
}Why it runs concurrently with the drain
Cleanup starts the instant the signal arrives, alongside the connection drain, under a second ceiling started at the same moment — so the process leaves in the longer of the two, never their sum.
That is load-bearing, not a micro-optimisation: DEFAULT_DRAIN_TIMEOUT is 10s and Docker's default kill grace is also 10s, so anything sequenced after the drain is killed rather than run, and the messages it existed to save are lost in exactly the deploys where losing them matters most. A slow drain also cannot eat the cleanup's budget — and a slow drain means held-open sockets, which is precisely when you least want cleanup starved.
Cancellation and reporting
- Overrunning the ceiling is ordinary cancellation: the future is dropped at its last
.await. - The server reports the overrun at
error!, because a cancelled hook cannot report its own cancellation — reporting it anywhere else would lose it silently, which is the bug this release exists to remove. on_shutdowntherefore takes no deadline argument. There is one ceiling and it belongs to the library, so a project cannot pick a number that disagrees with it.- The hook is dropped unrun when serving ends without a signal, so a bind failure returns immediately instead of waiting out a drain nobody asked for.
Idempotency
on_shutdown runs once per server holding the state, so a binary running several servers off one Arc<AppState> calls it once per server. The library cannot deduplicate that — each WebServer builds its own WebServerState, and only the application knows what they share. Guard anything that would misbehave twice behind a OnceCell of your own.
Upgrading
Projects carrying app state need to add an impl AppShutdown for AppState. Projects with no state (WebServer::new / from_env) need no change.
Also
- Extracted
attach_not_foundfromrun, which had grown past the 100-line lint. - Added tokio's
test-utilto dev-dependencies, so the overrun is asserted on a paused clock rather than by a test that really sleeps for the drain window. - Three new tests cover the hook running on signal, being cancelled at the ceiling, and not running when the server dies before any signal.
Rust webserver-base 0.2.2 · JSR @todd/webserver-base unchanged at 0.1.1
Full Changelog: v0.2.1...v0.2.2