diff --git a/Cargo.toml b/Cargo.toml index bda99804f..6a0255d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ base64.workspace = true bech32.workspace = true bincode.workspace = true chrono.workspace = true +libc = "0.2" futures-core.workspace = true futures-util.workspace = true hex.workspace = true diff --git a/src/serve/o7s_unix/mod.rs b/src/serve/o7s_unix/mod.rs old mode 100644 new mode 100755 index 39232f4fe..21c1c7a2b --- a/src/serve/o7s_unix/mod.rs +++ b/src/serve/o7s_unix/mod.rs @@ -6,6 +6,22 @@ use tracing::{debug, info, instrument, warn}; use crate::prelude::*; +/// Check if a process with the given PID is still running +fn is_process_running(pid: u32) -> bool { + // Send signal 0 to check if a process exists without affecting it. + // Returns true if the process is running, false if it doesn't exist + // or we lack permission (which means something else owns the pid). + #[cfg(unix)] + { + unsafe { libc::kill(pid as libc::pid_t, 0) == 0 } + } + #[cfg(not(unix))] + { + let _ = pid; + true + } +} + mod chainsync; mod statequery; mod utils; @@ -95,11 +111,37 @@ impl dolos_core::Driver for Driver { #[instrument(skip_all)] async fn run(cfg: Self::Config, domain: D, cancel: C) -> Result<(), ServeError> { // preventive removal of socket file in case of unclean shutdown + // check if a stale PID lockfile exists and the process is dead before removing + let lock_path = cfg.service.listen_path.with_extension("pid"); if std::fs::metadata(&cfg.service.listen_path).is_ok() { - debug!("preventive removal of socket file"); - std::fs::remove_file(&cfg.service.listen_path) - .map_err(|e| ServeError::Internal(e.into()))?; + let stale = match std::fs::read_to_string(&lock_path) { + Ok(pid_str) => { + let pid: u32 = pid_str.trim().parse().unwrap_or(0); + pid == 0 || !is_process_running(pid) + } + Err(_) => true, // no lockfile = stale, safe to remove + }; + if stale { + debug!("preventive removal of stale socket file"); + let _ = std::fs::remove_file(&lock_path); + std::fs::remove_file(&cfg.service.listen_path) + .map_err(|e| ServeError::Internal(e.into()))?; + } else { + return Err(ServeError::Internal( + format!( + "socket {} is in use by PID {}", + cfg.service.listen_path.display(), + std::fs::read_to_string(&lock_path) + .unwrap_or_default() + .trim() + ) + .into(), + )); + } } + // write our PID to the lockfile + std::fs::write(&lock_path, std::process::id().to_string()) + .map_err(|e| ServeError::Internal(e.into()))?; let mut tasks = TaskTracker::new(); @@ -119,6 +161,8 @@ impl dolos_core::Driver for Driver { return Err(ServeError::Internal(error.into())); } } + // clean up PID lockfile + let _ = std::fs::remove_file(cfg.service.listen_path.with_extension("pid")); // notify the tracker that we're done receiving new tasks. Without this explicit // close, the wait will block forever.