1use 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#[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}