mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
Recover daemon startup and updater re-exec after cwd deletion (#49819)
## Why Managed daemons and updaters can outlive their launching directory. Restarts and updater replacement need to keep working after that directory is removed. ## What changed - Share working-directory preparation between background launches and Unix updater re-execution. Fall back to the daemon state directory when the Unix working directory is missing, while retaining usable working directories and the existing Windows state-directory behavior. - Preserve configured `CODEX_HOME` paths and valid symlink aliases, and make certificate and AWS environment paths absolute before launching children. Handle both `npm_config_cafile` and `NPM_CONFIG_CAFILE`, preserving supported home-relative values. - Log warnings when scheduled daemon updates fail. ## Testing Add Unix regression coverage for restarting the daemon and updater from a deleted working directory, and for updater re-execution with preserved environment paths, `CODEX_HOME` symlink aliases, and a Linux non-Unicode home path. GitOrigin-RevId: f45aefd8ff060adc6e025badf0bb8c81f842bf88
This commit is contained in:
@@ -114,6 +114,63 @@ impl Drop for TestDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_daemon_restarts_from_deleted_working_directory() -> Result<()> {
|
||||
let daemon = TestDaemon::new()?;
|
||||
let project = daemon.home.path().join("project");
|
||||
std::fs::create_dir(&project)?;
|
||||
let started = daemon
|
||||
.command()
|
||||
.current_dir(&project)
|
||||
.args(["app-server", "daemon", "start"])
|
||||
.output()?;
|
||||
ensure!(
|
||||
started.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&started.stderr)
|
||||
);
|
||||
let old_pid = daemon.pid("app-server.pid")?;
|
||||
let old_updater_pid = daemon.pid("app-server-updater.pid")?;
|
||||
signal(old_updater_pid, libc::SIGTERM)?;
|
||||
wait_for_exit(old_updater_pid)?;
|
||||
|
||||
// Delete the restart caller's cwd without changing the test process's cwd.
|
||||
let restarted = Command::new("/bin/sh")
|
||||
.args([
|
||||
"-c",
|
||||
"rmdir \"$1\" && exec \"$2\" app-server daemon restart",
|
||||
"restart",
|
||||
])
|
||||
.arg(&project)
|
||||
.arg(&daemon.codex)
|
||||
.current_dir(&project)
|
||||
.env("CODEX_HOME", daemon.home.path())
|
||||
.output()?;
|
||||
ensure!(
|
||||
restarted.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&restarted.stderr)
|
||||
);
|
||||
let restarted: Value = serde_json::from_slice(&restarted.stdout)?;
|
||||
assert_eq!(restarted["status"], "restarted");
|
||||
assert_ne!(daemon.pid("app-server.pid")?, old_pid);
|
||||
assert_ne!(daemon.pid("app-server-updater.pid")?, old_updater_pid);
|
||||
assert_eq!(daemon.lifecycle("version")?["status"], "running");
|
||||
let updater_socket = daemon
|
||||
.home
|
||||
.path()
|
||||
.join("app-server-daemon/app-server-updater.sock");
|
||||
let deadline = Instant::now() + Duration::from_secs(30);
|
||||
while std::os::unix::net::UnixStream::connect(&updater_socket).is_err() {
|
||||
ensure!(
|
||||
Instant::now() < deadline,
|
||||
"replacement updater did not become ready"
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn signal(pid: u32, signal: libc::c_int) -> Result<()> {
|
||||
let raw_pid = libc::pid_t::try_from(pid).context("pid out of range")?;
|
||||
ensure!(
|
||||
|
||||
Reference in New Issue
Block a user