1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::collections::HashMap;
23use std::fmt;
24#[cfg(unix)]
25use std::path::{Path, PathBuf};
26use std::sync::{Arc, Weak};
27
28use once_cell::sync::Lazy;
29use parking_lot::lock_api::ArcMutexGuard;
30use parking_lot::{Mutex, RawMutex};
31
32mod error;
33#[cfg(unix)]
34mod unix;
35#[cfg(windows)]
36mod windows;
37
38pub use crate::error::*;
39#[cfg(unix)]
40use crate::unix::RawNamedLock;
41#[cfg(windows)]
42use crate::windows::RawNamedLock;
43
44#[cfg(unix)]
45type NameType = PathBuf;
46#[cfg(windows)]
47type NameType = String;
48
49static OPENED_RAW_LOCKS: Lazy<
60 Mutex<HashMap<NameType, Weak<Mutex<RawNamedLock>>>>,
61> = Lazy::new(|| Mutex::new(HashMap::new()));
62
63#[derive(Debug)]
65pub struct NamedLock {
66 raw: Arc<Mutex<RawNamedLock>>,
67}
68
69impl NamedLock {
70 pub fn create(name: &str) -> Result<NamedLock> {
93 if name.is_empty() {
94 return Err(Error::EmptyName);
95 }
96
97 if name.chars().any(|c| matches!(c, '\0' | '/' | '\\')) {
105 return Err(Error::InvalidCharacter);
106 }
107
108 #[cfg(unix)]
111 let name = std::env::var_os("TMPDIR")
112 .map(PathBuf::from)
113 .unwrap_or_else(|| PathBuf::from("/tmp"))
114 .join(format!("{}.lock", name));
115
116 #[cfg(windows)]
117 let name = format!("Global\\{}", name);
118
119 NamedLock::_create(name)
120 }
121
122 #[cfg(unix)]
129 #[cfg_attr(docsrs, doc(cfg(unix)))]
130 pub fn with_path<P>(path: P) -> Result<NamedLock>
131 where
132 P: AsRef<Path>,
133 {
134 NamedLock::_create(path.as_ref().to_owned())
135 }
136
137 fn _create(name: NameType) -> Result<NamedLock> {
138 let mut opened_locks = OPENED_RAW_LOCKS.lock();
139
140 let lock = match opened_locks.get(&name).and_then(|x| x.upgrade()) {
141 Some(lock) => lock,
142 None => {
143 let lock = Arc::new(Mutex::new(RawNamedLock::create(&name)?));
144 opened_locks.insert(name, Arc::downgrade(&lock));
145 lock
146 }
147 };
148
149 Ok(NamedLock {
150 raw: lock,
151 })
152 }
153
154 pub fn try_lock(&self) -> Result<NamedLockGuard> {
158 let guard = self.raw.try_lock_arc().ok_or(Error::WouldBlock)?;
159
160 guard.try_lock()?;
161
162 Ok(NamedLockGuard {
163 raw: guard,
164 })
165 }
166
167 pub fn lock(&self) -> Result<NamedLockGuard> {
169 let guard = self.raw.lock_arc();
170
171 guard.lock()?;
172
173 Ok(NamedLockGuard {
174 raw: guard,
175 })
176 }
177}
178
179pub struct NamedLockGuard {
181 raw: ArcMutexGuard<RawMutex, RawNamedLock>,
182}
183
184impl Drop for NamedLockGuard {
185 fn drop(&mut self) {
186 let _ = self.raw.unlock();
187 }
188}
189
190impl fmt::Debug for NamedLockGuard {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.debug_struct("NamedLockGuard").field("raw", &*self.raw).finish()
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use static_assertions::assert_impl_all;
200 use std::env;
201 use std::fmt::Debug;
202 use std::process::{Child, Command};
203 use std::thread::sleep;
204 use std::time::Duration;
205 use uuid::Uuid;
206
207 fn call_proc_num(num: u32, uuid: &str) -> Child {
208 let exe = env::current_exe().expect("no exe");
209 let mut cmd = Command::new(exe);
210
211 cmd.env("TEST_CROSS_PROCESS_LOCK_PROC_NUM", num.to_string())
212 .env("TEST_CROSS_PROCESS_LOCK_UUID", uuid)
213 .arg("tests::cross_process_lock")
214 .spawn()
215 .unwrap()
216 }
217
218 #[test]
219 fn cross_process_lock() -> Result<()> {
220 let proc_num = env::var("TEST_CROSS_PROCESS_LOCK_PROC_NUM")
221 .ok()
222 .and_then(|v| v.parse().ok())
223 .unwrap_or(0);
224 let uuid = env::var("TEST_CROSS_PROCESS_LOCK_UUID")
225 .unwrap_or_else(|_| Uuid::new_v4().as_hyphenated().to_string());
226
227 match proc_num {
228 0 => {
229 let mut handle1 = call_proc_num(1, &uuid);
230 sleep(Duration::from_millis(100));
231
232 let mut handle2 = call_proc_num(2, &uuid);
233 sleep(Duration::from_millis(200));
234
235 let lock = NamedLock::create(&uuid)?;
236 assert!(matches!(lock.try_lock(), Err(Error::WouldBlock)));
237 lock.lock().expect("failed to lock");
238
239 assert!(handle2.wait().unwrap().success());
240 assert!(handle1.wait().unwrap().success());
241 }
242 1 => {
243 let lock =
244 NamedLock::create(&uuid).expect("failed to create lock");
245
246 let _guard = lock.lock().expect("failed to lock");
247 assert!(matches!(lock.try_lock(), Err(Error::WouldBlock)));
248 sleep(Duration::from_millis(200));
249 }
250 2 => {
251 let lock =
252 NamedLock::create(&uuid).expect("failed to create lock");
253
254 assert!(matches!(lock.try_lock(), Err(Error::WouldBlock)));
255 let _guard = lock.lock().expect("failed to lock");
256 sleep(Duration::from_millis(300));
257 }
258 _ => unreachable!(),
259 }
260
261 Ok(())
262 }
263
264 #[test]
265 fn edge_cases() -> Result<()> {
266 let uuid = Uuid::new_v4().as_hyphenated().to_string();
267 let lock1 = NamedLock::create(&uuid)?;
268 let lock2 = NamedLock::create(&uuid)?;
269
270 {
271 let _guard1 = lock1.try_lock()?;
272 assert!(matches!(lock1.try_lock(), Err(Error::WouldBlock)));
273 assert!(matches!(lock2.try_lock(), Err(Error::WouldBlock)));
274 }
275
276 {
277 let _guard2 = lock2.try_lock()?;
278 assert!(matches!(lock1.try_lock(), Err(Error::WouldBlock)));
279 assert!(matches!(lock2.try_lock(), Err(Error::WouldBlock)));
280 }
281
282 Ok(())
283 }
284
285 #[test]
286 fn owned_guard() -> Result<()> {
287 let uuid = Uuid::new_v4().as_hyphenated().to_string();
288 let lock1 = NamedLock::create(&uuid)?;
289 let lock2 = NamedLock::create(&uuid)?;
290
291 let guard1 = lock1.try_lock()?;
293 assert!(matches!(lock2.try_lock(), Err(Error::WouldBlock)));
294
295 drop(lock1);
298 assert!(matches!(lock2.try_lock(), Err(Error::WouldBlock)));
299
300 drop(guard1);
302 let _guard2 = lock2.try_lock()?;
303
304 Ok(())
305 }
306
307 #[test]
308 fn invalid_names() {
309 assert!(matches!(NamedLock::create(""), Err(Error::EmptyName)));
310
311 assert!(matches!(
312 NamedLock::create("abc/"),
313 Err(Error::InvalidCharacter)
314 ));
315
316 assert!(matches!(
317 NamedLock::create("abc\\"),
318 Err(Error::InvalidCharacter)
319 ));
320
321 assert!(matches!(
322 NamedLock::create("abc\0"),
323 Err(Error::InvalidCharacter)
324 ));
325 }
326
327 #[test]
328 fn check_traits() {
329 assert_impl_all!(NamedLock: Debug, Send, Sync);
330 assert_impl_all!(NamedLockGuard: Debug, Send, Sync);
331 }
332}