tokio-rs/tokio · error · io::Error

failed to write entire datagram to socket

Error message

failed to write entire datagram to socket

What it means

Runtime error from `UdpFramed::poll_flush` (udp/frame.rs:165). After `poll_send_to`, the number of bytes written is less than the full datagram (`n != wr.len()`), so the entire datagram was not transmitted; Tokio returns `io::ErrorKind::Other`. UDP `send_to` is normally atomic, so a short send indicates the socket/OS refused part of the datagram.

Source

Thrown at tokio-util/src/udp/frame.rs:165

        }

        let Self {
            ref socket,
            ref mut out_addr,
            ref mut wr,
            ..
        } = *self;

        let n = ready!(socket.borrow().poll_send_to(cx, wr, *out_addr))?;

        let wrote_all = n == self.wr.len();
        self.wr.clear();
        self.flushed = true;

        let res = if wrote_all {
            Ok(())
        } else {
            Err(io::Error::new(
                io::ErrorKind::Other,
                "failed to write entire datagram to socket",
            )
            .into())
        };

        Poll::Ready(res)
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        ready!(self.poll_flush(cx))?;
        Poll::Ready(Ok(()))
    }
}

impl<C, T> UdpFramed<C, T>
where
    T: Borrow<UdpSocket>,

View on GitHub (pinned to 625954f365)

Solutions

  1. Keep each encoded frame within the path MTU / OS datagram size limit (commonly <= 1472 bytes for IPv4 UDP, or <= 65467 for max IPv4).
  2. Ensure one logical frame maps to one datagram when using `UdpFramed`.
  3. Handle the error and decide to drop or retry that datagram.
  4. Check and, if needed, raise `SO_SNDBUF` on the socket.

Example fix

// before: encoding a frame larger than a datagram should carry
let framed = UdpFramed::new(socket, codec);
framed.send((big_item, addr)).await?;

// after: bound the encoded size per frame
const MAX_DGRAM: usize = 1472; // IPv4, typical MTU 1500 - 20 - 8
if encoded_len(&item) > MAX_DGRAM {
    return Err(anyhow!("datagram exceeds MTU"));
}
framed.send((item, addr)).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Bound the encoded frame to a single datagram
const MAX_DGRAM: usize = 1472;
if encoded_len(&item) > MAX_DGRAM {
    return Err(io::Error::new(io::ErrorKind::Other, "datagram exceeds MTU").into());
}

Type guard

fn is_short_datagram(e: &io::Error) -> bool {
    e.kind() == io::ErrorKind::Other && e.to_string().contains("entire datagram")
}

Try / catch

if let Err(e) = framed.send((item, addr)).await {
    if is_short_datagram(&e) { /* drop oversized datagram or shrink payload */ continue; }
    return Err(e.into());
}

Prevention

When it happens

Trigger: `UdpFramed` sink flush where `poll_send_to` returns fewer bytes than the encoded frame length: oversized datagram relative to MTU/socket limits, a connected socket quirk, or a non-standard `AsyncWrite`/socket shim.

Common situations: Datagram larger than the path MTU or the OS `SO_SNDBUF`; platform-specific short-send behavior; writing through a wrapper that fragments; mis-encoded frame larger than a single datagram should carry.

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/5a91c5a56e7f2b46. Report an issue: GitHub.