Release persistent writers when session startup is cancelled (#44183)

## Why

Cancelling a session after persistence opens but while MCP startup is still pending can leave its writer held, blocking a subsequent resume.

## What changed

Create `LiveThreadInitGuard` inside the persistence startup future so it protects the live thread while `tokio::join!` waits for other startup work. Dropping the startup future then schedules writer cleanup.

## Testing

Add a regression test that cancels a resume while MCP startup is blocked, verifies the local writer is released, and resumes the same thread successfully.

GitOrigin-RevId: 57d8624e783a7b993579a6386aea35ba845da0e3
This commit is contained in:
jif
2026-09-09 15:54:07 +00:00
committed by copyberry
parent 2bba3a29a0
commit ce2c2759eb
3 changed files with 123 additions and 7 deletions
+7 -7
View File
@@ -869,7 +869,7 @@ impl Session {
// - load history metadata (skipped for subagents)
let thread_persistence_fut = async {
if config.ephemeral {
Ok::<_, anyhow::Error>(None)
Ok::<_, anyhow::Error>(LiveThreadInitGuard::new(/*live_thread*/ None))
} else {
let live_thread = match &initial_history {
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => {
@@ -947,7 +947,8 @@ impl Session {
.await?
}
};
Ok(Some(live_thread))
// The completed result can wait in join! while the other startup work is pending.
Ok(LiveThreadInitGuard::new(Some(live_thread)))
}
}
.instrument(info_span!(
@@ -1027,11 +1028,10 @@ impl Session {
let (thread_persistence_result, state_db_ctx, (auth, mcp_projection)) =
tokio::join!(thread_persistence_fut, state_db_fut, auth_and_mcp_fut);
let mut live_thread_init =
LiveThreadInitGuard::new(thread_persistence_result.map_err(|e| {
error!("failed to initialize thread persistence: {e:#}");
e
})?);
let mut live_thread_init = thread_persistence_result.map_err(|e| {
error!("failed to initialize thread persistence: {e:#}");
e
})?;
let session_result: anyhow::Result<Arc<Self>> = async {
let rollout_path = if let Some(live_thread) = live_thread_init.as_ref() {
live_thread.local_rollout_path().await?