fancy_flocks/
dirty_flock.rs1use std::ops::DerefMut;
5use std::io::{Seek, SeekFrom, Read, Write, Result, ErrorKind};
6use std::fs::File;
7use std::path::Path;
8use std::cell::Cell;
9use sd_flock::SdFlock;
10use rand::random;
11
12pub struct DirtyFlock(SdFlock, Cell<Epoch>, Cell<LockedExclusive>);
13
14#[derive(Copy, Clone, Eq, PartialEq, Debug)]
15struct Epoch {
16 era: u64, rev: u64, }
19
20type LockedExclusive = bool;
21
22#[derive(Copy, Clone, Eq, PartialEq, Debug)]
23pub enum State {
24 Dirty,
25 Clean,
26}
27
28impl DirtyFlock {
29 pub fn new<P>(p: P) -> DirtyFlock
30 where P: AsRef<Path>
31 {
32 let epoch = Epoch { era: 0, rev: 0 };
33 DirtyFlock(SdFlock::new(p), Cell::new(epoch), Cell::new(false))
34 }
35
36 pub fn lock_shared(&self) -> Result<State> {
37 self.init_take(SdFlock::lock_shared, false)
38 }
39
40 pub fn lock_exclusive(&self) -> Result<State> {
41 self.init_take(SdFlock::lock_exclusive, true)
42 }
43
44 pub fn try_lock_shared(&self) -> Result<State> {
45 self.init_take(SdFlock::try_lock_shared, false)
46 }
47
48 pub fn try_lock_exclusive(&self) -> Result<State> {
49 self.init_take(SdFlock::try_lock_exclusive, true)
50 }
51
52 pub fn unlock(&self) -> Result<()> {
53 if self.2.get() {
54 self.bump_epoch()?;
55 }
56
57 self.0.unlock()?;
58
59 self.2.set(false);
60
61 Ok(())
62 }
63
64 pub fn path(&self) -> &Path {
65 self.0.path()
66 }
67
68 pub fn file(&mut self) -> &mut File {
69 self.0.file()
70 }
71
72 fn path_file_size(&self) -> Result<u64> {
73 match self.0.path().metadata() {
74 Ok(m) => Ok(m.len()),
75 Err(e) => {
76 if e.kind() == ErrorKind::NotFound {
77 Ok(0)
78 } else {
79 Err(e)
80 }
81 }
82 }
83 }
84
85 fn file_size(&self) -> Result<u64> {
86 let mut file = self.0.borrow_file_mut();
87 let file = file.deref_mut().as_mut().expect("locked file");
88 Ok(file.metadata()?.len())
89 }
90
91 fn new_epoch(&self) -> Result<()> {
92 let epoch = Epoch { era: random(), rev: random() };
94 self.write_epoch(epoch)?;
95 Ok(())
96 }
97
98 fn bump_epoch(&self) -> Result<()> {
99 let old_epoch = self.1.get();
101 let new_epoch = Epoch { era: old_epoch.era, rev: old_epoch.rev + 1 };
102 self.write_epoch(new_epoch)?;
103 self.1.set(new_epoch);
104 Ok(())
105 }
106
107 fn read_epoch(&self) -> Result<Epoch> {
108 let buf = &mut [0; 16];
110 let mut file = self.0.borrow_file_mut();
111 let file = file.deref_mut().as_mut().expect("locked file");
112 file.seek(SeekFrom::Start(0))?;
113 file.read_exact(buf)?;
114 let epoch = array_to_epoch(buf);
115 Ok(epoch)
116 }
117
118 fn write_epoch(&self, epoch: Epoch) -> Result<()> {
119 {
121 let buf = &mut epoch_to_array(epoch);
122 let mut file_ = self.0.borrow_file_mut();
123 let file = file_.deref_mut().as_mut().expect("locked file");
124 file.seek(SeekFrom::Start(0))?;
125 file.write_all(buf)?;
126 }
127 assert_eq!(self.read_epoch()?, epoch);
128 Ok(())
129 }
130
131 fn init_take(&self, lock: fn(&SdFlock) -> Result<()>,
132 exclusive: bool) -> Result<State>
133 {
134 let known_epoch = self.1.get();
135
136 loop {
137 if self.path_file_size()? == 0 {
139 if self.0.try_lock_exclusive().is_ok() {
143 match self.file_size() {
144 Ok(sz) if sz == 0 => {
145 if let Err(e) = self.new_epoch() {
147 self.0.unlock().expect("unlock on error path");
148 return Err(e);
149 }
150 }
151 Ok(_) => {
152 }
154 Err(e) => {
155 self.0.unlock().expect("unlock on error path");
156 return Err(e);
157 }
158 }
159 self.0.unlock().expect("internal unlock");
160 }
161 }
162
163 lock(&self.0)?;
164
165 match self.file_size() {
166 Ok(sz) if sz != 0 => {
167 match self.read_epoch() {
168 Ok(epoch) => {
169 self.1.set(epoch);
170 self.2.set(exclusive);
171 if epoch == known_epoch {
172 return Ok(State::Clean);
173 } else {
174 return Ok(State::Dirty);
175 }
176 }
177 Err(e) => {
178 self.0.unlock().expect("unlock on error path");
179 return Err(e);
180 }
181 }
182 }
183 Ok(_) => {
184 self.0.unlock().expect("internal unlock");
186 continue;
187 }
188 Err(e) => {
189 self.0.unlock().expect("unlock on error path");
190 return Err(e);
191 }
192 }
193 }
194 }
195}
196
197fn epoch_to_array(epoch: Epoch) -> [u8; 16] {
198 [
199 (epoch.era >> 0) as u8,
200 (epoch.era >> 8) as u8,
201 (epoch.era >> 16) as u8,
202 (epoch.era >> 24) as u8,
203 (epoch.era >> 32) as u8,
204 (epoch.era >> 40) as u8,
205 (epoch.era >> 48) as u8,
206 (epoch.era >> 56) as u8,
207 (epoch.rev >> 0) as u8,
208 (epoch.rev >> 8) as u8,
209 (epoch.rev >> 16) as u8,
210 (epoch.rev >> 24) as u8,
211 (epoch.rev >> 32) as u8,
212 (epoch.rev >> 40) as u8,
213 (epoch.rev >> 48) as u8,
214 (epoch.rev >> 56) as u8,
215 ]
216}
217
218fn array_to_epoch(ar: &[u8; 16]) -> Epoch {
219 Epoch {
220 era: ((ar[0] as u64) << 0)
221 | ((ar[1] as u64) << 8)
222 | ((ar[2] as u64) << 16)
223 | ((ar[3] as u64) << 24)
224 | ((ar[4] as u64) << 32)
225 | ((ar[5] as u64) << 40)
226 | ((ar[6] as u64) << 48)
227 | ((ar[7] as u64) << 56),
228 rev: ((ar[8] as u64) << 0)
229 | ((ar[9] as u64) << 8)
230 | ((ar[10] as u64) << 16)
231 | ((ar[11] as u64) << 24)
232 | ((ar[12] as u64) << 32)
233 | ((ar[13] as u64) << 40)
234 | ((ar[14] as u64) << 48)
235 | ((ar[15] as u64) << 56)
236 }
237}
238
239#[cfg(test)]
240mod test {
241 use tempdir::TempDir;
242 use super::*;
243
244 #[test]
245 fn smoke() {
246 let dir = TempDir::new("dirtyflock").unwrap();
247 let path = dir.path().join("flock");
248 let flock1 = DirtyFlock::new(&path);
249 let flock2 = DirtyFlock::new(&path);
250
251 assert_eq!(State::Dirty, flock1.try_lock_shared().unwrap());
252 assert!(flock1.unlock().is_ok());
253 assert_eq!(State::Clean, flock1.try_lock_shared().unwrap());
254 assert!(flock1.unlock().is_ok());
255 assert_eq!(State::Clean, flock1.try_lock_exclusive().unwrap());
256 assert!(flock1.unlock().is_ok());
257 assert_eq!(State::Clean, flock1.try_lock_exclusive().unwrap());
258 assert!(flock1.unlock().is_ok());
259 assert_eq!(State::Clean, flock1.try_lock_shared().unwrap());
260 assert!(flock1.unlock().is_ok());
261
262 assert_eq!(State::Dirty, flock2.try_lock_exclusive().unwrap());
263 assert!(flock2.unlock().is_ok());
264 assert_eq!(State::Dirty, flock1.try_lock_exclusive().unwrap());
265 assert!(flock1.unlock().is_ok());
266
267 assert_eq!(State::Dirty, flock2.try_lock_shared().unwrap());
268 assert!(flock2.unlock().is_ok());
269 assert_eq!(State::Clean, flock1.try_lock_exclusive().unwrap());
270 assert!(flock1.unlock().is_ok());
271
272 assert_eq!(State::Dirty, flock2.try_lock_shared().unwrap());
273 assert!(flock2.unlock().is_ok());
274 assert_eq!(State::Clean, flock1.try_lock_shared().unwrap());
275 assert!(flock1.unlock().is_ok());
276 assert_eq!(State::Clean, flock2.try_lock_shared().unwrap());
277 assert!(flock2.unlock().is_ok());
278 }
279
280 #[test]
281 fn drop_unlocked() {
282 let dir = TempDir::new("dirtyflock").unwrap();
283 let path = dir.path().join("flock");
284 let flock1 = DirtyFlock::new(&path);
285 let flock2 = DirtyFlock::new(&path);
286
287 assert_eq!(State::Dirty, flock1.try_lock_shared().unwrap());
288 assert!(flock1.unlock().is_ok());
289 drop(flock2); assert_eq!(State::Dirty, flock1.try_lock_shared().unwrap());
291 assert!(flock1.unlock().is_ok());
292 }
293
294 #[test]
295 fn drop_locked() {
296 let dir = TempDir::new("dirtyflock").unwrap();
297 let path = dir.path().join("flock");
298 let flock1 = DirtyFlock::new(&path);
299 let flock2 = DirtyFlock::new(&path);
300
301 assert_eq!(State::Dirty, flock1.try_lock_shared().unwrap());
302 drop(flock2); assert!(flock1.unlock().is_ok());
304 assert_eq!(State::Clean, flock1.try_lock_shared().unwrap());
305 assert!(flock1.unlock().is_ok());
306 }
307
308}
309
310
311