mirror of
https://github.com/openai/codex.git
synced 2026-10-24 15:53:38 +00:00
## Why Managed app-server shutdowns use a fixed 60-second grace period. Allow users to adjust how long shutdown waits before forcing the process to exit. ## What changed - Add `shutdownGraceSeconds` to daemon settings, accepting integers from 0 through 300 and defaulting to 60. Zero requests graceful shutdown and then forces termination immediately. - Apply the setting to managed app-server stops and restarts, including updater-triggered restarts, and extend the lifecycle lock timeout to accommodate the maximum grace period. - Keep `stop` usable with unreadable or invalid settings by falling back to the default grace period. ## Testing Add tests for setting defaults and bounds, invalid-value fallback, preservation when saving remote-control settings, graceful process exit, and forced termination with zero or finite grace periods. GitOrigin-RevId: 8627b391323cf27b40df96935a82e66aa715f572
131 lines
3.9 KiB
Rust
131 lines
3.9 KiB
Rust
use pretty_assertions::assert_eq;
|
|
use tempfile::TempDir;
|
|
|
|
use super::DaemonSettings;
|
|
use super::MAX_SHUTDOWN_GRACE_SECONDS;
|
|
|
|
#[tokio::test]
|
|
async fn remote_control_save_preserves_updater_settings() {
|
|
let temp = TempDir::new().expect("temp dir");
|
|
let path = temp.path().join("settings.json");
|
|
tokio::fs::write(&path, r#"{"remoteControlEnabled":true}"#)
|
|
.await
|
|
.expect("write legacy settings");
|
|
|
|
let settings = DaemonSettings::load(&path).await.expect("load settings");
|
|
assert_eq!(
|
|
settings,
|
|
DaemonSettings {
|
|
remote_control_enabled: true,
|
|
..DaemonSettings::default()
|
|
}
|
|
);
|
|
|
|
tokio::fs::write(
|
|
&path,
|
|
r#"{"remoteControlEnabled":true,"shutdownGraceSeconds":25,"updater":{"autoUpdateEnabled":false,"updateIntervalMinutes":17},"futureSetting":42}"#,
|
|
)
|
|
.await
|
|
.expect("write settings");
|
|
let updated = DaemonSettings {
|
|
remote_control_enabled: false,
|
|
..DaemonSettings::load(&path)
|
|
.await
|
|
.expect("load updater settings")
|
|
};
|
|
updated.save(&path).await.expect("save remote control");
|
|
assert_eq!(
|
|
serde_json::from_slice::<serde_json::Value>(
|
|
&tokio::fs::read(&path).await.expect("read settings")
|
|
)
|
|
.expect("parse settings"),
|
|
serde_json::json!({
|
|
"remoteControlEnabled": false,
|
|
"shutdownGraceSeconds": 25,
|
|
"updater": {"autoUpdateEnabled": false, "updateIntervalMinutes": 17},
|
|
"futureSetting": 42,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn shutdown_grace_accepts_zero_through_five_minutes() {
|
|
let temp = TempDir::new().expect("temp dir");
|
|
let path = temp.path().join("settings.json");
|
|
assert_eq!(
|
|
DaemonSettings::load(&path).await.expect("missing settings"),
|
|
DaemonSettings::default()
|
|
);
|
|
assert_eq!(
|
|
DaemonSettings::load_for_stop(&path)
|
|
.await
|
|
.shutdown_grace_seconds,
|
|
60
|
|
);
|
|
|
|
for seconds in [
|
|
0,
|
|
MAX_SHUTDOWN_GRACE_SECONDS,
|
|
MAX_SHUTDOWN_GRACE_SECONDS + 1,
|
|
] {
|
|
tokio::fs::write(
|
|
&path,
|
|
serde_json::to_vec(&serde_json::json!({"shutdownGraceSeconds": seconds}))
|
|
.expect("serialize settings"),
|
|
)
|
|
.await
|
|
.expect("write settings");
|
|
let expected = (seconds <= MAX_SHUTDOWN_GRACE_SECONDS).then_some(seconds);
|
|
assert_eq!(
|
|
DaemonSettings::load(&path)
|
|
.await
|
|
.ok()
|
|
.map(|settings| settings.shutdown_grace_seconds),
|
|
expected
|
|
);
|
|
assert_eq!(
|
|
DaemonSettings::load_for_stop(&path)
|
|
.await
|
|
.shutdown_grace_seconds,
|
|
expected.unwrap_or(60)
|
|
);
|
|
}
|
|
|
|
tokio::fs::write(&path, r#"{"shutdownGraceSeconds":"unlimited"}"#)
|
|
.await
|
|
.expect("write invalid settings");
|
|
assert!(DaemonSettings::load(&path).await.is_err());
|
|
assert_eq!(
|
|
DaemonSettings::load_for_stop(&path)
|
|
.await
|
|
.shutdown_grace_seconds,
|
|
60
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn update_interval_accepts_long_values_and_rejects_zero() {
|
|
let temp = TempDir::new().expect("temp dir");
|
|
let path = temp.path().join("settings.json");
|
|
for minutes in [u32::MAX, 0] {
|
|
tokio::fs::write(
|
|
&path,
|
|
serde_json::to_vec(&serde_json::json!({
|
|
"updater": {"updateIntervalMinutes": minutes},
|
|
}))
|
|
.expect("serialize invalid settings"),
|
|
)
|
|
.await
|
|
.expect("write invalid settings");
|
|
let loaded = DaemonSettings::load(&path).await;
|
|
if minutes == 0 {
|
|
assert!(loaded.is_err());
|
|
} else {
|
|
assert_eq!(
|
|
loaded.expect("load long interval").update_interval_minutes,
|
|
minutes
|
|
);
|
|
}
|
|
}
|
|
}
|