hyperium/hyper · warning · hyper::Error

channel closed

Error message

channel closed

What it means

Thrown via Error::new_closed() (src/error.rs:398, Kind::ChannelClosed). It indicates the other end of an internal mpsc/watch channel was dropped: either the dispatch task (client side) or the body sender/receiver. Detect with Error::is_closed(). It usually means a producer or consumer went away — for a client it can mean the connection task ended; for a body it means the reader stopped polling.

Source

Thrown at src/error.rs:398

    #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
    pub(super) fn new_unexpected_message() -> Error {
        Error::new(Kind::UnexpectedMessage)
    }

    #[cfg(all(
        any(feature = "client", feature = "server"),
        any(feature = "http1", feature = "http2")
    ))]
    pub(super) fn new_io(cause: std::io::Error) -> Error {
        Error::new(Kind::Io).with(cause)
    }

    #[cfg(any(
        all(feature = "http1", any(feature = "client", feature = "server")),
        all(feature = "http2", feature = "client")
    ))]
    pub(super) fn new_closed() -> Error {
        Error::new(Kind::ChannelClosed)
    }

    #[cfg(all(
        any(feature = "client", feature = "server"),
        any(feature = "http1", feature = "http2")
    ))]
    pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error {
        Error::new(Kind::Body).with(cause)
    }

    #[cfg(all(
        any(feature = "client", feature = "server"),
        any(feature = "http1", feature = "http2")
    ))]
    pub(super) fn new_body_write<E: Into<Cause>>(cause: E) -> Error {
        Error::new(Kind::BodyWrite).with(cause)
    }

View on GitHub (pinned to 084473f728)

Solutions

  1. Detect with Error::is_closed() and create a brand-new connection/client rather than reusing the dead handle.
  2. Ensure the future that drives the connection (the one yielded by client::conn::http1::handshake) stays alive for as long as you use the SendRequest handle.
  3. For streaming bodies, make sure the body future isn't dropped prematurely, and finish sending (or abort intentionally) before the consumer disappears.

Example fix

// before: reuse a client handle whose connection task already ended
let resp = sender.send_request(req).await?; // Err: channel closed

// after: rebuild the connection when the channel is gone
match sender.send_request(req).await {
    Err(e) if e.is_closed() => {
        let (new_sender, conn) = hyper::client::conn::http1::handshake(stream).await?;
        tokio::spawn(conn);
        // retry with new_sender ...
    }
    other => other,
}
Defensive patterns

Strategy: fallback

Type guard

fn is_channel_closed(err: &hyper::Error) -> bool {
    err.is_closed()
}

Try / catch

match sender.send_request(req).await {
    Err(e) if e.is_closed() => {
        let (sender, conn) = hyper::client::conn::http1::handshake(new_stream()).await?;
        tokio::spawn(conn);
        // retry with the fresh sender
    }
    other => return other,
}

Prevention

When it happens

Trigger: Client send_request fails because the dispatch sender was dropped (client/dispatch.rs:79); a Body sender's channel receiver was dropped (body/incoming.rs:367/374/391/399/401); an HTTP/2 client stream's channel closed (client/conn/http2.rs:99).

Common situations: The connection-driving future was dropped (task cancelled) while you still hold a SendRequest handle; a streaming body producer was dropped before sending the end; a client is reused after its background connection task already finished; backpressure where the consumer is gone.

Related errors


AI-assisted analysis of hyperium/hyper@084473f728 (2026-08-06). Data as JSON: /data/errors/a679c309d2885974.json. Report an issue: GitHub.