use std::fmt;
use std::fs::{File, OpenOptions, TryLockError};
use std::io;
use std::path::{Path, PathBuf};
use super::DatabaseError;
const LOCK_FILE: &str = "writer.lock";
pub struct DataDirLock {
file: File,
lock_path: PathBuf,
}
impl fmt::Debug for DataDirLock {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DataDirLock")
.field("lock_path", &self.lock_path)
.field("file", &self.file)
.finish()
}
}
impl DataDirLock {
pub fn acquire(data_dir: &Path) -> Result<Self, LockError> {
let lock_path = data_dir.join(LOCK_FILE);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&lock_path)
.map_err(|error| LockError::Io {
lock_path: lock_path.clone(),
error,
})?;
match file.try_lock() {
Ok(()) => Ok(Self { file, lock_path }),
Err(TryLockError::WouldBlock) => Err(LockError::AlreadyLocked { lock_path }),
Err(error) => Err(LockError::Io {
lock_path,
error: error.into(),
}),
}
}
}
#[derive(Debug)]
pub enum LockError {
AlreadyLocked { lock_path: PathBuf },
Io {
lock_path: PathBuf,
error: io::Error,
},
}
impl fmt::Display for LockError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AlreadyLocked { lock_path } => write!(
formatter,
"data dir writer lock at {} is held by another live writer",
lock_path.display()
),
Self::Io { lock_path, error } => write!(
formatter,
"failed to acquire data dir writer lock at {}: {error}",
lock_path.display()
),
}
}
}
impl std::error::Error for LockError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io { error, .. } => Some(error),
Self::AlreadyLocked { .. } => None,
}
}
}
impl From<LockError> for DatabaseError {
fn from(error: LockError) -> Self {
match error {
LockError::AlreadyLocked { lock_path } => Self::DataDirLocked { lock_path },
LockError::Io { lock_path, error } => Self::LockFileIo { lock_path, error },
}
}
}