Files
codex/codex-rs/exec-server/src/websocket_pong_watchdog.rs
T
richardopenai 042e61726d [codex] bound Rendezvous WebSocket liveness (#30643)
## Summary

- require a Pong within 60 seconds for established Noise Rendezvous
WebSockets on both the harness and executor
- bound steady-state WebSocket writes and harness event delivery so
backpressure cannot mask the deadline
- classify executor disconnects with bounded reasons and feed them into
the existing reconnect metric and structured log
- cover silent peers, responsive peers, continuous non-Pong traffic, and
local application backpressure

## Why

The existing periodic Pings did not track Pongs, so a half-open or
blackholed connection could remain stuck until the operating system's
TCP timeout. This adds the smallest explicit liveness contract without
new spans, RTT histograms, feature flags, or TCP diagnostics.

## Testing

- `just test -p codex-exec-server` on devbox `richard-6` — 300 passed, 2
skipped
- `just fix -p codex-exec-server`
- `just fmt`
- independent correctness, performance/security, and YAGNI reviews — no
findings
2026-07-01 14:15:34 -07:00

45 lines
1.1 KiB
Rust

use std::time::Duration;
use tokio::time::Instant;
#[cfg(test)]
pub(crate) const WEBSOCKET_PONG_TIMEOUT: Duration = Duration::from_millis(100);
#[cfg(not(test))]
pub(crate) const WEBSOCKET_PONG_TIMEOUT: Duration = Duration::from_secs(60);
pub(crate) const WEBSOCKET_PONG_TIMEOUT_REASON: &str = "pong_timeout";
/// Tracks whether a WebSocket peer has acknowledged a keepalive ping.
pub(crate) struct WebSocketPongWatchdog {
timeout: Duration,
deadline: Option<Instant>,
}
impl WebSocketPongWatchdog {
pub(crate) fn new(timeout: Duration) -> Self {
Self {
timeout,
deadline: None,
}
}
pub(crate) fn ping_sent(&mut self, now: Instant) {
self.deadline.get_or_insert(now + self.timeout);
}
pub(crate) fn deadline(&self) -> Option<Instant> {
self.deadline
}
pub(crate) fn write_deadline(&self, now: Instant) -> Instant {
self.deadline.unwrap_or(now + self.timeout)
}
pub(crate) fn received_pong(&mut self) {
self.deadline = None;
}
}
#[cfg(test)]
#[path = "websocket_pong_watchdog_tests.rs"]
mod tests;