diff --git a/rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/main.rs b/rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/main.rs index 029287c1c..12c482359 100644 --- a/rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/main.rs +++ b/rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/src/main.rs @@ -1883,6 +1883,10 @@ async fn handle_ws_client(mut socket: WebSocket, state: SharedState) { info!("WebSocket client connected (sensing)"); + // Ping keepalive to prevent proxy idle timeouts (Caddy, nginx, etc.) + 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() => { @@ -1892,13 +1896,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 } } }