use super::{DiskCache, cstr, key_name};
use git_zero::{Error, Result, cache::selected::Key};
use rustix::{
fd::OwnedFd,
fs::{self, FlockOperation, Mode, OFlags},
io::Errno,
};
#[must_use = "keep the guard alive while checking and publishing the cache entry"]
pub struct Lease {
file: OwnedFd,
}
impl Drop for Lease {
fn drop(&mut self) {
let _ = fs::flock(&self.file, FlockOperation::Unlock);
}
}
impl DiskCache<'_, '_> {
pub fn try_lease(&self, key: Key) -> Result<Option<Lease>> {
if !self.enabled {
return Ok(None);
}
let mut name = key_name(key);
name[96..101].copy_from_slice(b".lock");
let file = fs::openat(
self.directory,
cstr(&name),
OFlags::RDWR | OFlags::CREATE | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NONBLOCK,
Mode::RUSR | Mode::WUSR,
)
.map_err(|_| Error::Backend)?;
if crate::storage::file_size(&file)? != 0 {
return Err(Error::InvalidObject);
}
match fs::flock(&file, FlockOperation::NonBlockingLockExclusive) {
Ok(()) => Ok(Some(Lease { file })),
Err(Errno::WOULDBLOCK) => Ok(None),
Err(_) => Err(Error::Backend),
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use rustix::fd::AsFd;
#[test]
fn dropping_a_guard_unlocks_even_while_a_duplicate_descriptor_remains() {
let path = std::env::temp_dir().join(std::format!(
"git-zero-coordination-dup-{}",
std::process::id()
));
std::fs::create_dir(&path).unwrap();
let directory = fs::openat(
fs::CWD,
path.as_os_str().as_encoded_bytes(),
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty(),
)
.unwrap();
let repository = git_zero::EmptyRepository::new("refs/heads/main").unwrap();
let key = Key::derive(
&repository,
git_zero::cache::selected::Selection::All,
&mut [],
)
.unwrap();
let mut resident = [0; 1];
let cache = DiskCache::new(directory.as_fd(), &mut resident, [7; 16]);
let guard = cache.try_lease(key).unwrap().unwrap();
assert!(
rustix::io::fcntl_getfd(&guard.file)
.unwrap()
.contains(rustix::io::FdFlags::CLOEXEC)
);
let inherited = rustix::io::dup(&guard.file).unwrap();
assert!(cache.try_lease(key).unwrap().is_none());
drop(guard);
let retry = cache.try_lease(key).unwrap().unwrap();
drop(retry);
drop(inherited);
drop(directory);
std::fs::remove_dir_all(path).unwrap();
}
}