Download codex-rs/app-server-daemon/src/backend/pid_start.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 13.6 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/app-server-daemon/src/backend/pid_start.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/app-server-daemon/src/backend/pid_start.rs
-
curl -L -o pid_start.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/app-server-daemon/src/backend/pid_start.rs
13.6 kB
| //! Detached process launch and PID publication. Hold the reservation lock until | |
| //! the record is published, and on Windows until an updater acknowledges startup. | |
| use super::PidBackend; | |
| use super::PidCommandKind; | |
| use super::PidFileState; | |
| use super::PidRecord; | |
| use super::read_process_start_time; | |
| use crate::managed_install::executable_identity; | |
| use anyhow::Context; | |
| use anyhow::Result; | |
| use anyhow::bail; | |
| use std::process::Stdio; | |
| use tokio::fs; | |
| use tokio::process::Command; | |
| impl PidBackend { | |
| pub(super) async fn start_inner(&self, replacement: Option<PidRecord>) -> Result<Option<u32>> { | |
| crate::backend::windows::ensure_not_elevated()?; | |
| if let Some(parent) = self.pid_file.parent() { | |
| codex_uds::prepare_private_socket_directory(parent) | |
| .await | |
| .with_context(|| format!("failed to create pid directory {}", parent.display()))?; | |
| } | |
| let reservation_lock = self.acquire_reservation_lock().await?; | |
| loop { | |
| match fs::OpenOptions::new() | |
| .create_new(true) | |
| .write(true) | |
| .open(&self.pid_file) | |
| .await | |
| { | |
| Ok(pid_file) => { | |
| drop(pid_file); | |
| break; | |
| } | |
| Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { | |
| match self.read_pid_file_state_with_lock_held().await? { | |
| PidFileState::Missing => continue, | |
| PidFileState::Running(record) => { | |
| if replacement.as_ref() == Some(&record) { | |
| break; | |
| } | |
| if replacement.is_some() { | |
| if self.record_is_active(&record).await? { | |
| bail!("updater ownership changed before handoff"); | |
| } | |
| // A failed rollback may leave the dead successor's record. | |
| break; | |
| } | |
| if self.record_is_active(&record).await? { | |
| return Ok(None); | |
| } | |
| let _ = fs::remove_file(&self.pid_file).await; | |
| continue; | |
| } | |
| PidFileState::Starting => { | |
| unreachable!("lock holder cannot observe starting") | |
| } | |
| } | |
| } | |
| Err(err) => { | |
| return Err(err).with_context(|| { | |
| format!("failed to reserve pid file {}", self.pid_file.display()) | |
| }); | |
| } | |
| } | |
| } | |
| // Pin the selected release across installer symlink/junction retargeting. | |
| let codex_bin = fs::canonicalize(&self.codex_bin) | |
| .await | |
| .unwrap_or_else(|_| self.codex_bin.clone()); | |
| let launched_identity = | |
| if matches!(self.command_kind, super::PidCommandKind::AppServer { .. }) { | |
| executable_identity(&codex_bin).await.ok() | |
| } else { | |
| None | |
| }; | |
| let mut command = Command::new(&codex_bin); | |
| let stderr_log = match self.open_stderr_log().await { | |
| Ok(stderr_log) => stderr_log, | |
| Err(err) => { | |
| if replacement.is_none() { | |
| let _ = fs::remove_file(&self.pid_file).await; | |
| } | |
| return Err(err); | |
| } | |
| }; | |
| command | |
| .args(self.command_args()) | |
| .stdin(Stdio::null()) | |
| .stdout(Stdio::null()) | |
| .stderr(Stdio::from(stderr_log.into_std().await)); | |
| // Older or pinned managed binaries may predate this optional startup flag. | |
| let managed_app_server = | |
| matches!(self.command_kind, super::PidCommandKind::AppServer { .. }); | |
| if managed_app_server | |
| && matches!( | |
| tokio::time::timeout( | |
| std::time::Duration::from_secs(5), | |
| Command::new(&codex_bin) | |
| .args(["app-server", "--managed-daemon", "--help"]) | |
| .stdin(Stdio::null()) | |
| .stdout(Stdio::null()) | |
| .stderr(Stdio::null()) | |
| .kill_on_drop(true) | |
| .status(), | |
| ).await, | |
| Ok(Ok(status)) if status.success() | |
| ) | |
| { | |
| command.arg("--managed-daemon"); | |
| } else if managed_app_server { | |
| let codex_home = self | |
| .pid_file | |
| .parent() | |
| .and_then(std::path::Path::parent) | |
| .context("daemon pid path has no Codex home")?; | |
| let recovery_file = codex_app_server_transport::daemon_recovery_file_path(codex_home); | |
| match fs::remove_file(&recovery_file).await { | |
| Ok(()) => {} | |
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} | |
| Err(err) => { | |
| tracing::warn!(path = %recovery_file.display(), %err, "failed to clear daemon recovery state before legacy launch"); | |
| } | |
| } | |
| } | |
| if let Some((key, value)) = self.command_env() { | |
| command.env(key, value); | |
| } | |
| { | |
| unsafe { | |
| command.pre_exec(|| { | |
| if libc::setsid() == -1 { | |
| return Err(std::io::Error::last_os_error()); | |
| } | |
| Ok(()) | |
| }); | |
| } | |
| } | |
| { | |
| use windows_sys::Win32::System::Threading::CREATE_BREAKAWAY_FROM_JOB; | |
| use windows_sys::Win32::System::Threading::DETACHED_PROCESS; | |
| // Preserve process-scoped paths before changing cwd; CA names match CUSTOM_CA_ENV_KEYS. | |
| for name in [ | |
| "CODEX_HOME", | |
| "CODEX_SQLITE_HOME", | |
| "CODEX_CA_CERTIFICATE", | |
| "SSL_CERT_FILE", | |
| "REQUESTS_CA_BUNDLE", | |
| "CURL_CA_BUNDLE", | |
| "NODE_EXTRA_CA_CERTS", | |
| "GIT_SSL_CAINFO", | |
| "CARGO_HTTP_CAINFO", | |
| "PIP_CERT", | |
| "BUNDLE_SSL_CA_CERT", | |
| "npm_config_cafile", | |
| "AWS_CONFIG_FILE", | |
| "AWS_SHARED_CREDENTIALS_FILE", | |
| "AWS_WEB_IDENTITY_TOKEN_FILE", | |
| "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", | |
| ] { | |
| let Some(mut value) = std::env::var_os(name) else { | |
| continue; | |
| }; | |
| if matches!(name, "CODEX_SQLITE_HOME" | "npm_config_cafile") { | |
| value = value.to_str().unwrap_or_default().trim().into(); | |
| } | |
| // These consumers expand `~` independently of the working directory. | |
| let expands_home = if name == "npm_config_cafile" { | |
| value | |
| .to_str() | |
| .is_some_and(|path| path.starts_with("~/") || path.starts_with("~\\")) | |
| } else { | |
| matches!( | |
| name, | |
| "CODEX_SQLITE_HOME" | "AWS_CONFIG_FILE" | "AWS_SHARED_CREDENTIALS_FILE" | |
| ) && std::path::Path::new(&value).starts_with("~") | |
| }; | |
| if value.is_empty() || expands_home { | |
| continue; | |
| } | |
| command.env(name, std::path::absolute(value)?); | |
| } | |
| if let Some(value) = std::env::var_os("SSL_CERT_DIR") { | |
| let paths = std::env::split_paths(&value) | |
| .filter(|path| !path.as_os_str().is_empty()) | |
| .map(std::path::absolute) | |
| .collect::<std::io::Result<Vec<_>>>()?; | |
| command.env("SSL_CERT_DIR", std::env::join_paths(paths)?); | |
| } | |
| // A Windows process pins its working directory for its lifetime. | |
| // Keep both managed children out of the launching project's directory. | |
| command.current_dir( | |
| self.pid_file | |
| .parent() | |
| .context("daemon pid path has no parent")?, | |
| ); | |
| // Never retry inside the parent's Job Object: that would report a | |
| // successful launch that dies when the terminal/SSH session closes. | |
| command.creation_flags(DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB); | |
| if matches!(self.command_kind, PidCommandKind::UpdateLoop { .. }) { | |
| match fs::remove_file(self.pid_file.with_extension("ready")).await { | |
| Ok(()) => {} | |
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} | |
| Err(err) => return Err(err).context("failed to clear updater readiness"), | |
| } | |
| } | |
| match self.command_kind { | |
| PidCommandKind::AppServer { .. } => { | |
| command.env(codex_app_server_transport::DAEMON_SHUTDOWN_SOCKET_ENV, "1"); | |
| } | |
| PidCommandKind::UpdateLoop { .. } => { | |
| let shutdown_file = self.pid_file.with_extension("shutdown"); | |
| match fs::remove_file(&shutdown_file).await { | |
| Ok(()) => {} | |
| Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} | |
| Err(err) => { | |
| return Err(err).context("failed to clear updater shutdown request"); | |
| } | |
| } | |
| command.env( | |
| codex_app_server_transport::DAEMON_SHUTDOWN_FILE_ENV, | |
| shutdown_file, | |
| ); | |
| } | |
| } | |
| } | |
| let child = match command.spawn() { | |
| Ok(child) => child, | |
| Err(err) => { | |
| if replacement.is_none() { | |
| let _ = fs::remove_file(&self.pid_file).await; | |
| } | |
| return Err(err).with_context(|| { | |
| let job_hint = if cfg!(windows) { | |
| " (the Windows host job must allow breakaway)" | |
| } else { | |
| "" | |
| }; | |
| format!( | |
| "failed to spawn detached app-server process using {}{job_hint}", | |
| self.codex_bin.display() | |
| ) | |
| }); | |
| } | |
| }; | |
| let pid = child | |
| .id() | |
| .context("spawned app-server process has no pid")?; | |
| let record = match async { | |
| super::super::windows::Process::open(pid)? | |
| .context("daemon exited during launch")? | |
| .ensure_detached()?; | |
| let process_start_time = read_process_start_time(pid).await?; | |
| let process_identity = super::identity::read_process_details(pid) | |
| .await | |
| .ok() | |
| .map(|(_, identity)| identity); | |
| let process_identity = None; | |
| anyhow::Ok(PidRecord { | |
| pid, | |
| process_start_time, | |
| process_identity, | |
| executable_identity: launched_identity, | |
| }) | |
| } | |
| .await | |
| { | |
| Ok(record) => record, | |
| Err(err) => { | |
| let _ = self.terminate_process(pid); | |
| let mut context = | |
| format!("failed to record pid-managed app-server process {pid} startup"); | |
| super::super::append_stderr_log_tail_context(&self.pid_file, &mut context).await; | |
| if replacement.is_none() { | |
| let _ = fs::remove_file(&self.pid_file).await; | |
| } | |
| return Err(err).context(context); | |
| } | |
| }; | |
| let contents = serde_json::to_vec(&record).context("failed to serialize pid record")?; | |
| let temp_pid_file = self.pid_file.with_extension("pid.tmp"); | |
| if let Err(err) = fs::write(&temp_pid_file, &contents).await { | |
| let _ = self.terminate_process(pid); | |
| if replacement.is_none() { | |
| let _ = fs::remove_file(&self.pid_file).await; | |
| } | |
| return Err(err).with_context(|| { | |
| format!("failed to write pid temp file {}", temp_pid_file.display()) | |
| }); | |
| } | |
| if let Err(err) = fs::rename(&temp_pid_file, &self.pid_file).await { | |
| let _ = self.terminate_process(pid); | |
| let _ = fs::remove_file(&temp_pid_file).await; | |
| if replacement.is_none() { | |
| let _ = fs::remove_file(&self.pid_file).await; | |
| } | |
| return Err(err).with_context(|| { | |
| format!("failed to publish pid file {}", self.pid_file.display()) | |
| }); | |
| } | |
| if matches!(self.command_kind, PidCommandKind::UpdateLoop { .. }) { | |
| self.finish_updater_start(&record, replacement.as_ref()) | |
| .await?; | |
| } | |
| drop(reservation_lock); | |
| Ok(Some(pid)) | |
| } | |
| } | |