mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## Why Remote configuration reads need a bounded wait without disrupting valid in-flight work on connections that cannot reconnect. ## What changed - Add `read_environment_config_with_timeout` to `ExecServerClient` and `Environment`, bounding RPC sending and receiving after connection establishment. Local reads retain their existing behavior. - Return a typed `ExecServerError::RpcTimedOut` and classify it as retryable for read-only preparation. - Share timeout handling with environment-info requests. Close the timed-out transport and request recovery only when a reconnect strategy exists; otherwise, keep the connection open. ## Testing Add a regression test verifying that a timed-out configuration read preserves an in-flight stdio read and allows subsequent reads on the same connection, including after a late response. GitOrigin-RevId: 6785d5846f4c3b07a6e7c505e359238609c3a6b6
69 lines
2.8 KiB
Rust
69 lines
2.8 KiB
Rust
//! Classifies typed executor failures without treating error text as a retry signal.
|
|
|
|
use std::io::ErrorKind;
|
|
|
|
use http::StatusCode;
|
|
use tokio_tungstenite::tungstenite::Error as WebSocketError;
|
|
use tokio_tungstenite::tungstenite::error::ProtocolError;
|
|
|
|
use crate::ExecServerError;
|
|
use crate::client::is_retryable_registry_error;
|
|
use crate::rpc::SESSION_ALREADY_ATTACHED_ERROR_CODE;
|
|
|
|
impl ExecServerError {
|
|
/// Identifies transient failures that read-only preparation can retry.
|
|
///
|
|
/// Includes transient handshake responses and connection EOFs, including wrapped
|
|
/// failures. Authentication, TLS, and malformed protocol errors are excluded.
|
|
/// This is public because callers in `codex-core` also classify capability discovery.
|
|
pub fn is_retryable_preparation_error(&self) -> bool {
|
|
let mut error = self;
|
|
while let Self::ConnectionAttempt(source) = error {
|
|
error = source;
|
|
}
|
|
if is_retryable_registry_error(error) {
|
|
return true;
|
|
}
|
|
match error {
|
|
Self::Closed
|
|
| Self::Disconnected(_)
|
|
| Self::WebSocketConnectTimeout { .. }
|
|
| Self::RpcTimedOut { .. }
|
|
| Self::InitializeTimedOut { .. } => true,
|
|
Self::WebSocketConnect { source, .. } => match source {
|
|
WebSocketError::ConnectionClosed
|
|
| WebSocketError::AlreadyClosed
|
|
| WebSocketError::Protocol(
|
|
ProtocolError::HandshakeIncomplete
|
|
| ProtocolError::ResetWithoutClosingHandshake,
|
|
) => true,
|
|
WebSocketError::Http(response) => {
|
|
response.status().is_server_error()
|
|
|| matches!(
|
|
response.status(),
|
|
StatusCode::REQUEST_TIMEOUT
|
|
| StatusCode::CONFLICT
|
|
| StatusCode::TOO_MANY_REQUESTS
|
|
)
|
|
}
|
|
WebSocketError::Io(error) => matches!(
|
|
error.kind(),
|
|
ErrorKind::ConnectionRefused
|
|
| ErrorKind::ConnectionReset
|
|
| ErrorKind::ConnectionAborted
|
|
| ErrorKind::NetworkDown
|
|
| ErrorKind::NetworkUnreachable
|
|
| ErrorKind::HostUnreachable
|
|
| ErrorKind::BrokenPipe
|
|
| ErrorKind::NotConnected
|
|
| ErrorKind::UnexpectedEof
|
|
| ErrorKind::TimedOut
|
|
),
|
|
_ => false,
|
|
},
|
|
Self::Server { code, .. } if *code == SESSION_ALREADY_ATTACHED_ERROR_CODE => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|