mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
## Why Reloading an evicted sub-agent disabled its rollout tracing, leaving later turns out of the trace. Replacement Code Mode sessions could also reuse cell IDs from earlier sessions. ## What changed - Reattach reloaded children to the root rollout trace and emit `ThreadResumed` without duplicating `ThreadStarted`. - Reopen existing threads during replay, preserving their identity and history while clearing stale runtime cell and terminal process mappings. - Allocate Code Mode cell generations across sessions for both gRPC and stdio hosts to prevent ID reuse after reload. - Record an aborted trace when session startup fails before the session loop begins. ## Testing Add regression coverage for child eviction and reload, including a failed reload, continued inference tracing, distinct cell IDs, and preserved mail and follow-up context. Extend replay tests for history preservation and reused runtime handles, and host tests for cell IDs across session replacement and reconnection. GitOrigin-RevId: 6c146c04b5cda9295afb7d973c0d5474b363a822
99 lines
3.2 KiB
Rust
99 lines
3.2 KiB
Rust
//! Cancellation of reentrant image getters through the standalone host.
|
|
|
|
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[tokio::test]
|
|
async fn cancellation_stops_reentrant_image_getter() -> Result<()> {
|
|
// Force reentry and JSON fallback at a shallow depth, independent of stack limits.
|
|
let source = r#"
|
|
let calls = 0;
|
|
const deadline = Date.now() + 60000;
|
|
const value = {
|
|
get image_url() {
|
|
switch (++calls) {
|
|
case 1: image(value); return "";
|
|
case 2: throw new Error("enter fallback");
|
|
default:
|
|
notify("started");
|
|
while (Date.now() < deadline) {}
|
|
return "";
|
|
}
|
|
}
|
|
};
|
|
try { image(value); } catch { text(calls); }
|
|
"#;
|
|
let host = HostHarness::start("grpc://127.0.0.1:0").await?;
|
|
let provider = GrpcCodeModeSessionProvider::new(host.endpoint);
|
|
let session = provider
|
|
.create_session()
|
|
.await
|
|
.map_err(anyhow::Error::msg)?;
|
|
let other_session = provider
|
|
.create_session()
|
|
.await
|
|
.map_err(anyhow::Error::msg)?;
|
|
let other_response = execute(
|
|
&other_session,
|
|
request(r#"store("alive", "yes"); text("yes");"#),
|
|
Arc::new(NoopCodeModeSessionDelegate),
|
|
)
|
|
.await?;
|
|
assert_eq!(
|
|
other_response,
|
|
text_response("g2:1", "yes", other_response.code_mode_host_duration())
|
|
);
|
|
|
|
let delegate = Arc::new(RecordingDelegate::default());
|
|
let mut pending = request(source);
|
|
pending.yield_time_ms = Some(60_000);
|
|
let started = session
|
|
.execute(pending, delegate.clone(), /*preempt*/ None)
|
|
.await
|
|
.map_err(anyhow::Error::msg)?;
|
|
let id = started.cell_id.clone();
|
|
// The second image call enters JSON fallback; wait until its getter is running.
|
|
timeout(TEST_TIMEOUT, delegate.notification_delivered.notified())
|
|
.await
|
|
.context("getter did not signal startup")?;
|
|
|
|
let result = timeout(Duration::from_secs(1), session.terminate(id.clone().into()))
|
|
.await
|
|
.context("workload did not stop within the cancellation deadline")?
|
|
.map_err(anyhow::Error::msg)?;
|
|
// The getter would otherwise run for 60 seconds and emit its counter.
|
|
assert_eq!(
|
|
result,
|
|
WaitOutcome::LiveCell(RuntimeResponse::Terminated {
|
|
code_mode_host_duration: result.code_mode_host_duration(),
|
|
cell_id: id.clone(),
|
|
content_items: Vec::new(),
|
|
})
|
|
);
|
|
let initial = timeout(TEST_TIMEOUT, started.initial_response())
|
|
.await
|
|
.context("initial termination response did not arrive")?
|
|
.map_err(anyhow::Error::msg)?;
|
|
assert_eq!(
|
|
initial,
|
|
RuntimeResponse::Terminated {
|
|
code_mode_host_duration: initial.code_mode_host_duration(),
|
|
cell_id: id,
|
|
content_items: Vec::new(),
|
|
}
|
|
);
|
|
let other_response = execute(
|
|
&other_session,
|
|
request(r#"text(load("alive"));"#),
|
|
Arc::new(NoopCodeModeSessionDelegate),
|
|
)
|
|
.await?;
|
|
assert_eq!(
|
|
other_response,
|
|
text_response("g2:2", "yes", other_response.code_mode_host_duration())
|
|
);
|
|
session.shutdown().await.map_err(anyhow::Error::msg)?;
|
|
other_session.shutdown().await.map_err(anyhow::Error::msg)?;
|
|
Ok(())
|
|
}
|