{"record":{"id":"fcad56dbe386f197","repo":"rathole-org/rathole","slug":"heartbeat-timed-out","errorCode":null,"errorMessage":"Heartbeat timed out","messagePattern":"Heartbeat timed out","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/client.rs","lineNumber":484,"sourceCode":"        loop {\n            tokio::select! {\n                val = read_control_cmd(&mut conn) => {\n                    let val = val?;\n                    debug!( \"Received {:?}\", val);\n                    match val {\n                        ControlChannelCmd::CreateDataChannel => {\n                            let args = data_ch_args.clone();\n                            tokio::spawn(async move {\n                                if let Err(e) = run_data_channel(args).await.with_context(|| \"Failed to run the data channel\") {\n                                    warn!(\"{:#}\", e);\n                                }\n                            }.instrument(Span::current()));\n                        },\n                        ControlChannelCmd::HeartBeat => ()\n                    }\n                },\n                _ = time::sleep(Duration::from_secs(self.heartbeat_timeout)), if self.heartbeat_timeout != 0 => {\n                    return Err(anyhow!(\"Heartbeat timed out\"))\n                }\n                _ = &mut self.shutdown_rx => {\n                    break;\n                }\n            }\n        }\n\n        info!(\"Control channel shutdown\");\n        Ok(())\n    }\n}\n\nimpl ControlChannelHandle {\n    #[instrument(name=\"handle\", skip_all, fields(service = %service.name))]\n    fn new<T: 'static + Transport>(\n        service: ClientServiceConfig,\n        remote_addr: String,\n        transport: Arc<T>,","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/rathole-org/rathole/blob/a292f7ed5402f840415fc6a53827da2f34337856/src/client.rs#L466-L502","documentation":"Fires in the client's control-channel event loop when read_control_cmd does not produce a command within the heartbeat timeout: the server stopped sending heartbeats or the control connection stalled/died. The loop's select! arm for the heartbeat interval elapses, so the client treats the control channel as lost and tears it down so the connection can be re-established.","triggerScenarios":"Thrown at src/client.rs:484 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Check network stability and NAT/firewall idle timeouts between client and server","Increase heartbeat interval/timeout constants if links are high-latency","Ensure the server process is alive and not blocked; inspect server logs for the same period","Rely on the client's reconnection/backoff logic (run_control_chan_backoff) to restore the channel"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"a292f7ed5402f840415fc6a53827da2f34337856","analyzedAt":"2026-09-07T09:56:55.739Z","contentChangedAt":"2026-09-07T09:56:55.739Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}