n0-computer/iroh · error · io::Error
NotConnected
NotConnected
Error message
connection closed
What it means
The noq multipath socket wrapper was asked to map a transmit's destination address while its underlying UDP socket was already closed. mapped_addr returns io::ErrorKind::NotConnected with 'connection closed' because sends can no longer be performed on a closed socket.
Solutions
- Ensure the QUIC endpoint/connection is fully shut down before closing the underlying UDP socket.
- Stop issuing transmits once the transport reports closed; treat NotConnected as terminal.
- Check for tasks holding the socket handle after shutdown and drop them.
- Guard sends with a liveness/connected check on the transport handle.
Example fix
// before
sock.send(transmit)?; // may hit closed socket
// after
if sock.is_closed() { return; }
sock.send(transmit)?; Defensive patterns
Strategy: type-guard
Type guard
fn sendable(sock: &MultipathSock, t: &Transmit) -> Result<(), std::io::Error> {
if sock.is_closed() { Err(std::io::Error::new(std::io::ErrorKind::NotConnected, "connection closed")) } else { Ok(()) }
} Try / catch
match sock.send(transmit) {
Err(e) if e.kind() == std::io::ErrorKind::NotConnected => { /* socket shut down: stop sending */ }
other => other?,
} Prevention
- Shut down connections before closing the underlying socket.
- Drop sender tasks during teardown to avoid racing sends.
- Treat NotConnected as terminal; never retry on a closed socket.
- Structure shutdown with an ordered close sequence.
When it happens
Trigger: Transmitting a packet through the transport after the socket has been closed (sock.is_closed() == true) — typically when the endpoint/transport was shut down but a queued send or poll still references it.
Common situations: Endpoint dropped/closed while in-flight packets are still being flushed, shutdown ordering issues where the QUIC connection outlives its socket, cancelled tasks racing with socket close.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/1ac2278424a96905.
Report an issue: GitHub.
Appendix: source
Thrown at iroh/src/socket/transports.rs:1156
/// [`Socket`] which expands it back to one or more [`Addr`]s and sends it
/// using the underlying [`Transports`].
#[derive(Debug)]
#[pin_project::pin_project]
pub(crate) struct Sender {
sock: Arc<Socket>,
#[pin]
sender: TransportsSender,
}
impl Sender {
/// Extracts the right [`Addr`] from the [`noq_udp::Transmit`].
///
/// Because Noq does only know about IP transports we map other transports to private
/// IPv6 Unique Local Address ranges. This extracts the transport addresses out of the
/// transmit's destination.
fn mapped_addr(&self, transmit: &noq_udp::Transmit) -> io::Result<MultipathMappedAddr> {
if self.sock.is_closed() {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"connection closed",
));
}
Ok(MultipathMappedAddr::from(transmit.destination))
}
}
impl noq::UdpSender for Sender {
fn poll_send(
self: Pin<&mut Self>,
noq_transmit: &noq_udp::Transmit,
cx: &mut Context,
) -> Poll<io::Result<()>> {
// On errors this methods prefers returning Ok(()) to Noq. Returning an error
// should only happen if the error is permanent and fatal and it will never be
// possible to send anything again. Doing so kills the Noq EndpointDriver. MostView on GitHub (pinned to 2b4de030ce)