mirror of
https://github.com/openai/codex.git
synced 2026-11-10 08:43:33 +00:00
Make the app-server thread unload delay configurable (#42320)
## What changed - Add the top-level `thread_unload_delay_secs` configuration key for the app-server, with a 60-second default instead of the previous 30-minute fixed delay. - Allow zero-delay unloading and reject values that cannot fit in a monotonic-clock deadline. - Reset the unload countdown when a thread gains a subscriber or becomes active, and preserve active turns even when the configured delay is zero. - Document that unloading ephemeral threads discards their in-memory state. ## Testing - Cover configuration parsing, defaults, overrides, and overflow rejection. - Cover unsubscribe, resubscribe, delayed and immediate unloading, notifications, persisted-thread resume, and WebSocket disconnect behavior. GitOrigin-RevId: edd46f6b49bbdafbf606bff74378e8f569e95977
This commit is contained in:
1 parent
a526f54b00
commit
5e26f7621c
11 files changed
+238
-13
No files matched your search
@@ -247,6 +247,43 @@ async fn load_config_applies_optional_mcp_startup_grace() -> std::io::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_resolves_thread_unload_delay() -> anyhow::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
for (toml, seconds) in [
|
||||
("", 60),
|
||||
("thread_unload_delay_secs = 0", 0),
|
||||
("thread_unload_delay_secs = 1800", 1800),
|
||||
] {
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
toml::from_str(toml)?,
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(config.thread_unload_delay, Duration::from_secs(seconds));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_config_rejects_thread_unload_delay_overflow() -> anyhow::Result<()> {
|
||||
let codex_home = tempdir()?;
|
||||
let error = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
thread_unload_delay_secs: Some(u64::MAX),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides::default(),
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await
|
||||
.expect_err("idle timeout must fit in a monotonic deadline");
|
||||
assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(error.to_string(), "thread_unload_delay_secs is too large");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_toml_parsing() {
|
||||
let history_with_persistence = r#"
|
||||
|
||||
@@ -1037,6 +1037,9 @@ pub struct Config {
|
||||
/// Default: `300000` (5 minutes).
|
||||
pub background_terminal_max_timeout: u64,
|
||||
|
||||
/// Idle timeout for unsubscribed app-server threads, resolved at server startup.
|
||||
pub thread_unload_delay: Duration,
|
||||
|
||||
/// Compatibility-only settings retained for legacy `ghost_snapshot`
|
||||
/// config loading.
|
||||
pub ghost_snapshot: GhostSnapshotConfig,
|
||||
@@ -3813,6 +3816,17 @@ impl Config {
|
||||
.background_terminal_max_timeout
|
||||
.unwrap_or(DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS)
|
||||
.max(MIN_EMPTY_YIELD_TIME_MS);
|
||||
let thread_unload_delay =
|
||||
Duration::from_secs(cfg.thread_unload_delay_secs.unwrap_or(/*default*/ 60));
|
||||
if std::time::Instant::now()
|
||||
.checked_add(thread_unload_delay)
|
||||
.is_none()
|
||||
{
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"thread_unload_delay_secs is too large",
|
||||
));
|
||||
}
|
||||
|
||||
let ghost_snapshot = {
|
||||
let mut config = GhostSnapshotConfig::default();
|
||||
@@ -4298,6 +4312,7 @@ impl Config {
|
||||
tool_registry,
|
||||
code_mode,
|
||||
background_terminal_max_timeout,
|
||||
thread_unload_delay,
|
||||
ghost_snapshot,
|
||||
multi_agent_v2,
|
||||
token_budget,
|
||||
|
||||
Reference in new issue
Block a user