1 //! Inter-process reader-writer lock builder.
3 //! This implementation uses fcntl record locks with non-blocking
4 //! F_SETLK command (never blocks).
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()`.
10 use anyhow
::{bail, Error}
;
12 use std
::sync
::{Arc, Mutex}
;
13 use std
::os
::unix
::io
::AsRawFd
;
14 use std
::collections
::HashMap
;
16 // fixme: use F_OFD_ locks when implemented with nix::fcntl
18 // Note: flock lock conversion is not atomic, so we need to use fcntl
20 /// Inter-process reader-writer lock
21 pub struct ProcessLocker
{
26 shared_guard_list
: HashMap
<u64, i64>, // guard_id => timestamp
29 /// Lock guard for shared locks
31 /// Release the lock when it goes out of scope.
32 pub struct ProcessLockSharedGuard
{
34 locker
: Arc
<Mutex
<ProcessLocker
>>,
37 impl Drop
for ProcessLockSharedGuard
{
39 let mut data
= self.locker
.lock().unwrap();
41 if data
.writers
== 0 { panic!("unexpected ProcessLocker state"); }
43 data
.shared_guard_list
.remove(&self.guard_id
);
45 if data
.writers
== 1 && !data
.exclusive
{
47 let op
= libc
::flock
{
48 l_type
: libc
::F_UNLCK
as i16,
49 l_whence
: libc
::SEEK_SET
as i16,
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
);
65 /// Lock guard for exclusive locks
67 /// Release the lock when it goes out of scope.
68 pub struct ProcessLockExclusiveGuard
{
69 locker
: Arc
<Mutex
<ProcessLocker
>>,
72 impl Drop
for ProcessLockExclusiveGuard
{
74 let mut data
= self.locker
.lock().unwrap();
76 if !data
.exclusive { panic!("unexpected ProcessLocker state"); }
78 let ltype
= if data
.writers
!= 0 { libc::F_RDLCK }
else { libc::F_UNLCK }
;
79 let op
= libc
::flock
{
81 l_whence
: libc
::SEEK_SET
as i16,
87 if let Err(err
) = nix
::fcntl
::fcntl(data
.file
.as_raw_fd(), nix
::fcntl
::FcntlArg
::F_SETLKW(&op
)) {
88 panic
!("unable to drop exclusive lock - {}", err
);
91 data
.exclusive
= false;
97 /// Create a new instance for the specified file.
99 /// This simply creates the file if it does not exist.
100 pub fn new
<P
: AsRef
<std
::path
::Path
>>(lockfile
: P
) -> Result
<Arc
<Mutex
<Self>>, Error
> {
102 let file
= std
::fs
::OpenOptions
::new()
108 Ok(Arc
::new(Mutex
::new(Self {
113 shared_guard_list
: HashMap
::new(),
117 fn try_lock(file
: &std
::fs
::File
, ltype
: i32) -> Result
<(), Error
> {
119 let op
= libc
::flock
{
120 l_type
: ltype
as i16,
121 l_whence
: libc
::SEEK_SET
as i16,
127 nix
::fcntl
::fcntl(file
.as_raw_fd(), nix
::fcntl
::FcntlArg
::F_SETLK(&op
))?
;
132 /// Try to acquire a shared lock
134 /// On success, this makes sure that no other process can get an exclusive lock for the file.
135 pub fn try_shared_lock(locker
: Arc
<Mutex
<Self>>) -> Result
<ProcessLockSharedGuard
, Error
> {
137 let mut data
= locker
.lock().unwrap();
139 if data
.writers
== 0 && !data
.exclusive
{
140 if let Err(err
) = Self::try_lock(&data
.file
, libc
::F_RDLCK
) {
141 bail
!("unable to get shared lock - {}", err
);
147 let guard
= ProcessLockSharedGuard { locker: locker.clone(), guard_id: data.next_guard_id }
;
148 data
.next_guard_id
+= 1;
150 let now
= unsafe { libc::time(std::ptr::null_mut()) }
;
152 data
.shared_guard_list
.insert(guard
.guard_id
, now
);
157 /// Get oldest shared lock timestamp
158 pub fn oldest_shared_lock(locker
: Arc
<Mutex
<Self>>) -> Option
<i64> {
159 let mut result
= None
;
161 let data
= locker
.lock().unwrap();
163 for v
in data
.shared_guard_list
.values() {
164 result
= match result
{
166 Some(x
) => if x
< *v { Some(x) }
else { Some(*v) }
,
173 /// Try to acquire a exclusive lock
175 /// Make sure the we are the only process which has locks for this file (shared or exclusive).
176 pub fn try_exclusive_lock(locker
: Arc
<Mutex
<Self>>) -> Result
<ProcessLockExclusiveGuard
, Error
> {
178 let mut data
= locker
.lock().unwrap();
181 bail
!("already locked exclusively");
184 if let Err(err
) = Self::try_lock(&data
.file
, libc
::F_WRLCK
) {
185 bail
!("unable to get exclusive lock - {}", err
);
188 data
.exclusive
= true;
190 Ok(ProcessLockExclusiveGuard { locker: locker.clone() }
)