hyperium/hyper · error · std::io::Error

io error

Error message

io error

What it means

Produced inside the C FFI IO adapter (src/ffi/io.rs:155) in hyper_io::poll_read. When the user-supplied C read callback returns HYPER_IO_ERROR, hyper reports a std::io::Error of kind Other with the message 'io error' (ffi/io.rs:155-158). This is the FFI boundary: the error originates entirely in the C-side callback, not in hyper. Only reachable via the hyper C API (feature="ffi").

Source

Thrown at src/ffi/io.rs:155

    _: *mut hyper_context<'_>,
    _buf: *const u8,
    _buf_len: size_t,
) -> size_t {
    0
}

impl Read for hyper_io {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        mut buf: crate::rt::ReadBufCursor<'_>,
    ) -> Poll<std::io::Result<()>> {
        let buf_ptr = unsafe { buf.as_mut() }.as_mut_ptr().cast::<u8>();
        let buf_len = buf.remaining();

        match (self.read)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
            HYPER_IO_PENDING => Poll::Pending,
            HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                "io error",
            ))),
            ok => {
                // We have to trust that the user's read callback actually
                // filled in that many bytes... :(
                unsafe { buf.advance(ok) };
                Poll::Ready(Ok(()))
            }
        }
    }
}

impl Write for hyper_io {
    fn poll_write(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],

View on GitHub (pinned to 084473f728)

Solutions

  1. Debug the C read callback: log the underlying transport error before returning HYPER_IO_ERROR, and return HYPER_IO_PENDING for non-blocking 'would block' (EAGAIN/EWOULDBLOCK) instead of an error.
  2. Ensure the callback fills the buffer and returns the byte count on success, 0 only for clean EOF, and HYPER_IO_ERROR solely for genuine failures.
  3. Reproduce with the smallest C harness around your transport to confirm which condition maps to the error.

Example fix

// before (C callback): returns error on EAGAIN, surfacing as 'io error'
size_t my_read(void* ud, hyper_context* ctx, uint8_t* buf, size_t len) {
    ssize_t n = recv(fd, buf, len, 0);
    if (n < 0) return HYPER_IO_ERROR;     // wrongly reports EAGAIN as fatal
    return (size_t)n;
}

// after: distinguish would-block from real failure
size_t my_read(void* ud, hyper_context* ctx, uint8_t* buf, size_t len) {
    ssize_t n = recv(fd, buf, len, 0);
    if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return HYPER_IO_PENDING;
    if (n < 0) return HYPER_IO_ERROR;     /* genuine failure only */
    return (size_t)n;
}
Defensive patterns

Strategy: validation

Validate before calling

// In the C read callback, only return HYPER_IO_ERROR on genuine failures.
// Map EAGAIN/EWOULDBLOCK to HYPER_IO_PENDING so hyper awaits readiness.
size_t cb_read(void* ud, hyper_context* ctx, uint8_t* buf, size_t len) {
    ssize_t n = recv(((Fd*)ud)->fd, buf, len, 0);
    if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return HYPER_IO_PENDING;
    if (n < 0) { log_errno(); return HYPER_IO_ERROR; }
    return (size_t)n;
}

Prevention

When it happens

Trigger: An application embedding hyper via the C FFI registers a read callback (hyper_io_set_read) that returns HYPER_IO_ERROR on a transport failure; poll_read (ffi/io.rs:153-158) maps that to io::Error. Common with custom transports (curl-style, another HTTP stack bridged in).

Common situations: The C-side transport (e.g. a socket, a TLS impl, or a bridged library) hit an error and the callback signals HYPER_IO_ERROR; mis-handling read returns (returning error for EAGAIN instead of HYPER_IO_PENDING); a transport that does not distinguish 'would block' from real failure.

Related errors


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