fix(sensing-server): handle WebSocket Lagged + add ping keepalive (#484)

Root cause: broadcast channel Lagged error caused instant disconnect
when clients fell behind 256 frames (10Hz * 50-200KB = easy to lag).
Client reconnects, immediately lags again, rapid cycling ensues.

Sensing handler: Lagged error now continues (skips missed frames)
instead of breaking. Added 30s ping interval for proxy keepalive.
Pose handler: same Lagged handling + Pong match arm.

CHANGELOG updated under Unreleased/Fixed.

Co-authored-by: Deploy Bot <deploy@example.com>
This commit is contained in:
Timothy Schwarz
2026-05-17 17:57:02 -04:00
committed by GitHub
parent 9d4f7820b2
commit 8b297dd706
2 changed files with 31 additions and 3 deletions
@@ -2023,6 +2023,10 @@ async fn handle_ws_client(mut socket: WebSocket, state: SharedState) {
info!("WebSocket client connected (sensing)");
// ADR-044/045: ping/pong keepalive to prevent proxy idle timeouts.
let mut ping_interval = tokio::time::interval(std::time::Duration::from_secs(30));
ping_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
tokio::select! {
msg = rx.recv() => {
@@ -2032,13 +2036,24 @@ async fn handle_ws_client(mut socket: WebSocket, state: SharedState) {
break;
}
}
Err(_) => break,
// Lagged: client fell behind — skip missed frames, don't disconnect.
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
tracing::debug!("WS client lagged by {n} frames, skipping");
continue;
}
Err(_) => break, // channel closed
}
}
_ = ping_interval.tick() => {
if socket.send(Message::Ping(vec![].into())).await.is_err() {
break;
}
}
msg = socket.recv() => {
match msg {
Some(Ok(Message::Close(_))) | None => break,
_ => {} // ignore client messages
Some(Ok(Message::Pong(_))) => {} // keepalive response
_ => {} // ignore other client messages
}
}
}
@@ -2207,7 +2222,12 @@ async fn handle_ws_pose_client(mut socket: WebSocket, state: SharedState) {
}
}
}
Err(_) => break,
// Lagged: skip missed frames, don't disconnect.
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
tracing::debug!("WS pose client lagged by {n} frames, skipping");
continue;
}
Err(_) => break, // channel closed
}
}
msg = socket.recv() => {
@@ -2222,6 +2242,7 @@ async fn handle_ws_pose_client(mut socket: WebSocket, state: SharedState) {
}
}
Some(Ok(Message::Close(_))) | None => break,
Some(Ok(Message::Pong(_))) => {} // keepalive response
_ => {}
}
}