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
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}