-
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 2 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,31 @@ 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+117
to
+123
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 | ⚡ 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 Proposed hardening- 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
- };
+ let stale = match std::fs::read_to_string(&lock_path) {
+ Ok(pid_str) => {
+ let pid: u32 = pid_str.trim().parse().map_err(|e| {
+ ServeError::Internal(
+ format!("invalid PID lockfile {}: {e}", lock_path.display()).into(),
+ )
+ })?;
+
+ if pid == 0 {
+ return Err(ServeError::Internal(
+ format!("invalid PID lockfile {}: PID cannot be 0", lock_path.display())
+ .into(),
+ ));
+ }
+
+ !is_process_running(pid)
+ }
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => true,
+ Err(error) => return Err(ServeError::Internal(error.into())),
+ };📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 +155,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. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.