1use std::fs::File;
2use std::fs::OpenOptions;
3use std::io;
4
5use codeq::error_context_ext::ErrorContextExt;
6use fs2::FileExt;
7use log::info;
8
9use crate::Config;
10
11#[derive(Debug)]
12pub struct WalLock {
13 path: String,
14 f: File,
15}
16
17impl WalLock {
18 pub(crate) const LOCK_FILE_NAME: &'static str = "LOCK";
19
20 pub(crate) fn new(config: &Config) -> Result<Self, io::Error> {
21 let path = Self::lock_path(config);
22
23 let f = OpenOptions::new()
24 .read(true)
25 .write(true)
26 .create(true)
27 .truncate(true)
28 .open(&path)
29 .context(|| format!("create lock file '{}'", path))?;
30
31 f.try_lock_exclusive().map_err(|e| {
32 io::Error::new(
33 io::ErrorKind::WouldBlock,
34 format!(
35 "Directory '{}' is already locked by another process, \
36 shutdown other process to continue; \
37 error:({})",
38 config.dir, e
39 ),
40 )
41 })?;
42
43 info!("Directory lock acquired: {}", path);
44
45 Ok(Self { path, f })
46 }
47
48 pub(crate) fn lock_path(config: &Config) -> String {
49 format!("{}/{}", config.dir, Self::LOCK_FILE_NAME)
50 }
51}
52
53impl Drop for WalLock {
54 fn drop(&mut self) {
55 let _ = fs2::FileExt::unlock(&self.f);
56 info!("Directory lock released: {}", self.path);
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::Config;
63 use crate::WalLock;
64
65 #[test]
66 fn test_lock_file() {
67 let temp_dir = tempfile::tempdir().unwrap();
68 let config = Config::new(temp_dir.path().to_str().unwrap());
69
70 let lf = WalLock::new(&config).unwrap();
71 println!("Directory locked successfully");
72
73 let lf2 = WalLock::new(&config);
74 assert!(lf2.is_err());
75
76 drop(lf);
77 let _lf2 = WalLock::new(&config).unwrap();
78 println!("Directory locked successfully after dropping first lock");
79 }
80}