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 std
::collections
::HashMap
;
11 use std
::os
::unix
::io
::AsRawFd
;
12 use std
::sync
::{Arc, Mutex}
;
14 use anyhow
::{bail, Error}
;
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 {
42 panic
!("unexpected ProcessLocker state");
45 data
.shared_guard_list
.remove(&self.guard_id
);
47 if data
.writers
== 1 && !data
.exclusive
{
48 let op
= libc
::flock
{
49 l_type
: libc
::F_UNLCK
as i16,
50 l_whence
: libc
::SEEK_SET
as i16,
57 nix
::fcntl
::fcntl(data
.file
.as_raw_fd(), nix
::fcntl
::FcntlArg
::F_SETLKW(&op
))
59 panic
!("unable to drop writer lock - {}", err
);
68 /// Lock guard for exclusive locks
70 /// Release the lock when it goes out of scope.
71 pub struct ProcessLockExclusiveGuard
{
72 locker
: Arc
<Mutex
<ProcessLocker
>>,
75 impl Drop
for ProcessLockExclusiveGuard
{
77 let mut data
= self.locker
.lock().unwrap();
80 panic
!("unexpected ProcessLocker state");
83 let ltype
= if data
.writers
!= 0 {
88 let op
= libc
::flock
{
90 l_whence
: libc
::SEEK_SET
as i16,
97 nix
::fcntl
::fcntl(data
.file
.as_raw_fd(), nix
::fcntl
::FcntlArg
::F_SETLKW(&op
))
99 panic
!("unable to drop exclusive lock - {}", err
);
102 data
.exclusive
= false;
107 /// Create a new instance for the specified file.
109 /// This simply creates the file if it does not exist.
110 pub fn new
<P
: AsRef
<std
::path
::Path
>>(lockfile
: P
) -> Result
<Arc
<Mutex
<Self>>, Error
> {
111 let file
= std
::fs
::OpenOptions
::new()
117 Ok(Arc
::new(Mutex
::new(Self {
122 shared_guard_list
: HashMap
::new(),
126 fn try_lock(file
: &std
::fs
::File
, ltype
: i32) -> Result
<(), Error
> {
127 let op
= libc
::flock
{
128 l_type
: ltype
as i16,
129 l_whence
: libc
::SEEK_SET
as i16,
135 nix
::fcntl
::fcntl(file
.as_raw_fd(), nix
::fcntl
::FcntlArg
::F_SETLK(&op
))?
;
140 /// Try to acquire a shared lock
142 /// On success, this makes sure that no other process can get an exclusive lock for the file.
143 pub fn try_shared_lock(locker
: Arc
<Mutex
<Self>>) -> Result
<ProcessLockSharedGuard
, Error
> {
144 let mut data
= locker
.lock().unwrap();
146 if data
.writers
== 0 && !data
.exclusive
{
147 if let Err(err
) = Self::try_lock(&data
.file
, libc
::F_RDLCK
) {
148 bail
!("unable to get shared lock - {}", err
);
154 let guard
= ProcessLockSharedGuard
{
155 locker
: locker
.clone(),
156 guard_id
: data
.next_guard_id
,
158 data
.next_guard_id
+= 1;
160 let now
= unsafe { libc::time(std::ptr::null_mut()) }
;
162 data
.shared_guard_list
.insert(guard
.guard_id
, now
);
167 /// Get oldest shared lock timestamp
168 pub fn oldest_shared_lock(locker
: Arc
<Mutex
<Self>>) -> Option
<i64> {
169 let mut result
= None
;
171 let data
= locker
.lock().unwrap();
173 for v
in data
.shared_guard_list
.values() {
174 result
= match result
{
189 /// Try to acquire a exclusive lock
191 /// Make sure the we are the only process which has locks for this file (shared or exclusive).
192 pub fn try_exclusive_lock(
193 locker
: Arc
<Mutex
<Self>>,
194 ) -> Result
<ProcessLockExclusiveGuard
, Error
> {
195 let mut data
= locker
.lock().unwrap();
198 bail
!("already locked exclusively");
201 if let Err(err
) = Self::try_lock(&data
.file
, libc
::F_WRLCK
) {
202 bail
!("unable to get exclusive lock - {}", err
);
205 data
.exclusive
= true;
207 Ok(ProcessLockExclusiveGuard
{
208 locker
: locker
.clone(),