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