{"record":{"id":"4c1d71a0d9da5bf2","repo":"tonhowtf/omniget","slug":"relay-closed-connection-unexpectedly-mod","errorCode":null,"errorMessage":"Relay closed connection unexpectedly","messagePattern":"Relay closed connection unexpectedly","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/platforms/p2p/mod.rs","lineNumber":42,"sourceCode":"async fn connect_relay() -> anyhow::Result<TcpStream> {\n    let addr = relay_addr();\n    let stream = tokio::time::timeout(\n        std::time::Duration::from_secs(10),\n        TcpStream::connect(&addr),\n    )\n    .await\n    .map_err(|_| anyhow!(\"Connection to relay timed out (10s)\"))?\n    .map_err(|e| anyhow!(\"Failed to connect to relay {}: {}\", addr, e))?;\n    Ok(stream)\n}\n\nasync fn read_line(\n    reader: &mut BufReader<tokio::io::ReadHalf<TcpStream>>,\n) -> anyhow::Result<String> {\n    let mut line = String::new();\n    let n = reader.read_line(&mut line).await?;\n    if n == 0 {\n        anyhow::bail!(\"Relay closed connection unexpectedly\");\n    }\n    Ok(line.trim_end().to_string())\n}\n\nfn check_relay_error(line: &str) -> anyhow::Result<()> {\n    if let Some(err) = line.strip_prefix(\"ERROR \") {\n        anyhow::bail!(\"Relay error: {}\", err);\n    }\n    Ok(())\n}\n\npub struct P2pDownloader;\n\nimpl P2pDownloader {\n    pub fn new() -> Self {\n        Self\n    }\n}","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/platforms/p2p/mod.rs#L24-L60","documentation":"read_line wraps a TCP read from the relay; a return of 0 bytes means EOF — the relay peer closed the socket without sending the expected line. The function converts that silent EOF into an explicit error so callers don't loop forever on empty input.","triggerScenarios":"The relay server (or the other P2P peer via the relay) closes the TCP connection while download or run_sender is waiting for the next protocol line.","commonSituations":"Relay crash or restart mid-transfer; intermediary/load-balancer idle timeout; the peer process exiting; firewall/NAT dropping a long-lived connection.","solutions":["Retry the operation, re-establishing the TCP connection to the relay","Add keepalive/ping messages to the P2P protocol so idle connections aren't dropped","Check relay server logs for crashes or restarts at the time of failure","Increase relay-side idle timeouts for long transfers"],"exampleFix":"// before\nlet line = read_line(&mut reader).await?;\n// after\nlet line = match read_line(&mut reader).await {\n    Err(e) if e.to_string().contains(\"closed connection\") => {\n        reconnect_relay().await?;\n        read_line(&mut reader).await?\n    }\n    other => other?,\n};","handlingStrategy":"retry","validationCode":"// check socket health before protocol steps\nstream.set_keepalive(Some(Duration::from_secs(30)))?;","typeGuard":null,"tryCatchPattern":"loop {\n    match read_line(&mut reader).await {\n        Err(e) if e.to_string().contains(\"closed connection\") => {\n            attempts += 1;\n            if attempts > 3 { return Err(e); }\n            reconnect().await?;\n            continue;\n        }\n        other => break other,\n    }\n}","preventionTips":["Enable TCP keepalive on relay connections","Add application-level ping/pong to the relay protocol","Handle reconnects transparently in the relay client"],"tags":["network","tcp","p2p","relay"],"backgroundTag":"connection-refused","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}