rustdesk/rustdesk-server · warning
Timeout
Error message
Timeout
What it means
In the relay server's pairing/forwarding loop, a tokio interval timer checks that data was received within the last 30 seconds; if `last_recv_time` is older, the relay connection is torn down with a bail!("Timeout"). This prevents relayed connections from holding sockets and the pairing slot open forever when a peer stops sending. The raised task is `relay`, invoked from `make_pair_`.
Solutions
- Ensure clients send periodic keepalive/heartbeat data over the relay connection so last_recv_time is refreshed.
- Check network path (NAT timeouts, firewall idle-connection drops) between client and relay; enable TCP keepalive.
- Retry the relay connection from the client after the timeout; the relay is expected to be restarted/re-paired.
- If 30s is too aggressive for your deployment, raise the threshold in src/relay_server.rs and recompile.
Example fix
// client side: send a heartbeat periodically
let mut ka = tokio::time::interval(Duration::from_secs(10));
tokio::select! {
_ = ka.tick() => stream.send(&keepalive_msg).await?,
n = stream.recv() => { /* refresh last_recv_time on relay */ }
} Defensive patterns
Strategy: retry
Validate before calling
// before connecting, confirm NAT/firewall allows the relay port and long-lived TCP nc -zvw5 <relay-host> 21117
Try / catch
match relay_connect().await {
Err(e) if e.to_string().contains("Timeout") => schedule_reconnect_with_backoff(),
Err(e) => log::error!("relay failed: {e}"),
Ok(s) => pump_with_keepalive(s).await,
} Prevention
- Send application-level heartbeats at intervals well under 30s.
- Enable TCP keepalive on relay sockets.
- Raise NAT/firewall idle timeouts for the relay port.
- Implement automatic re-pair on the client when the relay drops.
When it happens
Trigger: A relayed client or its peer stops sending any bytes for >30 seconds while the relay select loop is running (timer.tick() branch fires and elapsed().as_secs() > 30).
Common situations: Client behind aggressive NAT/firewall dropping idle TCP; remote peer crashed or network outage mid-session; mobile client suspending and killing its socket; very long idle relay sessions with no keepalive.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of rustdesk/rustdesk-server@a7736be5e4 (2026-09-09).
Data as JSON: /api/errors/4671929fbb0a67c2.
Report an issue: GitHub.
Appendix: source
Thrown at src/relay_server.rs:564
let nb = bytes.len() * 8;
if blacked || downgrade {
blacklist_limiter.consume(nb).await;
} else {
limiter.consume(nb).await;
}
total_limiter.consume(nb).await;
total += nb;
total_s += nb;
if !bytes.is_empty() {
peer.send_raw(bytes.into()).await?;
}
} else {
break;
}
},
_ = timer.tick() => {
if last_recv_time.elapsed().as_secs() > 30 {
bail!("Timeout");
}
}
}
let n = tm.elapsed().as_millis() as usize;
if n >= 1_000 {
if BLOCKLIST.read().await.get(&ip).is_some() {
log::info!("{} blocked", ip);
break;
}
blacked = BLACKLIST.read().await.get(&ip).is_some();
tm = std::time::Instant::now();
let speed = total_s / n;
if speed > highest_s {
highest_s = speed;
}
elapsed += n;
USAGE.write().await.insert(View on GitHub (pinned to a7736be5e4)