Files
codex/codex-rs/app-server-transport/src/daemon_shutdown.rs
T
Eric Traut 455318c202 Replace Windows app-server shutdown files with socket requests (#43308)
## What changed

Route managed Windows app-server shutdown through `/daemon/shutdown` on the local control socket. Require the server's PID and acknowledge it before triggering shutdown through the existing drain logic. Enable the endpoint only for managed Windows launches via `CODEX_DAEMON_SHUTDOWN_SOCKET`.

Watch running-turn changes whenever shutdown is requested so socket-triggered shutdown can finish draining. Keep the updater's file-based shutdown mechanism and retain forced termination as the manager's timeout fallback.

## Testing

Add a transport test covering rejection of unmanaged shutdown requests and mismatched PIDs, plus acknowledgment before emitting the shutdown event.

GitOrigin-RevId: 3e95dc1914f61e8a131f9b6fd56b6c63688d1744
2026-09-07 00:00:23 +00:00

54 lines
1.8 KiB
Rust

//! The detached Windows updater has no control socket, so its owner requests
//! termination through a file in its private state directory. Managed app-server
//! shutdown instead uses the protected local socket.
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 updater shutdown request.
#[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;