mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Support graceful daemon shutdown on Windows (#42364)
## Why Detached Windows processes cannot receive console signals, so app-server daemons need another way to enter the existing graceful and forced shutdown flow. ## What changed - Watch the path in `CODEX_DAEMON_SHUTDOWN_FILE` for a request addressed to the current process, and consume only a matching PID. - Race daemon shutdown requests with Ctrl-C on Windows while leaving unmanaged launches on the existing console-signal path. - Exercise the SIGTERM-equivalent graceful and forced WebSocket shutdown cases on Windows. ## Testing Added unit coverage ensuring inherited control paths and malformed requests cannot consume another process's shutdown request. GitOrigin-RevId: 426a3c12edc344802395d4d26288f2bb015c484f
This commit is contained in:
1 parent
577a4fcd06
commit
10aca93f18
7 files changed
+135
-9
No files matched your search
@@ -0,0 +1,54 @@
|
||||
//! Detached Windows processes cannot receive console signals. The daemon passes
|
||||
//! a request path inside its user-only state directory. Only the addressed PID
|
||||
//! consumes a request, triggering the SIGTERM drain without changing the wire API.
|
||||
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
#[cfg(windows)]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(windows)]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const DAEMON_SHUTDOWN_FILE_ENV: &str = "CODEX_DAEMON_SHUTDOWN_FILE";
|
||||
|
||||
/// Waits for and consumes one daemon shutdown request. Without a managed launch
|
||||
/// environment this stays pending, leaving normal console shutdown unchanged.
|
||||
#[cfg(windows)]
|
||||
pub async fn daemon_shutdown_signal() -> io::Result<()> {
|
||||
let Some(path) = std::env::var_os(DAEMON_SHUTDOWN_FILE_ENV).map(PathBuf::from) else {
|
||||
return std::future::pending().await;
|
||||
};
|
||||
loop {
|
||||
if take_shutdown_request(&path, std::process::id())? {
|
||||
return Ok(());
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn take_shutdown_request(path: &Path, pid: u32) -> io::Result<bool> {
|
||||
let file = match std::fs::File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(false),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
// Descendant app-servers may inherit the control path. Only the intended
|
||||
// process can consume a request. Bound reads to a u32 PID plus one extra byte.
|
||||
let mut contents = Vec::new();
|
||||
file.take(/*limit*/ 11).read_to_end(&mut contents)?;
|
||||
if contents != pid.to_string().as_bytes() {
|
||||
return Ok(false);
|
||||
}
|
||||
// Synchronous consumption cannot lose a request to select cancellation.
|
||||
match std::fs::remove_file(path) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "daemon_shutdown_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,22 @@
|
||||
use super::take_shutdown_request;
|
||||
|
||||
#[test]
|
||||
fn inherited_control_path_cannot_consume_another_process_request() {
|
||||
let directory = tempfile::tempdir().expect("directory");
|
||||
let request = directory.path().join("shutdown");
|
||||
std::fs::write(&request, "1234").expect("request");
|
||||
assert!(!take_shutdown_request(&request, /*pid*/ 5678).expect("descendant probe"));
|
||||
assert!(take_shutdown_request(&request, /*pid*/ 1234).expect("parent probe"));
|
||||
assert!(!take_shutdown_request(&request, /*pid*/ 1234).expect("already consumed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_or_invalid_requests_are_not_consumed() {
|
||||
let directory = tempfile::tempdir().expect("directory");
|
||||
let request = directory.path().join("shutdown");
|
||||
for contents in [b"".as_slice(), b"123", b"1234junk", &[0xff; 20]] {
|
||||
std::fs::write(&request, contents).expect("request");
|
||||
assert!(!take_shutdown_request(&request, /*pid*/ 1234).expect("probe"));
|
||||
assert!(request.exists());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
#[cfg(any(windows, test))]
|
||||
mod daemon_shutdown;
|
||||
#[cfg(windows)]
|
||||
pub use daemon_shutdown::DAEMON_SHUTDOWN_FILE_ENV;
|
||||
#[cfg(windows)]
|
||||
pub use daemon_shutdown::daemon_shutdown_signal;
|
||||
mod outgoing_message;
|
||||
mod transport;
|
||||
|
||||
|
||||
Reference in new issue
Block a user