agent_teams/util/
file_lock.rs1use std::fs::{File, OpenOptions};
20use std::path::{Path, PathBuf};
21
22use fs2::FileExt;
23
24use crate::error::{Error, Result};
25
26pub struct FileLock {
31 file: File,
32 path: PathBuf,
33}
34
35impl FileLock {
36 pub fn acquire(path: &Path) -> Result<Self> {
40 let file = OpenOptions::new()
41 .create(true)
42 .truncate(false)
43 .write(true)
44 .open(path)
45 .map_err(|e| Error::LockFailed {
46 path: path.to_path_buf(),
47 reason: e.to_string(),
48 })?;
49
50 file.lock_exclusive().map_err(|e| Error::LockFailed {
51 path: path.to_path_buf(),
52 reason: e.to_string(),
53 })?;
54
55 Ok(Self {
56 file,
57 path: path.to_path_buf(),
58 })
59 }
60
61 pub fn try_acquire(path: &Path) -> Result<Option<Self>> {
65 let file = OpenOptions::new()
66 .create(true)
67 .truncate(false)
68 .write(true)
69 .open(path)
70 .map_err(|e| Error::LockFailed {
71 path: path.to_path_buf(),
72 reason: e.to_string(),
73 })?;
74
75 match file.try_lock_exclusive() {
76 Ok(()) => Ok(Some(Self {
77 file,
78 path: path.to_path_buf(),
79 })),
80 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
81 Err(ref e)
84 if e.raw_os_error()
85 .is_some_and(|code| code == 11 || code == 35) =>
86 {
87 Ok(None)
88 }
89 Err(e) => Err(Error::LockFailed {
90 path: path.to_path_buf(),
91 reason: e.to_string(),
92 }),
93 }
94 }
95
96 pub fn path(&self) -> &Path {
98 &self.path
99 }
100}
101
102impl Drop for FileLock {
103 fn drop(&mut self) {
104 let _ = self.file.unlock();
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn acquire_and_release() {
115 let dir = tempfile::tempdir().unwrap();
116 let lock_path = dir.path().join("test.lock");
117
118 let lock = FileLock::acquire(&lock_path).unwrap();
119 assert!(lock_path.exists());
120 drop(lock);
121 }
122
123 #[test]
124 fn try_acquire_non_blocking() {
125 let dir = tempfile::tempdir().unwrap();
126 let lock_path = dir.path().join("test.lock");
127
128 let lock1 = FileLock::try_acquire(&lock_path).unwrap();
129 assert!(lock1.is_some());
130
131 drop(lock1);
133
134 let lock2 = FileLock::try_acquire(&lock_path).unwrap();
135 assert!(lock2.is_some());
136 }
137
138 #[test]
139 fn lock_path_accessor() {
140 let dir = tempfile::tempdir().unwrap();
141 let lock_path = dir.path().join("test.lock");
142
143 let lock = FileLock::acquire(&lock_path).unwrap();
144 assert_eq!(lock.path(), lock_path);
145 }
146}