git_zero_rustix/cache/
coordination.rs1use super::{DiskCache, cstr, key_name};
12use git_zero::{Error, Result, cache::selected::Key};
13use rustix::{
14 fd::OwnedFd,
15 fs::{self, FlockOperation, Mode, OFlags},
16 io::Errno,
17};
18
19#[must_use = "keep the guard alive while checking and publishing the cache entry"]
22pub struct Lease {
23 file: OwnedFd,
24}
25
26impl Drop for Lease {
27 fn drop(&mut self) {
28 let _ = fs::flock(&self.file, FlockOperation::Unlock);
31 }
32}
33
34impl DiskCache<'_, '_> {
35 pub fn try_lease(&self, key: Key) -> Result<Option<Lease>> {
45 if !self.enabled {
46 return Ok(None);
47 }
48 let mut name = key_name(key);
49 name[96..101].copy_from_slice(b".lock");
50 let file = fs::openat(
51 self.directory,
52 cstr(&name),
53 OFlags::RDWR | OFlags::CREATE | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::NONBLOCK,
54 Mode::RUSR | Mode::WUSR,
55 )
56 .map_err(|_| Error::Backend)?;
57 if crate::storage::file_size(&file)? != 0 {
58 return Err(Error::InvalidObject);
59 }
60 match fs::flock(&file, FlockOperation::NonBlockingLockExclusive) {
61 Ok(()) => Ok(Some(Lease { file })),
62 Err(Errno::WOULDBLOCK) => Ok(None),
63 Err(_) => Err(Error::Backend),
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 extern crate std;
71 use super::*;
72 use rustix::fd::AsFd;
73
74 #[test]
75 fn dropping_a_guard_unlocks_even_while_a_duplicate_descriptor_remains() {
76 let path = std::env::temp_dir().join(std::format!(
77 "git-zero-coordination-dup-{}",
78 std::process::id()
79 ));
80 std::fs::create_dir(&path).unwrap();
81 let directory = fs::openat(
82 fs::CWD,
83 path.as_os_str().as_encoded_bytes(),
84 OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
85 Mode::empty(),
86 )
87 .unwrap();
88 let repository = git_zero::EmptyRepository::new("refs/heads/main").unwrap();
89 let key = Key::derive(
90 &repository,
91 git_zero::cache::selected::Selection::All,
92 &mut [],
93 )
94 .unwrap();
95 let mut resident = [0; 1];
96 let cache = DiskCache::new(directory.as_fd(), &mut resident, [7; 16]);
97 let guard = cache.try_lease(key).unwrap().unwrap();
98 assert!(
99 rustix::io::fcntl_getfd(&guard.file)
100 .unwrap()
101 .contains(rustix::io::FdFlags::CLOEXEC)
102 );
103 let inherited = rustix::io::dup(&guard.file).unwrap();
106 assert!(cache.try_lease(key).unwrap().is_none());
107 drop(guard);
108 let retry = cache.try_lease(key).unwrap().unwrap();
109 drop(retry);
110 drop(inherited);
111 drop(directory);
112 std::fs::remove_dir_all(path).unwrap();
113 }
114}