server: free serve_connection over any HTTP service; axum::serve / serve_tls on the shared loop and driver - #308
Merged
Merged
Conversation
iainmcgin
force-pushed
the
rpb/b-connection-config
branch
from
September 17, 2026 04:48
572cf45 to
314acde
Compare
iainmcgin
force-pushed
the
rpb/c-serve-connection
branch
from
September 17, 2026 04:48
4da9e4a to
7648589
Compare
iainmcgin
force-pushed
the
rpb/c-serve-connection
branch
from
September 18, 2026 05:41
7648589 to
f4cfeac
Compare
iainmcgin
force-pushed
the
rpb/b-connection-config
branch
from
September 18, 2026 05:41
314acde to
9db55d1
Compare
…/ `serve_tls` run on the shared loop and driver The per-connection driver is generalised from `ConnectRpcService<D>` to any `tower::Service<Request<Incoming>, Response = Response<B>>` and published as `server::serve_connection(io, info, service, config, shutdown) -> ConnectionClosed`, with panic isolation applied per connection (`CatchPanic` around the caller's service) and hyper's `serve_connection_with_upgrades`. `Server::serve_connection` delegates to it, and the crate-private accept loop takes the same service bounds. `connectrpc::axum` then needs only `axum` + `server`: `serve(listener, router)` (plaintext) and `serve_tls` return `Serve` (the renamed `ServeTls`, kept as a deprecated alias), which holds a `ConnectionConfig` (`with_connection_config`) and runs the router on the shared accept loop and driver instead of its own copy. That fixes `serve_tls` silently lacking max age / idle / max requests, HTTP/2 settings and panic isolation, and makes its connection tasks owned by the future rather than detached. CI lints and checks the `server` and `axum,server` feature shapes. Guide: "Custom accept loops" shows a hand-written loop with an inline TLS handshake over `Server::serve_connection`; "Raw hyper" is what is left for hyper-only knobs. Tests: plaintext `axum::serve` stamps `PeerAddr`; max connection age retires an axum h2 connection; a panicking axum handler yields 500 and the connection survives; the free function hosts an `axum::Router` for a hand-written loop and reports `Closed`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BgcfmMV1ECyzhbqGvUV1a7 Signed-off-by: Ryan Brewster <rpb@anthropic.com>
… driver Spawning `serve_connection`'s future on another runtime moves its timers, HTTP/2 stream tasks and handlers, but a tokio socket stays on the I/O driver of the runtime that created it. That runtime keeps waking the connection and must outlive it, so a busy accepting runtime still delays a connection spawned on another runtime. The guide's accept loop now re-registers the socket on the target runtime (`into_std`, then `TcpStream::from_std` inside the task) and spawns with `JoinSet::spawn_on`, and the `serve_connection` docs say why. A new test serves an HTTP/2 connection on a second runtime after the accepting runtime has shut down, and checks that the handler ran there. The shared driver, which `connectrpc::axum::serve` uses in place of `axum::serve`, also had four problems: - A panic while a response body was produced unwound through the connection task, so over HTTP/1.1 the `serve_connection` future panicked instead of reporting `CloseReason::Error`. Every response body is now wrapped so that such a panic is logged and becomes a body error: an HTTP/2 stream reset with `INTERNAL_ERROR`, or a closed HTTP/1.1 connection. - With idle reaping on, a request stopped counting as in flight once its response head was returned, so a long streaming response could be closed as idle. The in-flight guard now lives in the response body. - The accept loop retried `EMFILE` / `ENFILE` without pausing, spinning a core until a descriptor was freed. It now waits a second first, or until shutdown. - HTTP/2 extended CONNECT was off, so HTTP/2 WebSockets failed. The changelog covers these. The README and crate-docs feature tables now say which `axum` items need `server` / `server-tls`, the `ServeTls` deprecation names 0.10.0, and the changelog no longer describes the unreleased `Server::serve_connection` as changed. Signed-off-by: Iain McGinniss <309153+iainmcgin@users.noreply.github.com>
iainmcgin
force-pushed
the
rpb/c-serve-connection
branch
from
September 18, 2026 05:49
f4cfeac to
86f528e
Compare
iainmcgin
marked this pull request as ready for review
September 18, 2026 05:49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The connection driver becomes a public free function over any tower HTTP service.
connectrpc::axum::serve_tls, which had its own copy, and the newconnectrpc::axum::servenow run on it and onServer's accept loop.server::serve_connection(io, info, service, config, shutdown)binds nothing to a runtime until its future is first polled, so a loop chooses where a connection runs by where it spawns the future. A tokio socket stays on the I/O driver that registered it; the guide's "Custom accept loops" section moves it withinto_std/TcpStream::from_std, which does not work once the stream is wrapped in TLS.connectrpc::axumnow needsaxum+server;serve_tlsstill needsserver-tls.ServeTlsis renamedServe, with a#[deprecated(since = "0.10.0")]alias, andServe::with_connection_configis new.serve_tls, a handler that panics before responding now gets a500with a Connectinternalbody instead of a dropped connection, the standalone server's connection defaults andConnectionConfigapply, and theServefuture owns the connection tasks.INTERNAL_ERROR) or closes the HTTP/1.1 connection, instead of unwinding through the connection task. HTTP/2 extended CONNECT is advertised, and the accept loop pauses for up to a second afterEMFILE/ENFILE.max_connection_idleset, a request stopped counting as in flight once its response head was returned, so a live streaming response could be closed as idle. It now counts until hyper drops the body, so a client that stops reading keeps its connection untilmax_connection_ageretires it.Part of #191 and #49.