use super::*;
pub(crate) const LOCK_RETRY_ATTEMPTS: usize = 40;
pub(crate) const LOCK_RETRY_DELAY_MS: u64 = 50;
pub(crate) const LOCK_STALE_AFTER_SECS: u64 = 300;
pub(crate) struct MemoryLock {
pub(super) path: PathBuf,
}
impl MemoryLock {
pub(crate) async fn acquire(path: &Path) -> Result<Self> {
Self::acquire_with_stale_after(path, Duration::from_secs(LOCK_STALE_AFTER_SECS)).await
}
pub(crate) async fn acquire_with_stale_after(path: &Path, stale_after: Duration) -> Result<Self> {
for _ in 0..LOCK_RETRY_ATTEMPTS {
match tokio::fs::OpenOptions::new().create_new(true).write(true).open(path).await {
Ok(_) => {
return Ok(Self { path: path.to_path_buf() });
}
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
if let Some(age) = lock_age(path).await {
if age >= stale_after {
let _ = tokio::fs::remove_file(path).await;
}
}
sleep(Duration::from_millis(LOCK_RETRY_DELAY_MS)).await
}
Err(err) => {
return Err(err).with_context(|| format!("Failed to acquire {}", path.display()));
}
}
}
Err(anyhow::anyhow!("Timed out waiting for persistent memory lock {}", path.display()))
}
}
pub(crate) async fn lock_age(path: &Path) -> Option<Duration> {
let meta = tokio::fs::metadata(path).await.ok()?;
meta.modified().ok()?.elapsed().ok()
}
impl Drop for MemoryLock {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}