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