A stop waited for every connection to close, not only for requests in flight. One vanished HTTP/2 client, or one client part way through sending its request headers, held the stop for the full 10 s drain with nothing in flight. The production journal for Oct 23 shows exactly that: nothing in flight from no longer accepting until the drain ran out, and the firehose streams closing afterwards, in the usual order (#228).
A stop now waits on the requests in flight, each counted from when it reaches the router until its whole response is sent. Once none are left, a late request gets 503 ServerStopping, so no write can commit after the firehose streams close. Remaining connections get 500 ms to close on their own and are then dropped, and the firehose streams then get everything they are owed and a 1001 close, as before. drain_secs still caps the wait, and the data layout is unchanged. stop_window.rs now holds the production mix: resuming relay streams, a write still sending its body, HTTP/2 long polls, a vanished client, a half-sent header and a stalled TLS handshake. The listener's outage drops from 10.02 s on main to about 1.01 s, and every subscriber sees every acknowledged write exactly once. docs/operations.md says how to time a restart's outage from the journal.
Open question for review: the 500 ms grace is a constant, not a config key. Every exposed parameter is meant to get its own key, so say if this one should.
🤖 Generated with Claude Code