mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Add thread attachment operations with coordinated deletion (#44350)
## Why Attachment mutations must coordinate with thread deletion so queued requests cannot use metadata belonging to a deleted thread. ## What changed - Add typed `ThreadStore` operations for idempotent attachment creation, paginated listing, and removal, with capability detection and unsupported defaults. - Implement attachment operations for SQLite-backed `LocalThreadStore`, including unloaded threads, and preserve invalid-request and missing-thread errors. - Coordinate attachment mutations with lifecycle locks and move SQLite cleanup into thread-store deletion. Remove state rows only after rollout deletion, preserving retries after cleanup failures. - Give the in-memory store ownership of its associated SQLite cleanup and remove duplicate cleanup from the app-server. ## Testing Add coverage for attachment pagination and idempotency, unsupported stores, error categories, lifecycle exclusion, mutations queued behind single and batch deletion, cleanup failure retries, and in-memory state cleanup. Extend the app-server descendant deletion test to verify SQLite rows are removed. GitOrigin-RevId: f0157538c7c5c2ee1362ce40eac157101c5d4505
This commit is contained in:
1 parent
e444aa99d7
commit
2df0b747ba
11 files changed
+834
-20
No files matched your search
@@ -1,8 +1,9 @@
|
||||
//! Local hard-delete support for persisted threads.
|
||||
//!
|
||||
//! Existing rollout files are deleted before this operation reports success. A rollout file that
|
||||
//! vanishes after discovery counts as already deleted. The app-server deletes main state DB rows
|
||||
//! after every associated rollout is removed; this module deletes local history projection rows.
|
||||
//! vanishes after discovery counts as already deleted. Main state DB rows are deleted after every
|
||||
//! associated rollout is removed, under the same lifecycle lock, so queued artifact mutations cannot
|
||||
//! use deleted thread metadata.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
@@ -72,7 +73,19 @@ pub(super) async fn delete_thread(
|
||||
let thread_rollouts = ThreadRollouts::from_index(&reference_index, thread_id);
|
||||
ensure_no_external_references(&reference_index, std::slice::from_ref(&thread_rollouts))?;
|
||||
let mut writer_guards = store.acquire_writer_locks(&[thread_id]).await?;
|
||||
delete_thread_after_reference_check(store, thread_rollouts, &mut writer_guards).await
|
||||
let found_rollout =
|
||||
match delete_thread_after_reference_check(store, thread_rollouts, &mut writer_guards).await
|
||||
{
|
||||
Ok(()) => true,
|
||||
Err(ThreadStoreError::ThreadNotFound { .. }) => false,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let deleted_state_rows = delete_state_rows(store, &[thread_id]).await?;
|
||||
if found_rollout || deleted_state_rows > 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ThreadStoreError::ThreadNotFound { thread_id })
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn delete_threads(
|
||||
@@ -111,9 +124,26 @@ pub(super) async fn delete_threads(
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
// Retain the complete retry graph until every rollout has been removed.
|
||||
delete_state_rows(store, &thread_ids).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_state_rows(
|
||||
store: &LocalThreadStore,
|
||||
thread_ids: &[codex_protocol::ThreadId],
|
||||
) -> ThreadStoreResult<u64> {
|
||||
let Some(state_db) = store.state_db.as_ref() else {
|
||||
return Ok(0);
|
||||
};
|
||||
state_db
|
||||
.delete_threads_strict(thread_ids)
|
||||
.await
|
||||
.map_err(|err| ThreadStoreError::Internal {
|
||||
message: format!("failed to delete thread state: {err}"),
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_no_external_references(
|
||||
reference_index: &RolloutReferenceIndex,
|
||||
thread_rollouts: &[ThreadRollouts],
|
||||
|
||||
Reference in new issue
Block a user