1#![expect(missing_docs)]
16
17#[cfg(unix)]
18mod unix;
19#[cfg(windows)]
20mod windows;
21
22use std::io;
23use std::path::PathBuf;
24
25use thiserror::Error;
26
27#[cfg(unix)]
28pub use self::unix::FileLock;
29#[cfg(windows)]
30pub use self::windows::FileLock;
31
32#[derive(Debug, Error)]
33#[error("{message}: {path}")]
34pub struct FileLockError {
35 pub message: &'static str,
36 pub path: PathBuf,
37 #[source]
38 pub err: io::Error,
39}
40
41#[cfg(test)]
42mod tests {
43 use std::cmp::max;
44 use std::fs;
45 use std::thread;
46 use std::time::Duration;
47
48 use super::*;
49 use crate::tests::new_temp_dir;
50
51 #[test]
52 fn lock_basic() {
53 let temp_dir = new_temp_dir();
54 let lock_path = temp_dir.path().join("test.lock");
55 assert!(!lock_path.exists());
56 {
57 let _lock = FileLock::lock(lock_path.clone()).unwrap();
58 assert!(lock_path.exists());
59 }
60 assert!(!lock_path.exists());
61 }
62
63 #[test]
64 fn lock_concurrent() {
65 let temp_dir = new_temp_dir();
66 let data_path = temp_dir.path().join("test");
67 let lock_path = temp_dir.path().join("test.lock");
68 fs::write(&data_path, 0_u32.to_le_bytes()).unwrap();
69 let num_threads = max(num_cpus::get(), 4);
70 thread::scope(|s| {
71 for _ in 0..num_threads {
72 s.spawn(|| {
73 let _lock = FileLock::lock(lock_path.clone()).unwrap();
74 let data = fs::read(&data_path).unwrap();
75 let value = u32::from_le_bytes(data.try_into().unwrap());
76 thread::sleep(Duration::from_millis(1));
77 fs::write(&data_path, (value + 1).to_le_bytes()).unwrap();
78 });
79 }
80 });
81 let data = fs::read(&data_path).unwrap();
82 let value = u32::from_le_bytes(data.try_into().unwrap());
83 assert_eq!(value, num_threads as u32);
84 }
85}