Skip to main content

jj_lib/lock/
mod.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![expect(missing_docs)]
16
17mod backoff;
18#[cfg(unix)]
19mod unix;
20#[cfg(windows)]
21mod windows;
22
23use std::io;
24use std::path::PathBuf;
25
26use thiserror::Error;
27
28#[cfg(unix)]
29pub use self::unix::FileLock;
30#[cfg(windows)]
31pub use self::windows::FileLock;
32
33#[derive(Debug, Error)]
34#[error("{message}: {path}")]
35pub struct FileLockError {
36    pub message: &'static str,
37    pub path: PathBuf,
38    #[source]
39    pub err: io::Error,
40}
41
42#[cfg(test)]
43mod tests {
44    use std::cmp::max;
45    use std::fs;
46    use std::thread;
47    use std::time::Duration;
48
49    use super::*;
50    use crate::tests::new_temp_dir;
51
52    #[test]
53    fn lock_basic() {
54        let temp_dir = new_temp_dir();
55        let lock_path = temp_dir.path().join("test.lock");
56        assert!(!lock_path.exists());
57        {
58            let _lock = FileLock::lock(lock_path.clone()).unwrap();
59            assert!(lock_path.exists());
60        }
61        assert!(!lock_path.exists());
62    }
63
64    #[test]
65    fn lock_concurrent() {
66        let temp_dir = new_temp_dir();
67        let data_path = temp_dir.path().join("test");
68        let lock_path = temp_dir.path().join("test.lock");
69        fs::write(&data_path, 0_u32.to_le_bytes()).unwrap();
70        let num_threads = max(num_cpus::get(), 4);
71        thread::scope(|s| {
72            for _ in 0..num_threads {
73                s.spawn(|| {
74                    let _lock = FileLock::lock(lock_path.clone()).unwrap();
75                    let data = fs::read(&data_path).unwrap();
76                    let value = u32::from_le_bytes(data.try_into().unwrap());
77                    thread::sleep(Duration::from_millis(1));
78                    fs::write(&data_path, (value + 1).to_le_bytes()).unwrap();
79                });
80            }
81        });
82        let data = fs::read(&data_path).unwrap();
83        let value = u32::from_le_bytes(data.try_into().unwrap());
84        assert_eq!(value, num_threads as u32);
85    }
86}