-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #5633 Added functionality to create and manage a lock file containing the process ID (pid) of the running instance of the software. This mechanism prevents multiple instances of the software from running simultaneously by checking the existence and content of the lock file. If the lock file exists and contains a valid pid, the struct will error gracefully to avoid conflicts. If the lock file is missing or contains an invalid pid, the struct will proceed by removing the file. This ensures that only one instance of the software can run at a time and it avoids stale locking to prevent future instances
- Loading branch information
Showing
6 changed files
with
203 additions
and
49 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use crate::{hash_path, user_forc_directory}; | ||
use std::{ | ||
fs::{create_dir_all, remove_file, File}, | ||
io::{self, Read, Write}, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
/// Very simple AdvisoryPathMutex class | ||
/// | ||
/// The goal of this struct is to signal other processes that a path is being used by another | ||
/// process exclusively. | ||
/// | ||
/// This struct will self-healh if the process that locked the file is no longer running. | ||
pub struct PidFileLocking(PathBuf); | ||
|
||
impl PidFileLocking { | ||
pub fn new<X: AsRef<Path>, Y: AsRef<Path>>(path: X, dir: Y, extension: &str) -> PidFileLocking { | ||
let file_name = hash_path(path); | ||
Self( | ||
user_forc_directory() | ||
.join(dir) | ||
.join(file_name) | ||
.with_extension(extension), | ||
) | ||
} | ||
|
||
pub fn lsp<X: AsRef<Path>>(path: X) -> PidFileLocking { | ||
Self::new(path, ".lsp-locks", "dirty") | ||
} | ||
|
||
fn is_pid_active(pid: usize) -> bool { | ||
use sysinfo::{Pid, System}; | ||
if pid == std::process::id() as usize { | ||
return false; | ||
} | ||
System::new_all().process(Pid::from(pid)).is_some() | ||
} | ||
|
||
pub fn remove(&self) -> io::Result<()> { | ||
if self.is_locked()? { | ||
Err(io::Error::new( | ||
std::io::ErrorKind::Other, | ||
"Cannot remove a dirty lock file, it is locked by another process", | ||
)) | ||
} else { | ||
self.remove_file() | ||
} | ||
} | ||
|
||
fn remove_file(&self) -> io::Result<()> { | ||
match remove_file(&self.0) { | ||
Err(error) => { | ||
if error.kind() == io::ErrorKind::NotFound { | ||
Ok(()) | ||
} else { | ||
Err(error) | ||
} | ||
} | ||
_ => Ok(()), | ||
} | ||
} | ||
|
||
pub fn is_locked(&self) -> io::Result<bool> { | ||
let fs = File::open(&self.0); | ||
println!("{:#?}", fs); | ||
match fs { | ||
Ok(mut file) => { | ||
let mut pid = String::new(); | ||
file.read_to_string(&mut pid)?; | ||
let is_locked = pid | ||
.trim() | ||
.parse::<usize>() | ||
.map(|x| Self::is_pid_active(x)) | ||
.unwrap_or_default(); | ||
drop(file); | ||
if !is_locked { | ||
self.remove_file()?; | ||
} | ||
Ok(is_locked) | ||
} | ||
Err(err) => { | ||
if err.kind() == io::ErrorKind::NotFound { | ||
Ok(false) | ||
} else { | ||
Err(err) | ||
} | ||
} | ||
} | ||
} | ||
|
||
pub fn lock(&self) -> io::Result<()> { | ||
self.remove()?; | ||
if let Some(dir) = self.0.parent() { | ||
// Ensure the directory exists | ||
create_dir_all(dir)?; | ||
} | ||
|
||
let mut fs = File::create(&self.0)?; | ||
fs.write_all(&std::process::id().to_string().as_bytes())?; | ||
fs.sync_all()?; | ||
fs.flush()?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::PidFileLocking; | ||
use std::{ | ||
fs::{metadata, File}, | ||
io::{ErrorKind, Write}, | ||
}; | ||
|
||
#[test] | ||
fn same_process() { | ||
let x = PidFileLocking::lsp("test"); | ||
assert!(x.lock().is_ok()); | ||
// The current process is locking "test" | ||
let x = PidFileLocking::lsp("test"); | ||
assert!(!x.is_locked().unwrap()); | ||
} | ||
|
||
#[test] | ||
fn stale() { | ||
let x = PidFileLocking::lsp("stale"); | ||
assert!(x.lock().is_ok()); | ||
|
||
// lock file exists, | ||
assert!(metadata(&x.0).is_ok()); | ||
|
||
// simulate a stale lock file | ||
let mut x = File::create(&x.0).unwrap(); | ||
x.write_all(b"191919191919").unwrap(); | ||
x.flush().unwrap(); | ||
drop(x); | ||
|
||
// PID=191919191919 does not exists, hopefully, and this should remove the lock file | ||
let x = PidFileLocking::lsp("test"); | ||
assert!(!x.is_locked().unwrap()); | ||
let e = metadata(&x.0).unwrap_err().kind(); | ||
assert_eq!(e, ErrorKind::NotFound); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters