{"record":{"id":"263b05798d54c83e","repo":"n0-computer/iroh","slug":"notconnected-263b05","errorCode":"NotConnected","errorMessage":"connection closed","messagePattern":"connection closed","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"iroh/src/socket/transports/relay.rs","lineNumber":113,"sourceCode":"        metas: &mut [noq_udp::RecvMeta],\n        recv_infos: &mut [RecvInfo],\n    ) -> Poll<io::Result<usize>> {\n        assert_eq!(bufs.len(), metas.len(), \"non matching bufs & metas\");\n        assert_eq!(\n            bufs.len(),\n            recv_infos.len(),\n            \"non matching bufs & recv_infos\"\n        );\n        let mut num_msgs = 0;\n        for i in 0..bufs.len() {\n            let buf_out = &mut bufs[i];\n            let meta_out = &mut metas[i];\n            let recv_info = &mut recv_infos[i];\n            let dm = match self.poll_recv_queue(cx) {\n                Poll::Ready(Some(recv)) => recv,\n                Poll::Ready(None) => {\n                    error!(\"relay_recv_channel closed\");\n                    return Poll::Ready(Err(io::Error::new(\n                        io::ErrorKind::NotConnected,\n                        \"connection closed\",\n                    )));\n                }\n                Poll::Pending => {\n                    break;\n                }\n            };\n\n            // This *tries* to make the datagrams fit into our buffer by re-batching them.\n            let num_segments = dm\n                .datagrams\n                .segment_size\n                .map_or(1, |ss| buf_out.len() / u16::from(ss) as usize);\n            let datagrams = dm.datagrams.take_segments(num_segments);\n            let empty_now = datagrams.contents.is_empty();\n            let empty_after = dm.datagrams.contents.is_empty();\n","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/n0-computer/iroh/blob/2b4de030ce5e0133f272871a76f0c685c63f552a/iroh/src/socket/transports/relay.rs#L95-L131","documentation":"The relay transport's receive channel was closed and drained (poll_recv_queue returned None), meaning the relay connection is gone. poll_recv surfaces this as io::ErrorKind::NotConnected with 'connection closed' and logs an error.","triggerScenarios":"Calling poll_recv on the relay transport after the relay connection terminated — the relay recv channel sent its final item and closed, so any subsequent recv hits Poll::Ready(None).","commonSituations":"Relay server disconnecting the client (idle timeout, server restart), network loss ending the relay WebSocket, application shutting down the endpoint while another task still polls recv, forgetting to reconnect after a relay drop.","solutions":["Treat NotConnected on the relay transport as terminal: stop polling and tear down/rebuild the endpoint or transport.","Implement automatic reconnection to the relay when the connection drops, before resuming recv.","Coordinate shutdown so no task polls recv after the relay connection is closed.","Check relay server logs/health if drops are frequent (timeouts, restarts)."],"exampleFix":"// before\nloop { let pkt = transport.poll_recv(cx)?; } // NotConnected after relay drop\n// after\nmatch transport.poll_recv(cx) {\n    Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::NotConnected => { reconnect_relay().await?; }\n    other => return other,\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":"fn relay_alive(t: &RelayTransport) -> bool { !t.recv_channel_closed() }","tryCatchPattern":"match transport.poll_recv(cx) {\n    Poll::Ready(Err(e)) if e.kind() == std::io::ErrorKind::NotConnected => { schedule_relay_reconnect(); Poll::Pending }\n    other => other,\n}","preventionTips":["Reconnect to the relay automatically when the connection drops.","Stop recv loops once NotConnected is observed; don't spin.","Coordinate endpoint shutdown with all recv tasks.","Watch for relay disconnects caused by idle timeouts and send periodic keep-alives."],"tags":["relay","not-connected","connection-closed","reconnect"],"backgroundTag":"not-connected","analyzedSha":"2b4de030ce5e0133f272871a76f0c685c63f552a","analyzedAt":"2026-09-08T04:26:47.755Z","contentChangedAt":"2026-09-08T04:26:47.755Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}