fix(app-server): speed up shutdown (#23578)

## Why

Pressing `Ctrl+C` or `Ctrl+D` in the TUI could make Codex pause during
shutdown when app-server background work still held outbound sender
clones.

Shutdown tracing against the current `~/.codex` path found three
relevant holders:

- `SkillsWatcher` kept its event-loop task alive until the shutdown
timeout path.
- `AppServerAttestationProvider` retained a strong
`Arc<OutgoingMessageSender>`, which could keep outbound teardown waiting
after the processor task had exited.
- A background `apps/list` task could still own an outbound sender when
shutdown began, causing the in-process app-server runtime to wait for
its outbound channel to close.

## What Changed

- Give `SkillsWatcher` an explicit shutdown `CancellationToken` and
cancel it from app-server teardown so its event loop drops the outbound
sender promptly.
- Change `AppServerAttestationProvider` to keep a
`Weak<OutgoingMessageSender>` and return immediately when it can no
longer be upgraded.
- Give `AppsRequestProcessor` a shutdown `CancellationToken` and cancel
in-flight background `apps/list` work during teardown.

## How to Test

1. Start Codex TUI from a real home configuration.
2. Press `Ctrl+C`.
3. Confirm Codex exits promptly instead of pausing during shutdown.
4. Repeat with `Ctrl+D` and confirm the same prompt exit path.

Focused manual trace validation from the investigation:

- Before the full fix, reproduced shutdown traces showed outbound
teardown waiting on lingering owners, including `attestation.provider=1`
and later `apps.list.task=1`.
- After the fix, fresh real-home `Ctrl+D` traces showed
`app_server.runtime.outbound_state_after_processor_join` with
`owners=none`, `app_server.runtime.wait_outbound_handle = 0ms`, and
total TUI app-server shutdown around `18ms`.

Targeted validation:

- `RUST_MIN_STACK=8388608 cargo test -p codex-app-server`
This commit is contained in:
Felipe Coury authored and GitHub committed 2026-05-20 17:30:19 +00:00
1 parent c0f7e1b99f
commit 050a2e2668
5 files changed
+54 -8

No files matched your search

+6 -3
View File
@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::sync::Weak;
use axum::http::HeaderValue;
use codex_app_server_protocol::AttestationGenerateParams;
@@ -22,13 +23,13 @@ pub(crate) fn app_server_attestation_provider(
thread_state_manager: ThreadStateManager,
) -> Arc<dyn AttestationProvider> {
Arc::new(AppServerAttestationProvider {
outgoing,
outgoing: Arc::downgrade(&outgoing),
thread_state_manager,
})
}
struct AppServerAttestationProvider {
outgoing: Arc<OutgoingMessageSender>,
outgoing: Weak<OutgoingMessageSender>,
thread_state_manager: ThreadStateManager,
}
@@ -42,7 +43,9 @@ impl std::fmt::Debug for AppServerAttestationProvider {
impl AttestationProvider for AppServerAttestationProvider {
fn header_for_request(&self, context: AttestationContext) -> GenerateAttestationFuture<'_> {
let outgoing = self.outgoing.clone();
let Some(outgoing) = self.outgoing.upgrade() else {
return Box::pin(async { None });
};
let thread_state_manager = self.thread_state_manager.clone();
Box::pin(async move {
request_attestation_header_value_with_timeout(