]> git.proxmox.com Git - proxmox-backup.git/blob - src/tools/process_locker.rs
f0ccc76d0f71b39df4659e425d7fc86241c06e25
[proxmox-backup.git] / src / tools / process_locker.rs
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 //! 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()`.
9
10 use failure::*;
11
12 use std::sync::{Arc, Mutex};
13 use std::os::unix::io::AsRawFd;
14 use std::collections::HashMap;
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,
25 next_guard_id: u64,
26 shared_guard_list: HashMap<u64, i64>, // guard_id => timestamp
27 }
28
29 /// Lock guard for shared locks
30 ///
31 /// Release the lock when it goes out of scope.
32 pub struct ProcessLockSharedGuard {
33 guard_id: u64,
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
43 data.shared_guard_list.remove(&self.guard_id);
44
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.
98 pub fn new<P: AsRef<std::path::Path>>(lockfile: P) -> Result<Arc<Mutex<Self>>, Error> {
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 {
107 file,
108 exclusive: false,
109 writers: 0,
110 next_guard_id: 0,
111 shared_guard_list: HashMap::new(),
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
130 /// Try to aquire a shared lock
131 ///
132 /// On sucess, this makes sure that no other process can get an exclusive lock for the file.
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
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
161 for v in data.shared_guard_list.values() {
162 result = match result {
163 None => Some(*v),
164 Some(x) => if x < *v { Some(x) } else { Some(*v) },
165 };
166 }
167
168 result
169 }
170
171 /// Try to aquire a exclusive lock
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 }