use crate::cache_file::{CacheFileError, confined::ConfinedManagedPath};
use std::fmt;
pub(super) struct RefreshLockGuard {
path: ConfinedManagedPath,
active: bool,
}
impl fmt::Debug for RefreshLockGuard {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("RefreshLockGuard")
.field("path", &self.path.display_path())
.field("active", &self.active)
.finish()
}
}
impl RefreshLockGuard {
pub(super) const fn new(path: ConfinedManagedPath) -> Self {
Self { path, active: true }
}
pub(super) fn release(self) -> Result<(), CacheFileError> {
self.release_with_sync(ConfinedManagedPath::sync_parent)
}
fn release_with_sync(
mut self,
sync: impl FnOnce(&ConfinedManagedPath) -> Result<(), CacheFileError>,
) -> Result<(), CacheFileError> {
self.path
.remove_file()
.map_err(|source| CacheFileError::RemoveRefreshLock {
path: self.path.display_path().to_path_buf(),
source,
})?;
self.active = false;
sync(&self.path)
}
}
impl Drop for RefreshLockGuard {
fn drop(&mut self) {
if self.active {
let _ = self.path.remove_file();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{cache_file::confined::ConfinedCacheRoot, test_support::temp_dir};
use std::{fs, io};
#[test]
fn sync_failure_preserves_a_replacement_lock() {
let root_path = temp_dir("ic-query-lock-sync-failure");
let root = ConfinedCacheRoot::open(&root_path, true)
.expect("open root")
.expect("root exists");
let path = root_path.join("refresh.lock");
let managed = root.resolve_parent(&path, true).unwrap().unwrap();
fs::write(&path, b"original").unwrap();
let error = RefreshLockGuard::new(managed)
.release_with_sync(|_| {
fs::write(&path, b"replacement").unwrap();
Err(CacheFileError::SyncDirectory {
path: root_path.clone(),
source: io::Error::other("injected sync failure"),
})
})
.expect_err("sync failure is returned");
assert!(matches!(error, CacheFileError::SyncDirectory { .. }));
assert_eq!(fs::read(&path).unwrap(), b"replacement");
fs::remove_dir_all(root_path).unwrap();
}
}