-
Notifications
You must be signed in to change notification settings - Fork 483
Expand file tree
/
Copy pathlock.rs
More file actions
171 lines (148 loc) · 5.88 KB
/
lock.rs
File metadata and controls
171 lines (148 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::process;
use sysinfo::{PidExt, ProcessExt, System, SystemExt};
/* This locking mechanism is meant to never be deleted. Instead, it stores the PID of the process
* that's running, when trying to aquire a lock, it checks wether that process is still running. If
* not, it rewrites the lockfile to have its own PID instead. */
pub static LOCKFILE: &str = "rescript.lock";
pub enum Error {
Locked(u32),
ParsingLockfile(std::num::ParseIntError),
ReadingLockfile(std::io::Error),
WritingLockfile(std::io::Error),
ProjectFolderMissing(std::path::PathBuf),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let msg = match self {
Error::Locked(pid) => {
format!("A ReScript build is already running. The process ID (PID) is {pid}")
}
Error::ParsingLockfile(e) => format!(
"Could not parse lockfile: \n {e} \n (try removing it and running the command again)"
),
Error::ReadingLockfile(e) => {
format!("Could not read lockfile: \n {e} \n (try removing it and running the command again)")
}
Error::WritingLockfile(e) => format!("Could not write lockfile: \n {e}"),
Error::ProjectFolderMissing(path) => format!(
"Could not write lockfile because the specified project folder does not exist: {}",
path.to_string_lossy()
),
};
write!(f, "{msg}")
}
}
pub enum Lock {
Aquired(u32),
Error(Error),
}
fn matching_process_name() -> Option<String> {
std::env::current_exe()
.ok()
.and_then(|path| path.file_name().map(|name| name.to_string_lossy().into_owned()))
}
fn pid_matches_current_process(to_check_pid: u32) -> bool {
let system = System::new_all();
let current_process_name = matching_process_name();
system.processes().iter().any(|(pid, process)| {
if pid.as_u32() != to_check_pid {
return false;
}
match ¤t_process_name {
Some(current_process_name) => process
.exe()
.file_name()
.map(|name| name.to_string_lossy() == current_process_name.as_str())
.unwrap_or_else(|| process.name() == current_process_name.as_str()),
None => true,
}
})
}
pub fn get(folder: &str) -> Lock {
let project_folder = Path::new(folder);
if !project_folder.exists() {
return Lock::Error(Error::ProjectFolderMissing(project_folder.to_path_buf()));
}
let lib_dir = project_folder.join("lib");
let location = lib_dir.join(LOCKFILE);
let pid = process::id();
// When a lockfile already exists we parse its PID: if the process is still alive we refuse to
// proceed, otherwise we will overwrite the stale lock with our own PID.
match fs::read_to_string(&location) {
Ok(contents) => match contents.parse::<u32>() {
Ok(parsed_pid) if pid_matches_current_process(parsed_pid) => {
return Lock::Error(Error::Locked(parsed_pid));
}
Ok(_) => (),
Err(e) => return Lock::Error(Error::ParsingLockfile(e)),
},
Err(e) if e.kind() == std::io::ErrorKind::NotFound => (),
Err(e) => return Lock::Error(Error::ReadingLockfile(e)),
}
if let Err(e) = fs::create_dir_all(&lib_dir) {
return Lock::Error(Error::WritingLockfile(e));
}
// Rewrite the lockfile with our own PID.
match File::create(&location) {
Ok(mut file) => match file.write(pid.to_string().as_bytes()) {
Ok(_) => Lock::Aquired(pid),
Err(e) => Lock::Error(Error::WritingLockfile(e)),
},
Err(e) => Lock::Error(Error::WritingLockfile(e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn returns_error_when_project_folder_missing() {
let temp_dir = TempDir::new().expect("temp dir should be created");
let missing_folder = temp_dir.path().join("missing_project");
match get(missing_folder.to_str().expect("path should be valid")) {
Lock::Error(Error::ProjectFolderMissing(path)) => {
assert_eq!(path, missing_folder);
}
_ => panic!("expected ProjectFolderMissing error"),
}
assert!(
!missing_folder.exists(),
"missing project folder should not be created"
);
}
#[test]
fn creates_lock_when_project_folder_exists() {
let temp_dir = TempDir::new().expect("temp dir should be created");
let project_folder = temp_dir.path().join("project");
fs::create_dir(&project_folder).expect("project folder should be created");
match get(project_folder.to_str().expect("path should be valid")) {
Lock::Aquired(_) => {}
_ => panic!("expected lock to be acquired"),
}
assert!(
project_folder.join("lib").exists(),
"lib directory should be created"
);
assert!(
project_folder.join("lib").join(LOCKFILE).exists(),
"lockfile should be created"
);
}
#[test]
fn ignores_stale_lock_for_unrelated_process_name() {
let temp_dir = TempDir::new().expect("temp dir should be created");
let project_folder = temp_dir.path().join("project");
let lib_dir = project_folder.join("lib");
fs::create_dir_all(&lib_dir).expect("lib directory should be created");
fs::write(lib_dir.join(LOCKFILE), "1").expect("lockfile should be written");
match get(project_folder.to_str().expect("path should be valid")) {
Lock::Aquired(_) => {}
_ => panic!("expected stale lock from unrelated process to be ignored"),
}
}
}