]>
Commit | Line | Data |
---|---|---|
a650f503 DM |
1 | //! Inter-process reader-writer lock builder. |
2 | //! | |
3 | //! This implemenation uses fcntl record locks with non-blocking | |
4 | //! F_SETLK command (never blocks). | |
5 | ||
6 | use failure::*; | |
7 | ||
8 | use std::sync::{Arc, Mutex}; | |
9 | use std::os::unix::io::AsRawFd; | |
10 | ||
11 | // fixme: use F_OFD_ locks when implemented with nix::fcntl | |
12 | ||
13 | // Note: flock lock conversion is not atomic, so we need to use fcntl | |
14 | ||
15 | /// Inter-process reader-writer lock | |
16 | pub struct ProcessLocker { | |
17 | file: std::fs::File, | |
18 | exclusive: bool, | |
19 | writers: usize, | |
20 | } | |
21 | ||
22 | /// Lock guard for shared locks | |
23 | /// | |
24 | /// Release the lock when it goes out of scope. | |
25 | pub struct ProcessLockSharedGuard { | |
26 | locker: Arc<Mutex<ProcessLocker>>, | |
27 | } | |
28 | ||
29 | impl Drop for ProcessLockSharedGuard { | |
30 | fn drop(&mut self) { | |
31 | let mut data = self.locker.lock().unwrap(); | |
32 | ||
33 | if data.writers == 0 { panic!("unexpected ProcessLocker state"); } | |
34 | ||
35 | if data.writers == 1 && !data.exclusive { | |
36 | ||
37 | let op = libc::flock { | |
38 | l_type: libc::F_UNLCK as i16, | |
39 | l_whence: libc::SEEK_SET as i16, | |
40 | l_start: 0, | |
41 | l_len: 0, | |
42 | l_pid: 0, | |
43 | }; | |
44 | ||
45 | if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) { | |
46 | panic!("unable to drop writer lock - {}", err); | |
47 | } | |
48 | data.writers = 0; | |
49 | } | |
50 | } | |
51 | } | |
52 | ||
53 | /// Lock guard for exclusive locks | |
54 | /// | |
55 | /// Release the lock when it goes out of scope. | |
56 | pub struct ProcessLockExclusiveGuard { | |
57 | locker: Arc<Mutex<ProcessLocker>>, | |
58 | } | |
59 | ||
60 | impl Drop for ProcessLockExclusiveGuard { | |
61 | fn drop(&mut self) { | |
62 | let mut data = self.locker.lock().unwrap(); | |
63 | ||
64 | if !data.exclusive { panic!("unexpected ProcessLocker state"); } | |
65 | ||
66 | let ltype = if data.writers != 0 { libc::F_RDLCK } else { libc::F_UNLCK }; | |
67 | let op = libc::flock { | |
68 | l_type: ltype as i16, | |
69 | l_whence: libc::SEEK_SET as i16, | |
70 | l_start: 0, | |
71 | l_len: 0, | |
72 | l_pid: 0, | |
73 | }; | |
74 | ||
75 | if let Err(err) = nix::fcntl::fcntl(data.file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLKW(&op)) { | |
76 | panic!("unable to drop exclusive lock - {}", err); | |
77 | } | |
78 | ||
79 | data.exclusive = false; | |
80 | } | |
81 | } | |
82 | ||
83 | impl ProcessLocker { | |
84 | ||
85 | /// Create a new instance for the specified file. | |
86 | /// | |
87 | /// This simply creates the file if it does not exist. | |
abfc001f | 88 | pub fn new<P: AsRef<std::path::Path>>(lockfile: P) -> Result<Arc<Mutex<Self>>, Error> { |
a650f503 DM |
89 | |
90 | let file = std::fs::OpenOptions::new() | |
91 | .create(true) | |
92 | .read(true) | |
93 | .write(true) | |
94 | .open(lockfile)?; | |
95 | ||
96 | Ok(Arc::new(Mutex::new(Self { | |
97 | file: file, | |
98 | exclusive: false, | |
99 | writers: 0, | |
100 | }))) | |
101 | } | |
102 | ||
103 | fn try_lock(file: &std::fs::File, ltype: i32) -> Result<(), Error> { | |
104 | ||
105 | let op = libc::flock { | |
106 | l_type: ltype as i16, | |
107 | l_whence: libc::SEEK_SET as i16, | |
108 | l_start: 0, | |
109 | l_len: 0, | |
110 | l_pid: 0, | |
111 | }; | |
112 | ||
113 | nix::fcntl::fcntl(file.as_raw_fd(), nix::fcntl::FcntlArg::F_SETLK(&op))?; | |
114 | ||
115 | Ok(()) | |
116 | } | |
117 | ||
118 | /// Try to aquire a shared lock | |
119 | /// | |
120 | /// On sucess, this makes sure that no other process can get an exclusive lock for the file. | |
121 | pub fn try_shared_lock(locker: Arc<Mutex<Self>>) -> Result<ProcessLockSharedGuard, Error> { | |
122 | ||
123 | let mut data = locker.lock().unwrap(); | |
124 | ||
125 | if data.writers == 0 && !data.exclusive { | |
126 | if let Err(err) = Self::try_lock(&data.file, libc::F_RDLCK) { | |
127 | bail!("unable to get shared lock - {}", err); | |
128 | } | |
129 | } | |
130 | ||
131 | data.writers += 1; | |
132 | ||
133 | Ok(ProcessLockSharedGuard { locker: locker.clone() }) | |
134 | } | |
135 | ||
136 | /// Try to aquire a exclusive lock | |
137 | /// | |
138 | /// Make sure the we are the only process which has locks for this file (shared or exclusive). | |
139 | pub fn try_exclusive_lock(locker: Arc<Mutex<Self>>) -> Result<ProcessLockExclusiveGuard, Error> { | |
140 | ||
141 | let mut data = locker.lock().unwrap(); | |
142 | ||
143 | if data.exclusive { | |
144 | bail!("already locked exclusively"); | |
145 | } | |
146 | ||
147 | if let Err(err) = Self::try_lock(&data.file, libc::F_WRLCK) { | |
148 | bail!("unable to get exclusive lock - {}", err); | |
149 | } | |
150 | ||
151 | data.exclusive = true; | |
152 | ||
153 | Ok(ProcessLockExclusiveGuard { locker: locker.clone() }) | |
154 | } | |
155 | } |