use std::fs::{self, File, OpenOptions};
use std::io::{Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use fs2::FileExt;
pub const LOCK_FILENAME: &str = ".lock";
#[derive(Debug)]
pub struct DirLock {
path: PathBuf,
file: File,
}
impl DirLock {
pub fn acquire(dir: &Path) -> Result<Self> {
fs::create_dir_all(dir)
.with_context(|| format!("datawal: create_dir_all {}", dir.display()))?;
let path = dir.join(LOCK_FILENAME);
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(&path)
.with_context(|| format!("datawal: open lock file {}", path.display()))?;
file.try_lock_exclusive().map_err(|e| {
anyhow!(
"datawal: log directory is already locked: {} ({}). Another process is using \
this datawal directory.",
path.display(),
e
)
})?;
let pid = std::process::id();
let _ = file.set_len(0);
let _ = file.seek(SeekFrom::Start(0));
let _ = writeln!(file, "{pid}");
let _ = file.sync_all();
Ok(Self {
path,
file: _take(file),
})
}
pub fn path(&self) -> &Path {
&self.path
}
}
fn _take(f: File) -> File {
f
}
impl Drop for DirLock {
fn drop(&mut self) {
let _ = FileExt::unlock(&self.file);
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn acquire_then_release_allows_reacquire() {
let td = TempDir::new().unwrap();
{
let _l = DirLock::acquire(td.path()).unwrap();
assert!(td.path().join(LOCK_FILENAME).exists());
}
assert!(td.path().join(LOCK_FILENAME).exists());
let _l2 = DirLock::acquire(td.path()).unwrap();
}
#[test]
fn second_acquire_fails_while_first_held() {
let td = TempDir::new().unwrap();
let _l1 = DirLock::acquire(td.path()).unwrap();
let err = DirLock::acquire(td.path()).unwrap_err();
assert!(format!("{err}").contains("already locked"));
}
#[test]
fn drop_releases_lock() {
let td = TempDir::new().unwrap();
{
let _l1 = DirLock::acquire(td.path()).unwrap();
}
let _l2 = DirLock::acquire(td.path()).unwrap();
}
#[test]
fn stale_lock_file_is_not_a_problem() {
let td = TempDir::new().unwrap();
let path = td.path().join(LOCK_FILENAME);
std::fs::write(path, b"12345\n").unwrap();
let _l = DirLock::acquire(td.path()).unwrap();
}
}