rathole-org/rathole · error
Heartbeat timed out
Error message
Heartbeat timed out
What it means
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.
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
Defensive patterns
Strategy: retry
When it happens
Trigger: Thrown at src/client.rs:484 when the library encounters an invalid state.
Common situations: See trigger scenarios.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
AI-assisted analysis of rathole-org/rathole@a292f7ed54 (2026-09-07).
Data as JSON: /api/errors/fcad56dbe386f197.
Report an issue: GitHub.
Appendix: source
Thrown at src/client.rs:484
loop {
tokio::select! {
val = read_control_cmd(&mut conn) => {
let val = val?;
debug!( "Received {:?}", val);
match val {
ControlChannelCmd::CreateDataChannel => {
let args = data_ch_args.clone();
tokio::spawn(async move {
if let Err(e) = run_data_channel(args).await.with_context(|| "Failed to run the data channel") {
warn!("{:#}", e);
}
}.instrument(Span::current()));
},
ControlChannelCmd::HeartBeat => ()
}
},
_ = time::sleep(Duration::from_secs(self.heartbeat_timeout)), if self.heartbeat_timeout != 0 => {
return Err(anyhow!("Heartbeat timed out"))
}
_ = &mut self.shutdown_rx => {
break;
}
}
}
info!("Control channel shutdown");
Ok(())
}
}
impl ControlChannelHandle {
#[instrument(name="handle", skip_all, fields(service = %service.name))]
fn new<T: 'static + Transport>(
service: ClientServiceConfig,
remote_addr: String,
transport: Arc<T>,View on GitHub (pinned to a292f7ed54)