mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
feat: mem v2 - PR6 (consolidation) (#11374)
This commit is contained in:
1 parent
2c9be54c9a
commit
674799d356
7 files changed
+910
-156
No files matched your search
@@ -885,6 +885,7 @@ mod tests {
|
||||
use sqlx::Row;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use uuid::Uuid;
|
||||
@@ -1125,6 +1126,133 @@ mod tests {
|
||||
let _ = tokio::fs::remove_dir_all(codex_home).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stage1_concurrent_claim_for_same_thread_is_conflict_safe() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
runtime
|
||||
.upsert_thread(&test_thread_metadata(
|
||||
&codex_home,
|
||||
thread_id,
|
||||
codex_home.join("workspace"),
|
||||
))
|
||||
.await
|
||||
.expect("upsert thread");
|
||||
|
||||
let owner_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let owner_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let thread_id_a = thread_id;
|
||||
let thread_id_b = thread_id;
|
||||
let runtime_a = Arc::clone(&runtime);
|
||||
let runtime_b = Arc::clone(&runtime);
|
||||
let claim_with_retry = |runtime: Arc<StateRuntime>,
|
||||
thread_id: ThreadId,
|
||||
owner: ThreadId| async move {
|
||||
for attempt in 0..5 {
|
||||
match runtime
|
||||
.try_claim_stage1_job(thread_id, owner, 100, 3_600, 64)
|
||||
.await
|
||||
{
|
||||
Ok(outcome) => return outcome,
|
||||
Err(err) if err.to_string().contains("database is locked") && attempt < 4 => {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
}
|
||||
Err(err) => panic!("claim stage1 should not fail: {err}"),
|
||||
}
|
||||
}
|
||||
panic!("claim stage1 should have returned within retry budget")
|
||||
};
|
||||
|
||||
let (claim_a, claim_b) = tokio::join!(
|
||||
claim_with_retry(runtime_a, thread_id_a, owner_a),
|
||||
claim_with_retry(runtime_b, thread_id_b, owner_b),
|
||||
);
|
||||
|
||||
let claim_outcomes = vec![claim_a, claim_b];
|
||||
let claimed_count = claim_outcomes
|
||||
.iter()
|
||||
.filter(|outcome| matches!(outcome, Stage1JobClaimOutcome::Claimed { .. }))
|
||||
.count();
|
||||
assert_eq!(claimed_count, 1);
|
||||
assert!(
|
||||
claim_outcomes.iter().all(|outcome| {
|
||||
matches!(
|
||||
outcome,
|
||||
Stage1JobClaimOutcome::Claimed { .. } | Stage1JobClaimOutcome::SkippedRunning
|
||||
)
|
||||
}),
|
||||
"unexpected claim outcomes: {claim_outcomes:?}"
|
||||
);
|
||||
|
||||
let _ = tokio::fs::remove_dir_all(codex_home).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stage1_concurrent_claims_respect_running_cap() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id");
|
||||
runtime
|
||||
.upsert_thread(&test_thread_metadata(
|
||||
&codex_home,
|
||||
thread_a,
|
||||
codex_home.join("workspace-a"),
|
||||
))
|
||||
.await
|
||||
.expect("upsert thread a");
|
||||
runtime
|
||||
.upsert_thread(&test_thread_metadata(
|
||||
&codex_home,
|
||||
thread_b,
|
||||
codex_home.join("workspace-b"),
|
||||
))
|
||||
.await
|
||||
.expect("upsert thread b");
|
||||
|
||||
let owner_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let owner_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id");
|
||||
let runtime_a = Arc::clone(&runtime);
|
||||
let runtime_b = Arc::clone(&runtime);
|
||||
|
||||
let (claim_a, claim_b) = tokio::join!(
|
||||
async move {
|
||||
runtime_a
|
||||
.try_claim_stage1_job(thread_a, owner_a, 100, 3_600, 1)
|
||||
.await
|
||||
.expect("claim stage1 thread a")
|
||||
},
|
||||
async move {
|
||||
runtime_b
|
||||
.try_claim_stage1_job(thread_b, owner_b, 101, 3_600, 1)
|
||||
.await
|
||||
.expect("claim stage1 thread b")
|
||||
},
|
||||
);
|
||||
|
||||
let claim_outcomes = vec![claim_a, claim_b];
|
||||
let claimed_count = claim_outcomes
|
||||
.iter()
|
||||
.filter(|outcome| matches!(outcome, Stage1JobClaimOutcome::Claimed { .. }))
|
||||
.count();
|
||||
assert_eq!(claimed_count, 1);
|
||||
assert!(
|
||||
claim_outcomes
|
||||
.iter()
|
||||
.any(|outcome| { matches!(outcome, Stage1JobClaimOutcome::SkippedRunning) }),
|
||||
"one concurrent claim should be throttled by running cap: {claim_outcomes:?}"
|
||||
);
|
||||
|
||||
let _ = tokio::fs::remove_dir_all(codex_home).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn claim_stage1_jobs_filters_by_age_idle_and_current_thread() {
|
||||
let codex_home = unique_temp_dir();
|
||||
@@ -1717,6 +1845,62 @@ WHERE kind = 'memory_stage1'
|
||||
let _ = tokio::fs::remove_dir_all(codex_home).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn phase2_failure_fallback_updates_unowned_running_job() {
|
||||
let codex_home = unique_temp_dir();
|
||||
let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None)
|
||||
.await
|
||||
.expect("initialize runtime");
|
||||
|
||||
runtime
|
||||
.enqueue_global_consolidation(400)
|
||||
.await
|
||||
.expect("enqueue global consolidation");
|
||||
|
||||
let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner");
|
||||
let claim = runtime
|
||||
.try_claim_global_phase2_job(owner, 3_600)
|
||||
.await
|
||||
.expect("claim global consolidation");
|
||||
let ownership_token = match claim {
|
||||
Phase2JobClaimOutcome::Claimed {
|
||||
ownership_token, ..
|
||||
} => ownership_token,
|
||||
other => panic!("unexpected claim outcome: {other:?}"),
|
||||
};
|
||||
|
||||
sqlx::query("UPDATE jobs SET ownership_token = NULL WHERE kind = ? AND job_key = ?")
|
||||
.bind("memory_consolidate_global")
|
||||
.bind("global")
|
||||
.execute(runtime.pool.as_ref())
|
||||
.await
|
||||
.expect("clear ownership token");
|
||||
|
||||
assert_eq!(
|
||||
runtime
|
||||
.mark_global_phase2_job_failed(ownership_token.as_str(), "lost", 3_600)
|
||||
.await
|
||||
.expect("mark phase2 failed with strict ownership"),
|
||||
false,
|
||||
"strict failure update should not match unowned running job"
|
||||
);
|
||||
assert!(
|
||||
runtime
|
||||
.mark_global_phase2_job_failed_if_unowned(ownership_token.as_str(), "lost", 3_600)
|
||||
.await
|
||||
.expect("fallback failure update should match unowned running job"),
|
||||
"fallback failure update should transition the unowned running job"
|
||||
);
|
||||
|
||||
let claim = runtime
|
||||
.try_claim_global_phase2_job(ThreadId::new(), 3_600)
|
||||
.await
|
||||
.expect("claim after fallback failure");
|
||||
assert_eq!(claim, Phase2JobClaimOutcome::SkippedNotDirty);
|
||||
|
||||
let _ = tokio::fs::remove_dir_all(codex_home).await;
|
||||
}
|
||||
|
||||
fn test_thread_metadata(
|
||||
codex_home: &Path,
|
||||
thread_id: ThreadId,
|
||||
|
||||
Reference in new issue
Block a user