-
Notifications
You must be signed in to change notification settings - Fork 58
fix: recover from unclean shutdown using PID-based lockfile #1031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<D: Domain, C: CancelToken> dolos_core::Driver<D, C> 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()))?; | ||
|
Comment on lines
116
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Make PID lock ownership atomic. Two instances starting together can both pass Line 116 before either binds, then Line 137 can be overwritten by the losing process. A later restart can see a live socket paired with a dead PID and remove the active socket. Acquire the PID lock atomically, check it even when the socket is absent, and only remove the lockfile on shutdown if it still contains this process’s PID. Also applies to: 158-159 🤖 Prompt for AI Agents |
||
|
|
||
| let mut tasks = TaskTracker::new(); | ||
|
|
||
|
|
@@ -119,6 +161,8 @@ impl<D: Domain, C: CancelToken> dolos_core::Driver<D, C> 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. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Fail closed on unreadable or malformed PID files.
Line 122 treats all read errors as stale, and Line 119 turns parse failures into PID
0; both paths can remove a socket without proving the owner is dead. OnlyNotFoundshould follow the stale/no-lockfile path; other read or parse failures should return a clear error.Proposed hardening
📝 Committable suggestion
🤖 Prompt for AI Agents