Pumpkin-MC/Pumpkin · error · WritingError

IO error

Error message

IO error: {0}

What it means

WritingError::IoError wraps a std::io::Error (via #[from]) that occurred while writing an encoded packet to the underlying transport, typically the TCP stream. The packet serialization itself succeeded; the syscall/socket layer failed. It is the lowest-level failure in the write path and often means the connection is already broken.

Solutions

  1. Match on the inner io::ErrorKind: BrokenPipe/ConnectionReset/ConnectionAborted mean the peer is gone — close the connection, don't retry.
  2. Filter dead connections out of broadcast lists before sending.
  3. For WouldBlock/TimedOut, consider retrying or tearing down per your tolerance.
  4. Enable TCP keepalive and handle disconnect events promptly so writes target live sockets.

Example fix

// before: treating all write failures alike
packet.write(stream).await?;

// after: distinguish peer disconnects
if let Err(WritingError::IoError(e)) = packet.write(stream).await {
    if matches!(e.kind(), ErrorKind::BrokenPipe | ErrorKind::ConnectionReset) {
        debug!("client gone: {e}");
        return Ok(()); // drop connection
    }
    return Err(e.into());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// skip connections known to be closed before broadcasting
let targets = clients.iter().filter(|c| c.is_open()).collect::<Vec<_>>();

Try / catch

if let Err(WritingError::IoError(e)) = packet.write(&mut stream).await {
    match e.kind() {
        ErrorKind::BrokenPipe | ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => {
            debug!("peer disconnected: {e}");
            disconnect(peer_id).await;
        }
        _ => return Err(e.into()),
    }
}

Prevention

When it happens

Trigger: Any packet write (write_all on the TCP stream) when the socket is closed or reset by the peer (BrokenPipe, ConnectionReset), the send buffer is full, or the OS reports a network error.

Common situations: Server broadcasting a packet to a client that just disconnected (connection reset by peer); writing to a closed keep-alive connection; network outage mid-game; socket buffer exhaustion under heavy load.

Related errors


AI-assisted analysis of Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09). Data as JSON: /api/errors/3eea590b081de540. Report an issue: GitHub.

Appendix: source

Thrown at crates/pumpkin-protocol/src/ser/mod.rs:39

    #[error("EOF, Tried to read {0} but No bytes left to consume")]
    CleanEOF(String),
    #[error("incomplete: {0}")]
    Incomplete(String),
    #[error("too large: {0}")]
    TooLarge(String),
    #[error("{0}")]
    Message(String),
}

impl serde::de::Error for ReadingError {
    fn custom<T: std::fmt::Display>(msg: T) -> Self {
        Self::Message(msg.to_string())
    }
}

#[derive(Debug, Error)]
pub enum WritingError {
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),
    #[error("Serde failure: {0}")]
    Serde(String),
    #[error("Packet is not supported in Minecraft version {0:?}")]
    UnsupportedVersion(JavaMinecraftVersion),
    #[error("Failed to serialize packet: {0}")]
    Message(String),
}

impl serde::ser::Error for WritingError {
    fn custom<T: std::fmt::Display>(msg: T) -> Self {
        Self::Serde(msg.to_string())
    }
}

struct NetworkReadDataSource<'a, R: NetworkReadExt + ?Sized>(&'a mut R);

impl<'a, R: NetworkReadExt + ?Sized> pumpkin_nbt::deserializer::NbtDataSource<'a>

View on GitHub (pinned to 8d4639e25a)