jj_lib/lock/
fallback.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
15use std::fs::File;
16use std::fs::OpenOptions;
17use std::path::PathBuf;
18use std::time::Duration;
19
20use tracing::instrument;
21
22use super::FileLockError;
23
24pub struct FileLock {
25    path: PathBuf,
26    _file: File,
27}
28
29struct BackoffIterator {
30    next_sleep_secs: f32,
31    elapsed_secs: f32,
32}
33
34impl BackoffIterator {
35    fn new() -> Self {
36        Self {
37            next_sleep_secs: 0.001,
38            elapsed_secs: 0.0,
39        }
40    }
41}
42
43impl Iterator for BackoffIterator {
44    type Item = Duration;
45
46    fn next(&mut self) -> Option<Self::Item> {
47        if self.elapsed_secs >= 10.0 {
48            None
49        } else {
50            let current_sleep = self.next_sleep_secs * (rand::random::<f32>() + 0.5);
51            self.next_sleep_secs *= 1.5;
52            self.elapsed_secs += current_sleep;
53            Some(Duration::from_secs_f32(current_sleep))
54        }
55    }
56}
57
58// Suppress warning on platforms where specialized lock impl is available
59#[cfg_attr(all(unix, not(test)), expect(dead_code))]
60impl FileLock {
61    pub fn lock(path: PathBuf) -> Result<Self, FileLockError> {
62        let mut options = OpenOptions::new();
63        options.create_new(true);
64        options.write(true);
65        let mut backoff_iterator = BackoffIterator::new();
66        loop {
67            match options.open(&path) {
68                Ok(file) => {
69                    return Ok(Self { path, _file: file });
70                }
71                Err(err)
72                    if err.kind() == std::io::ErrorKind::AlreadyExists
73                        || (cfg!(windows)
74                            && err.kind() == std::io::ErrorKind::PermissionDenied) =>
75                {
76                    if let Some(duration) = backoff_iterator.next() {
77                        std::thread::sleep(duration);
78                    } else {
79                        return Err(FileLockError {
80                            message: "Timed out while trying to create lock file",
81                            path,
82                            err,
83                        });
84                    }
85                }
86                Err(err) => {
87                    return Err(FileLockError {
88                        message: "Failed to create lock file",
89                        path,
90                        err,
91                    });
92                }
93            }
94        }
95    }
96}
97
98impl Drop for FileLock {
99    #[instrument(skip_all)]
100    fn drop(&mut self) {
101        std::fs::remove_file(&self.path)
102            .inspect_err(|err| tracing::warn!(?err, ?self.path, "Failed to delete lock file"))
103            .ok();
104    }
105}