fancy_flocks/
dirty_flock.rs

1/// A self-deleting flock that tracks whether a protected value needs
2/// to be refreshed
3
4use 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, // Randomly initialized
17    rev: u64, // Randomly iniitalized, incremented on write
18}
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        // TODO assert exclusive lock
93        let epoch = Epoch { era: random(), rev: random() };
94        self.write_epoch(epoch)?;
95        Ok(())
96    }
97
98    fn bump_epoch(&self) -> Result<()> {
99        // TODO assert exclusive lock
100        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        // TODO assert lock
109        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        // TODO assert exclusive lock
120        {
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 there's no epoch, create one
138            if self.path_file_size()? == 0 {
139                // Take the exclusive lock so we can write the epoch.
140                // If we can't get the lock we've raced and somebody
141                // else will create the epoch.
142                if self.0.try_lock_exclusive().is_ok() {
143                    match self.file_size() {
144                        Ok(sz) if sz == 0 => {
145                            // Still need to create the epoch
146                            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                            // Somebody else has created the epoch
153                        }
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                    // We raced on deleting the lockfile. Try again
185                    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); // Deletes the unlocked flock
290        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); // Doesn't delete the locked flock
303        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