mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
Track executor registrations across connection refresh and recovery (#46555)
## Why An executor can renew its registration while retaining its Noise identity. Connection refresh must distinguish registrations even when their keys match, and a stale registry lookup must not retire a session that recovery has already renewed. ## What changed - Track the installed session's registration and expose it through `Environment::cached_executor_registration_id()` without connecting. Publish registration changes only when the new connection is installed. - Require both registration and executor key to match before refresh reuses a session. Retry stale lookups and prevent recovery from installing a connection after retirement. - Pin session recovery to the original Noise key while allowing registration renewal to preserve the session and running processes. - Expose `CodexThread::active_turn_environment_selections()` so hosts can authorize steering against the active turn's selections, including starting and failed environments, independently of later settings updates. ## Testing Add regression coverage for registration replacement, renewal with a running process, stale refresh lookups, missing sessions, and rejection of changed Noise keys. Extend active-turn tests to check selection snapshots across settings updates, deferred startup, and turn completion. GitOrigin-RevId: 4395bc6c57f137691887e6585f52dcc5b958101c
This commit is contained in:
@@ -85,6 +85,9 @@ impl LazyRemoteExecServerClient {
|
||||
let _refresh = self.refresh_lock.lock().await;
|
||||
let (previous, attempt) = loop {
|
||||
let observed = self.cached_client();
|
||||
let observed_registration = observed
|
||||
.as_ref()
|
||||
.and_then(ExecServerClient::executor_registration_id);
|
||||
let mut transport = self.transport_params.clone().ok_or_else(|| {
|
||||
ExecServerError::Protocol(
|
||||
"connection refresh requires a Noise registry".to_string(),
|
||||
@@ -111,6 +114,7 @@ impl LazyRemoteExecServerClient {
|
||||
)
|
||||
})??;
|
||||
let executor_public_key = bundle.executor_public_key.clone();
|
||||
let executor_registration_id = bundle.executor_registration_id.clone();
|
||||
*provider = Arc::new(PrefetchedConnectProvider {
|
||||
bundle: StdMutex::new(Some(bundle)),
|
||||
provider: Arc::clone(provider),
|
||||
@@ -124,8 +128,8 @@ impl LazyRemoteExecServerClient {
|
||||
.current_client
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
// An ordinary connection may have finished during the lookup. Re-read the
|
||||
// registry rather than retire a newer client using a superseded response.
|
||||
// A new client makes the lookup stale. Recovery can also renew the
|
||||
// registration on the same client, so compare it under the connection lock.
|
||||
if !match (&observed, &*current) {
|
||||
(Some(observed), Some(current)) => Arc::ptr_eq(&observed.inner, ¤t.inner),
|
||||
(None, None) => true,
|
||||
@@ -133,18 +137,31 @@ impl LazyRemoteExecServerClient {
|
||||
} {
|
||||
continue;
|
||||
}
|
||||
// is_disconnected means terminally failed, not temporarily recovering.
|
||||
// Preserve same-executor recovery; the final probe fails fast if still recovering.
|
||||
if current.as_ref().is_some_and(|client| {
|
||||
!client.is_disconnected()
|
||||
if let Some(client) = current.as_ref() {
|
||||
let connection = client
|
||||
.inner
|
||||
.connection
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if connection.executor_registration_id != observed_registration {
|
||||
continue;
|
||||
}
|
||||
// Preserve same-executor recovery; the final probe fails fast if still recovering.
|
||||
if !matches!(connection.status, ConnectionStatus::Failed(_))
|
||||
&& connection.executor_registration_id.as_ref()
|
||||
== Some(&executor_registration_id)
|
||||
&& matches!(
|
||||
client.inner.reconnect_strategy.as_ref(),
|
||||
Some(ExecServerReconnectStrategy::NoiseRendezvous {
|
||||
executor_public_key: key, ..
|
||||
}) if key == &executor_public_key
|
||||
)
|
||||
}) {
|
||||
break (current.clone(), None);
|
||||
{
|
||||
break (current.clone(), None);
|
||||
}
|
||||
// Claim retirement before recovery can install another registration.
|
||||
// Transport shutdown and pending-work cleanup happen outside these locks.
|
||||
client.inner.retired.cancel();
|
||||
}
|
||||
// Cancellation and connection installation use the same lock. A late
|
||||
// handshake cannot install a client after its attempt has been superseded.
|
||||
|
||||
Reference in New Issue
Block a user