fix(openai): allow more time for WebSocket heartbeat responses

Increase the default heartbeat timeout from 5 to 30 seconds so brief upstream stalls have more time to recover during response generation. Document that heartbeat checks also apply to active pooled connections and accept any inbound frame as liveness.

Validation: WebSocket pool tests (26 passed), strict OpenAI Clippy with all targets and features, workspace rustfmt, and diff whitespace checks passed.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-09-09 17:46:45 +08:00
co-authored by Codex
parent 18a04f1213
commit 9944041dc8
3 changed files with 10 additions and 5 deletions
@@ -36,7 +36,8 @@ const DEFAULT_MAX_CONNECTING: usize = 16;
const DEFAULT_MAX_AGE: Duration = Duration::from_mins(55);
const DEFAULT_MAINTENANCE_INTERVAL: Duration = Duration::from_secs(25);
const DEFAULT_PING_INTERVAL: Duration = Duration::from_secs(25);
const DEFAULT_PING_TIMEOUT: Duration = Duration::from_secs(5);
// 心跳也覆盖正在生成的连接,给短时链路停顿留出恢复余量。
const DEFAULT_PING_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) const DEFAULT_STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(300);
/// WebSocket 连接池。
@@ -67,9 +68,9 @@ pub struct CodexWebSocketPoolConfig {
pub max_connecting: usize,
/// 后台维护间隔;`None` 表示不启动后台任务。
pub maintenance_interval: Option<Duration>,
/// idle socket 探活 ping 间隔;`None` 表示维护时只做过期清理。
/// 池化连接的探活 ping 间隔(包括正在生成的连接);`None` 表示不主动 ping。
pub ping_interval: Option<Duration>,
/// 发送 ping 后等待上游响应的超时时间;零值表示不校验 Pong deadline。
/// 发送 ping 后等待任意入站帧的超时时间;零值表示不校验 Pong deadline。
pub ping_timeout: Duration,
/// idle socket 无活动多久后视为失活。
pub liveness_timeout: Option<Duration>,
@@ -2,7 +2,7 @@
//!
//! 每条上游 WebSocket 连接都由一个后台 pump 任务独占:
//! - 持续读取 socket:一旦观察到 `Close` / EOF / 传输错误,立即把连接标记为 `closed`。
//! - 自动回应上游 `Ping`,并按 `ping_interval` 主动 `Ping`;匹配的 `Pong` 必须在 deadline 内返回。
//! - 自动回应上游 `Ping`,并按 `ping_interval` 主动 `Ping`;收到任意入站帧即解除本次心跳 deadline。
//! - 可选 `liveness_timeout`:长时间无任何入站活动时判定连接失活并退出。
//!
//! 因此空闲连接的“死没死”在后台被实时感知;复用方只需零成本读取 [`PumpedWebSocket::is_closed`],
@@ -57,7 +57,7 @@ impl PumpLogContext {
pub(crate) struct PumpKeepalive {
/// 主动 `Ping` 间隔;`None` 表示 pump 不主动 ping(仅被动读取 + 回应上游 ping)。
pub(crate) ping_interval: Option<Duration>,
/// 主动 `Ping` 发出后等待匹配 `Pong` 的 deadline;`None` 表示不校验响应。
/// 主动 `Ping` 发出后等待任意入站帧的 deadline;`None` 表示不校验响应。
pub(crate) ping_timeout: Option<Duration>,
/// 无入站活动多久后判定失活;`None` 表示只靠显式 close/传输错误发现死亡。
pub(crate) liveness_timeout: Option<Duration>,
+4
View File
@@ -90,6 +90,10 @@ Compose 默认只绑定 `127.0.0.1`。从其他设备访问时,在应用前配
并设置 `X-Accel-Buffering: no` 和 `Cache-Control: no-cache, no-transform`。
反向代理仍需允许这些响应头生效;首个事件到达前的等待也需要足够的读取超时。
OpenAI 上游池化 WebSocket 默认每 25 秒发送一次 Ping,发出后允许等待 30 秒;
收到 Pong 或其他入站帧即解除本次心跳截止,持续无响应则以 `pong_timeout` 关闭连接。
此策略也覆盖正在生成的请求,与等待下一条上游消息的 `stream_idle_timeout_ms` 分别计时。
若 Codex 在压缩或长时间生成时出现 `error decoding response body`,这表示
客户端读取 HTTP 响应体失败。请结合网关请求诊断中的 `upstream.read.failed`、
`downstream.body.closed` 和反向代理日志判断断开位置,不能仅凭此消息认定是 JSON 格式错误。