hyperium/hyper · warning · hyper::Error
error shutting down connection
Error message
error shutting down connection
What it means
Thrown via Error::new_shutdown() (src/error.rs:478, Kind::Shutdown), HTTP/1 only. It wraps the std::io::Error returned by AsyncWrite::shutdown on the underlying stream — i.e. the graceful connection close sequence itself failed. Produced at proto/h1/conn.rs:851 (debug! "error shutting down IO"). Detect with Error::is_shutdown().
Source
Thrown at src/error.rs:478
Error::new_user(User::Service).with(cause)
}
#[cfg(all(
any(feature = "client", feature = "server"),
any(feature = "http1", feature = "http2")
))]
pub(super) fn new_user_body<E: Into<Cause>>(cause: E) -> Error {
Error::new_user(User::Body).with(cause)
}
#[cfg(all(feature = "client", feature = "http2"))]
pub(super) fn new_user_invalid_connect() -> Error {
Error::new_user(User::InvalidConnectWithBody)
}
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
pub(super) fn new_shutdown(cause: std::io::Error) -> Error {
Error::new(Kind::Shutdown).with(cause)
}
#[cfg(feature = "ffi")]
pub(super) fn new_user_aborted_by_callback() -> Error {
Error::new_user(User::AbortedByCallback)
}
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
pub(super) fn new_user_dispatch_gone() -> Error {
Error::new(Kind::User(User::DispatchGone))
}
#[cfg(all(any(feature = "client", feature = "server"), feature = "http2"))]
pub(super) fn new_h2(cause: ::h2::Error) -> Error {
if cause.is_io() {
Error::new_io(cause.into_io().expect("h2::Error::is_io"))
} else {
Error::new(Kind::Http2).with(cause)View on GitHub (pinned to 084473f728)
Solutions
- Treat it as best-effort: the message exchange already completed in most cases, so log at debug/trace and continue rather than failing the request.
- Detect with Error::is_shutdown() and downgrade severity in logs.
- If using a custom IO/TLS layer, ensure its shutdown is robust to an already-closed peer.
Example fix
// before: shutdown error fails the whole connection future
let (_tx, conn) = hyper::client::conn::http1::handshake(stream).await?;
conn.await?;
// after: a shutdown error after the work is done is not fatal
tokio::spawn(async move {
if let Err(e) = conn.await {
if !e.is_shutdown() {
tracing::error!("connection error: {e}");
} else {
tracing::debug!("connection shutdown reported error: {e}");
}
}
}); Defensive patterns
Strategy: try-catch
Type guard
fn is_shutdown_err(err: &hyper::Error) -> bool {
err.is_shutdown()
} Try / catch
tokio::spawn(async move {
if let Err(e) = conn.await {
if e.is_shutdown() {
tracing::debug!("shutdown reported error (usually harmless): {e}");
} else {
tracing::error!("conn error: {e}");
}
}
}); Prevention
- After the response is delivered, treat shutdown errors as non-fatal (log debug).
- Make custom/TLS AsyncWrite::shutdown tolerant of an already-closed peer.
- Don't gate request success on a clean shutdown.
When it happens
Trigger: Conn::poll_shutdown calls io.poll_shutdown (conn.rs:844-855) and the stream returns an io::Error (e.g. the peer already RST the connection, or TLS close_notify failed). Fires during the teardown path, typically after the real work is already done.
Common situations: Peer has already hard-closed the connection by the time hyper sends the shutdown; a TLS layer fails to exchange close_notify; a custom AsyncWrite (e.g. a proxy) errors on shutdown. Usually harmless because the response was already sent.
Related errors
- connection closed before message completed
- operation was canceled
- message head is too large
- invalid HTTP version parsed (found HTTP2 preface)
- received unexpected message from connection
AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06).
Data as JSON: /data/errors/f0bbd56b52cc14b2.json.
Report an issue: GitHub.