use crate::{Error, Result};
use std::fs::{File, OpenOptions, TryLockError};
use std::path::{Path, PathBuf};
const RETRY_MS: u64 = 5;
const GRACE_MS: u64 = 250;
pub struct WriterLock {
_file: File,
}
pub(crate) fn lock_path(db: &Path) -> PathBuf {
let mut name = db.file_name().unwrap_or_default().to_os_string();
name.push(".lock");
db.with_file_name(name)
}
impl WriterLock {
pub fn acquire(db: &Path) -> Result<Self> {
let file = OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(lock_path(db))?;
let mut waited = 0;
loop {
match file.try_lock() {
Ok(()) => return Ok(Self { _file: file }),
Err(TryLockError::WouldBlock) if waited < GRACE_MS => {
std::thread::sleep(std::time::Duration::from_millis(RETRY_MS));
waited += RETRY_MS;
}
Err(TryLockError::WouldBlock) => return Err(Error::Locked),
Err(TryLockError::Error(e)) => return Err(e.into()),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
#[test]
fn waits_for_a_briefly_held_lock() {
let d = tempfile::tempdir().unwrap();
let p = d.path().join(".pith");
let held = WriterLock::acquire(&p).unwrap();
let t = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(50));
drop(held);
});
assert!(WriterLock::acquire(&p).is_ok());
t.join().unwrap();
}
#[test]
fn a_long_held_lock_is_reported_locked_quickly() {
let d = tempfile::tempdir().unwrap();
let p = d.path().join(".pith");
let _held = WriterLock::acquire(&p).unwrap();
let t = Instant::now();
assert!(matches!(WriterLock::acquire(&p), Err(Error::Locked)));
assert!(t.elapsed() < Duration::from_secs(3)); }
}