mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Make thread store process-scoped (#19474)
- Build one app-server process ThreadStore from startup config and share it with ThreadManager and CodexMessageProcessor. - Remove per-thread/fork store reconstruction so effective thread config cannot switch the persistence backend. - Add params to ThreadStore create/resume for specifying thread metadata, since otherwise the metadata from store creation would be used (incorrectly).
This commit is contained in:
@@ -161,8 +161,7 @@ fn fork_thread_accepts_legacy_usize_snapshot_argument() {
|
||||
) {
|
||||
let _future = manager.fork_thread(
|
||||
usize::MAX,
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
config,
|
||||
path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
@@ -263,12 +262,12 @@ async fn shutdown_all_threads_bounded_submits_shutdown_to_every_thread() {
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
);
|
||||
let thread_1 = manager
|
||||
.start_thread(config.clone(), thread_store_from_config(&config))
|
||||
.start_thread(config.clone())
|
||||
.await
|
||||
.expect("start first thread")
|
||||
.thread_id;
|
||||
let thread_2 = manager
|
||||
.start_thread(config.clone(), thread_store_from_config(&config))
|
||||
.start_thread(config.clone())
|
||||
.await
|
||||
.expect("start second thread")
|
||||
.thread_id;
|
||||
@@ -314,7 +313,6 @@ async fn start_thread_accepts_explicit_environment_when_default_environment_is_d
|
||||
|
||||
let thread = manager
|
||||
.start_thread_with_options(StartThreadOptions {
|
||||
thread_store: thread_store_from_config(&config),
|
||||
config: config.clone(),
|
||||
initial_history: InitialHistory::New,
|
||||
session_source: None,
|
||||
@@ -347,10 +345,8 @@ async fn start_thread_keeps_internal_threads_hidden_from_normal_lookups() {
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
);
|
||||
let thread_store = thread_store_from_config(&config);
|
||||
let thread = manager
|
||||
.start_thread_with_options(StartThreadOptions {
|
||||
thread_store,
|
||||
config,
|
||||
initial_history: InitialHistory::New,
|
||||
session_source: Some(SessionSource::Internal(
|
||||
@@ -393,6 +389,7 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
let selected_cwd =
|
||||
AbsolutePathBuf::try_from(config.cwd.as_path().join("selected")).expect("absolute path");
|
||||
@@ -401,11 +398,8 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
cwd: selected_cwd.clone(),
|
||||
}];
|
||||
let default_cwd = config.cwd.clone();
|
||||
let thread_store = thread_store_from_config(&config);
|
||||
|
||||
let source = manager
|
||||
.start_thread_with_options(StartThreadOptions {
|
||||
thread_store: Arc::clone(&thread_store),
|
||||
config: config.clone(),
|
||||
initial_history: InitialHistory::New,
|
||||
session_source: None,
|
||||
@@ -437,7 +431,6 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
let resumed = manager
|
||||
.resume_thread_from_rollout(
|
||||
config.clone(),
|
||||
Arc::clone(&thread_store),
|
||||
rollout_path.clone(),
|
||||
auth_manager,
|
||||
/*parent_trace*/ None,
|
||||
@@ -459,7 +452,6 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
.fork_thread(
|
||||
ForkSnapshot::Interrupted,
|
||||
config,
|
||||
thread_store,
|
||||
rollout_path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
@@ -478,6 +470,117 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
assert_ne!(forked_turn.environments[0].cwd, selected_cwd);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_active_thread_from_rollout_returns_running_thread() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home").abs();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let source = manager
|
||||
.start_thread(config.clone())
|
||||
.await
|
||||
.expect("start source thread");
|
||||
source.thread.ensure_rollout_materialized().await;
|
||||
source
|
||||
.thread
|
||||
.flush_rollout()
|
||||
.await
|
||||
.expect("flush source rollout");
|
||||
let rollout_path = source
|
||||
.thread
|
||||
.rollout_path()
|
||||
.expect("source rollout path should exist");
|
||||
|
||||
let resumed = manager
|
||||
.resume_thread_from_rollout(
|
||||
config,
|
||||
rollout_path,
|
||||
auth_manager,
|
||||
/*parent_trace*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("resume active source thread");
|
||||
assert_eq!(resumed.thread_id, source.thread_id);
|
||||
assert!(Arc::ptr_eq(&resumed.thread, &source.thread));
|
||||
|
||||
source
|
||||
.thread
|
||||
.shutdown_and_wait()
|
||||
.await
|
||||
.expect("shutdown source thread");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_stopped_thread_from_rollout_spawns_new_thread() {
|
||||
let temp_dir = tempdir().expect("tempdir");
|
||||
let mut config = test_config().await;
|
||||
config.codex_home = temp_dir.path().join("codex-home").abs();
|
||||
config.cwd = config.codex_home.abs();
|
||||
std::fs::create_dir_all(&config.codex_home).expect("create codex home");
|
||||
|
||||
let auth_manager =
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing());
|
||||
let manager = ThreadManager::new(
|
||||
&config,
|
||||
auth_manager.clone(),
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let source = manager
|
||||
.start_thread(config.clone())
|
||||
.await
|
||||
.expect("start source thread");
|
||||
source.thread.ensure_rollout_materialized().await;
|
||||
source
|
||||
.thread
|
||||
.flush_rollout()
|
||||
.await
|
||||
.expect("flush source rollout");
|
||||
let rollout_path = source
|
||||
.thread
|
||||
.rollout_path()
|
||||
.expect("source rollout path should exist");
|
||||
source
|
||||
.thread
|
||||
.shutdown_and_wait()
|
||||
.await
|
||||
.expect("shutdown source thread");
|
||||
|
||||
let resumed = manager
|
||||
.resume_thread_from_rollout(
|
||||
config,
|
||||
rollout_path,
|
||||
auth_manager,
|
||||
/*parent_trace*/ None,
|
||||
)
|
||||
.await
|
||||
.expect("resume stopped source thread");
|
||||
assert_eq!(resumed.thread_id, source.thread_id);
|
||||
assert!(!Arc::ptr_eq(&resumed.thread, &source.thread));
|
||||
|
||||
resumed
|
||||
.thread
|
||||
.shutdown_and_wait()
|
||||
.await
|
||||
.expect("shutdown resumed thread");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn new_uses_active_provider_for_model_refresh() {
|
||||
let server = MockServer::start().await;
|
||||
@@ -499,6 +602,7 @@ async fn new_uses_active_provider_for_model_refresh() {
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let _ = manager.list_models(RefreshStrategy::Online).await;
|
||||
@@ -709,12 +813,12 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let source = manager
|
||||
.resume_thread_with_history(
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
InitialHistory::Forked(vec![
|
||||
RolloutItem::ResponseItem(user_msg("hello")),
|
||||
RolloutItem::ResponseItem(assistant_msg("partial")),
|
||||
@@ -741,7 +845,6 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
|
||||
.fork_thread(
|
||||
ForkSnapshot::Interrupted,
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
source_path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
@@ -812,12 +915,12 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let source = manager
|
||||
.resume_thread_with_history(
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
InitialHistory::Forked(vec![
|
||||
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
|
||||
turn_id: "turn-explicit".to_string(),
|
||||
@@ -855,7 +958,6 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
|
||||
.fork_thread(
|
||||
ForkSnapshot::Interrupted,
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
source_path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
@@ -904,12 +1006,12 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let source = manager
|
||||
.resume_thread_with_history(
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
InitialHistory::Forked(vec![
|
||||
RolloutItem::ResponseItem(user_msg("hello")),
|
||||
RolloutItem::ResponseItem(assistant_msg("partial")),
|
||||
@@ -934,7 +1036,6 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
|
||||
.fork_thread(
|
||||
ForkSnapshot::Interrupted,
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
source_path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
@@ -975,7 +1076,6 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
|
||||
.fork_thread(
|
||||
ForkSnapshot::Interrupted,
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
forked_path,
|
||||
/*persist_extended_history*/ false,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1042,12 +1142,12 @@ async fn resumed_thread_activates_paused_goal_and_continues_on_request() -> anyh
|
||||
SessionSource::Exec,
|
||||
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store_from_config(&config),
|
||||
);
|
||||
|
||||
let source = manager
|
||||
.resume_thread_with_history(
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
InitialHistory::Forked(vec![RolloutItem::ResponseItem(user_msg("keep working"))]),
|
||||
auth_manager.clone(),
|
||||
/*persist_extended_history*/ false,
|
||||
@@ -1072,12 +1172,12 @@ async fn resumed_thread_activates_paused_goal_and_continues_on_request() -> anyh
|
||||
/*token_budget*/ None,
|
||||
)
|
||||
.await?;
|
||||
source.thread.shutdown_and_wait().await?;
|
||||
manager.remove_thread(&source.thread_id).await;
|
||||
|
||||
let resumed = manager
|
||||
.resume_thread_from_rollout(
|
||||
config.clone(),
|
||||
thread_store_from_config(&config),
|
||||
source_path,
|
||||
auth_manager,
|
||||
/*parent_trace*/ None,
|
||||
|
||||
Reference in New Issue
Block a user