From 5a3808b77d18668f2ec794e64de094112a2e600b Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Sun, 26 Jul 2026 22:06:49 +0000 Subject: [PATCH] Shut down the in-process outbound router explicitly (#35523) ## Why Detached processor work can retain outgoing message senders, so waiting for channel closure can keep the in-process app-server's outbound router alive during shutdown. ## What changed Add an explicit shutdown signal for the outbound router and prioritize it over pending outgoing messages. Send that signal after processor tasks are stopped so runtime shutdown no longer depends on every sender being dropped. ## Testing Add a paused-time test that verifies the router exits and closes its channel even when an outgoing sender is still retained. GitOrigin-RevId: a8f88d88a0950f50339b1c56909fedb781f178d4 --- codex-rs/app-server/src/in_process.rs | 61 +++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/codex-rs/app-server/src/in_process.rs b/codex-rs/app-server/src/in_process.rs index f75a1b750c..4a81f652e6 100644 --- a/codex-rs/app-server/src/in_process.rs +++ b/codex-rs/app-server/src/in_process.rs @@ -383,6 +383,25 @@ pub async fn start(mut args: InProcessStartArgs) -> IoResult, + mut outbound_connections: HashMap, + mut shutdown_rx: oneshot::Receiver<()>, +) { + loop { + tokio::select! { + biased; + _ = &mut shutdown_rx => break, + envelope = outgoing_rx.recv() => { + let Some(envelope) = envelope else { + break; + }; + route_outgoing_envelope(&mut outbound_connections, envelope).await; + } + } + } +} + async fn start_uninitialized(args: InProcessStartArgs) -> IoResult { let channel_capacity = args.channel_capacity.max(1); let installation_id = resolve_installation_id(&args.config.codex_home).await?; @@ -390,7 +409,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult(channel_capacity); let runtime_handle = tokio::spawn(async move { - let (outgoing_tx, mut outgoing_rx) = mpsc::channel::(channel_capacity); + let (outgoing_tx, outgoing_rx) = mpsc::channel::(channel_capacity); let auth_manager = AuthManager::shared_from_config(args.config.as_ref(), args.enable_codex_api_key_env) .await; @@ -418,11 +437,12 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult IoResult IoResult