mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
[codex] add code-mode host failure supervision hooks (#30110)
## Why A process host should be discarded and rebuilt after critical actor or V8 failure, while the existing in-process production path must keep its current cell-error semantics. This change establishes that failure boundary without adding the host process or remote client. ## What changed - add optional task-failure supervision to the transport-neutral code-mode session runtime - report Tokio cell-actor failures and V8 runtime-thread panics to a host-provided fail-stop handler - preserve the existing handler-less in-process behavior - make host-owned cell ID allocation fail before numeric wraparound ## Follow-up The V8 panic signal surfaced here should also be consumed by the `InProcessCodeModeSession` manager in a future change so it can fail the affected cell. This PR intentionally leaves the handler-less in-process behavior unchanged while putting the required panic tracking in place. ## Stack This is **2 of 4** in the process-owned code-mode session stack. - #30108 is merged into `main` - The next PR targets this branch ## Validation - `just test -p codex-code-mode` — 53 passed - `just argument-comment-lint -p codex-code-mode` - `just fix -p codex-code-mode`
This commit is contained in:
@@ -15,6 +15,8 @@ use crate::cell_actor::CompletionCommit;
|
||||
|
||||
struct RecordingDelegate;
|
||||
|
||||
struct PanickingClosedDelegate;
|
||||
|
||||
impl SessionRuntimeDelegate for RecordingDelegate {
|
||||
async fn invoke_tool(
|
||||
&self,
|
||||
@@ -37,6 +39,62 @@ impl SessionRuntimeDelegate for RecordingDelegate {
|
||||
fn cell_closed(&self, _cell_id: &CellId) {}
|
||||
}
|
||||
|
||||
impl SessionRuntimeDelegate for PanickingClosedDelegate {
|
||||
async fn invoke_tool(
|
||||
&self,
|
||||
_invocation: NestedToolCall,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<JsonValue, String> {
|
||||
Ok(JsonValue::Null)
|
||||
}
|
||||
|
||||
async fn notify(
|
||||
&self,
|
||||
_call_id: String,
|
||||
_cell_id: CellId,
|
||||
_text: String,
|
||||
_cancellation_token: CancellationToken,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cell_closed(&self, _cell_id: &CellId) {
|
||||
panic!("cell close panic probe");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reports_cell_actor_panics_to_the_owner() {
|
||||
let (failure_tx, mut failure_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let runtime = SessionRuntime::new_with_task_failure_handler(
|
||||
Arc::new(PanickingClosedDelegate),
|
||||
Some(Arc::new(move |reason| {
|
||||
let _ = failure_tx.send(reason);
|
||||
})),
|
||||
);
|
||||
let started = runtime
|
||||
.execute(
|
||||
execute_request(r#"text("done");"#),
|
||||
ObserveMode::YieldAfter(Duration::from_secs(1)),
|
||||
)
|
||||
.await
|
||||
.expect("start cell");
|
||||
assert_eq!(
|
||||
started.initial_event().await,
|
||||
Ok(CellEvent::Completed {
|
||||
content_items: vec![OutputItem::Text {
|
||||
text: "done".to_string(),
|
||||
}],
|
||||
error_text: None,
|
||||
})
|
||||
);
|
||||
runtime.shutdown().await.expect("shutdown runtime");
|
||||
let failure = failure_rx
|
||||
.try_recv()
|
||||
.expect("shutdown should wait for the cell failure watcher");
|
||||
assert!(failure.contains("code-mode cell 1 task failed"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn termination_rejects_a_waiting_store_commit_before_the_next_cell_can_load_it() {
|
||||
let runtime = SessionRuntime::new(Arc::new(RecordingDelegate));
|
||||
@@ -118,6 +176,26 @@ fn execute_request(source: &str) -> CreateCellRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cell_id_allocation_fails_before_wrapping() {
|
||||
let runtime = SessionRuntime::new(Arc::new(RecordingDelegate));
|
||||
runtime
|
||||
.inner
|
||||
.next_cell_id
|
||||
.store(u64::MAX, Ordering::Relaxed);
|
||||
|
||||
assert_eq!(
|
||||
runtime
|
||||
.execute(
|
||||
execute_request(r#"text("unreachable");"#),
|
||||
ObserveMode::YieldAfter(Duration::from_secs(1)),
|
||||
)
|
||||
.await
|
||||
.err(),
|
||||
Some(Error::CellIdSpaceExhausted)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[expect(
|
||||
clippy::await_holding_invalid_type,
|
||||
|
||||
Reference in New Issue
Block a user