code-mode: make session shutdown authoritative (#29287)

## Summary

- Give each session and cell a hierarchical cancellation token.
- Track cell tasks so shutdown waits for admitted actors without polling
the registry.
- Make shutdown authoritative across concurrent admission and
non-cooperative callbacks.

## Why

A best-effort registry scan can miss cells admitted concurrently or
blocked behind the registry lock.

## Impact

Session shutdown reliably stops every admitted cell and rejects new work
once shutdown begins.

## Validation

- Stack-tip validation: `just test -p codex-code-mode -p
codex-code-mode-protocol` (70 passed).
- Parent branch: `cconger/code-mode-runtime-compact-03c-terminal-state`.
This commit is contained in:
Channing Conger
2026-06-21 13:15:38 -07:00
committed by GitHub
parent f774455c3a
commit 9c79d87d06
6 changed files with 107 additions and 37 deletions
@@ -108,3 +108,80 @@ async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_loa
);
runtime.shutdown().await.unwrap();
}
fn execute_request(source: &str) -> CreateCellRequest {
CreateCellRequest {
tool_call_id: "call-1".to_string(),
enabled_tools: Vec::new(),
source: source.to_string(),
}
}
#[tokio::test]
#[expect(
clippy::await_holding_invalid_type,
reason = "test holds the registry lock to force admission ahead of shutdown"
)]
async fn shutdown_rejects_cell_admission_queued_before_the_registry_lock() {
let runtime = Arc::new(SessionRuntime::new(Arc::new(RecordingDelegate)));
let cells = runtime.inner.cells.lock().await;
let execution = runtime.execute(
execute_request("while (true) {}"),
ObserveMode::YieldAfter(Duration::from_millis(/*millis*/ 1)),
);
tokio::pin!(execution);
std::future::poll_fn(|context| match execution.as_mut().poll(context) {
Poll::Pending => Poll::Ready(()),
Poll::Ready(Ok(_)) => panic!("execution completed before the registry lock was released"),
Poll::Ready(Err(error)) => {
panic!("execution failed before the registry lock was released: {error}")
}
})
.await;
let shutdown = runtime.shutdown();
tokio::pin!(shutdown);
std::future::poll_fn(|context| match shutdown.as_mut().poll(context) {
Poll::Pending => Poll::Ready(()),
Poll::Ready(Ok(())) => panic!("shutdown completed before acquiring the registry lock"),
Poll::Ready(Err(error)) => {
panic!("shutdown failed before acquiring the registry lock: {error}")
}
})
.await;
assert!(!runtime.is_alive());
drop(cells);
assert!(matches!(execution.await, Err(Error::ShuttingDown)));
assert_eq!(shutdown.await, Ok(()));
}
#[tokio::test]
async fn drop_terminates_cells_when_the_registry_is_locked() {
let runtime = SessionRuntime::new(Arc::new(RecordingDelegate));
let started = runtime
.execute(
execute_request("while (true) {}"),
ObserveMode::YieldAfter(Duration::from_millis(/*millis*/ 1)),
)
.await
.unwrap();
assert_eq!(started.cell_id, CellId::new("1"));
assert_eq!(
started.initial_event().await,
Ok(CellEvent::Yielded {
content_items: Vec::new(),
})
);
let inner = Arc::clone(&runtime.inner);
let cells = inner.cells.lock().await;
drop(runtime);
drop(cells);
tokio::time::timeout(Duration::from_secs(/*secs*/ 1), inner.cell_tasks.wait())
.await
.unwrap();
assert!(inner.cell_tasks.is_empty());
}