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

  1. Ensure clients send periodic keepalive/heartbeat data over the relay connection so last_recv_time is refreshed.
  2. Check network path (NAT timeouts, firewall idle-connection drops) between client and relay; enable TCP keepalive.
  3. Retry the relay connection from the client after the timeout; the relay is expected to be restarted/re-paired.
  4. 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

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.

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)