use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
use fs2::FileExt;
use crate::error::StoreError;
#[derive(Debug)]
pub struct WriterLease {
file: File,
run_id: String,
}
pub fn lock_path(root: &Path, run_id: &str) -> PathBuf {
let safe: String = run_id
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-') {
c
} else {
'_'
}
})
.collect();
root.join("locks").join(format!("{safe}.lock"))
}
fn open_lock_file(root: &Path, run_id: &str) -> Result<File, StoreError> {
let path = lock_path(root, run_id);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
Ok(OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path)?)
}
impl WriterLease {
pub fn acquire(root: impl AsRef<Path>, run_id: &str) -> Result<Self, StoreError> {
let file = open_lock_file(root.as_ref(), run_id)?;
match FileExt::try_lock_exclusive(&file) {
Ok(()) => Ok(WriterLease {
file,
run_id: run_id.to_owned(),
}),
Err(err) if err.kind() == fs2::lock_contended_error().kind() => {
Err(StoreError::WriterBusy {
run_id: run_id.to_owned(),
})
}
Err(err) => Err(err.into()),
}
}
pub fn is_held(root: impl AsRef<Path>, run_id: &str) -> bool {
let file = match OpenOptions::new()
.read(true)
.write(true)
.open(lock_path(root.as_ref(), run_id))
{
Ok(file) => file,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return false,
Err(_) => return true,
};
match FileExt::try_lock_exclusive(&file) {
Ok(()) => {
let _ = FileExt::unlock(&file);
false
}
Err(_) => true,
}
}
pub fn run_id(&self) -> &str {
&self.run_id
}
}
impl Drop for WriterLease {
fn drop(&mut self) {
let _ = FileExt::unlock(&self.file);
}
}