use std::fs::{File, TryLockError};
use std::path::Path;
use crate::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FileLocking {
#[default]
Enabled,
Disabled,
BestEffort,
}
fn parse_env(value: &str) -> Option<FileLocking> {
let v = value.trim();
if v.eq_ignore_ascii_case("FALSE")
|| v == "0"
|| v.eq_ignore_ascii_case("NO")
|| v.eq_ignore_ascii_case("OFF")
{
Some(FileLocking::Disabled)
} else if v.eq_ignore_ascii_case("BEST_EFFORT") {
Some(FileLocking::BestEffort)
} else if v.eq_ignore_ascii_case("TRUE")
|| v == "1"
|| v.eq_ignore_ascii_case("YES")
|| v.eq_ignore_ascii_case("ON")
{
Some(FileLocking::Enabled)
} else {
None
}
}
fn resolve(requested: FileLocking) -> FileLocking {
std::env::var("HDF5_USE_FILE_LOCKING")
.ok()
.and_then(|v| parse_env(&v))
.unwrap_or(requested)
}
pub(crate) fn acquire_exclusive(
handle: &File,
requested: FileLocking,
path: &Path,
) -> Result<(), Error> {
let mode = resolve(requested);
if mode == FileLocking::Disabled {
return Ok(());
}
match handle.try_lock() {
Ok(()) => Ok(()),
Err(TryLockError::WouldBlock) => Err(Error::FileLocked(format!(
"{}: file is already locked by another process. If a previous writer \
crashed, the OS lock is released automatically (try again); a leftover \
on-disk SWMR flag can be cleared with SwmrWriter::clear_swmr_flag. Set \
HDF5_USE_FILE_LOCKING=FALSE or pass FileLocking::Disabled to bypass locking.",
path.display(),
))),
Err(TryLockError::Error(e)) => match mode {
FileLocking::BestEffort => Ok(()),
_ => Err(Error::Io(e)),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_env_recognizes_disable_values() {
for v in ["FALSE", "false", "0", "No", "off", " false "] {
assert_eq!(parse_env(v), Some(FileLocking::Disabled), "value {v:?}");
}
}
#[test]
fn parse_env_recognizes_enable_and_best_effort() {
for v in ["TRUE", "true", "1", "Yes", "on"] {
assert_eq!(parse_env(v), Some(FileLocking::Enabled), "value {v:?}");
}
assert_eq!(parse_env("BEST_EFFORT"), Some(FileLocking::BestEffort));
assert_eq!(parse_env("best_effort"), Some(FileLocking::BestEffort));
}
#[test]
fn parse_env_unrecognized_is_none() {
assert_eq!(parse_env(""), None);
assert_eq!(parse_env("maybe"), None);
assert_eq!(parse_env("2"), None);
}
#[test]
fn default_is_enabled() {
assert_eq!(FileLocking::default(), FileLocking::Enabled);
}
}