1 //! Inter-process reader-writer lock builder.
3 //! This implemenation 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()`.
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
);
63 /// Lock guard for exclusive locks
65 /// Release the lock when it goes out of scope.
66 pub struct ProcessLockExclusiveGuard
{
67 locker
: Arc
<Mutex
<ProcessLocker
>>,
70 impl Drop
for ProcessLockExclusiveGuard
{
72 let mut data
= self.locker
.lock().unwrap();
74 if !data
.exclusive { panic!("unexpected ProcessLocker state"); }
76 let ltype
= if data
.writers
!= 0 { libc::F_RDLCK }
else { libc::F_UNLCK }
;
77 let op
= libc
::flock
{
79 l_whence
: libc
::SEEK_SET
as i16,
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
);
89 data
.exclusive
= false;
95 /// Create a new instance for the specified file.
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
> {
100 let file
= std
::fs
::OpenOptions
::new()
106 Ok(Arc
::new(Mutex
::new(Self {
111 shared_guard_list
: HashMap
::new(),
115 fn try_lock(file
: &std
::fs
::File
, ltype
: i32) -> Result
<(), Error
> {
117 let op
= libc
::flock
{
118 l_type
: ltype
as i16,
119 l_whence
: libc
::SEEK_SET
as i16,
125 nix
::fcntl
::fcntl(file
.as_raw_fd(), nix
::fcntl
::FcntlArg
::F_SETLK(&op
))?
;
130 /// Try to aquire a shared lock
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
> {
135 let mut data
= locker
.lock().unwrap();
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
);
145 let guard
= ProcessLockSharedGuard { locker: locker.clone(), guard_id: data.next_guard_id }
;
146 data
.next_guard_id
+= 1;
148 let now
= unsafe { libc::time(std::ptr::null_mut()) }
;
150 data
.shared_guard_list
.insert(guard
.guard_id
, now
);
155 /// Get oldest shared lock timestamp
156 pub fn oldest_shared_lock(locker
: Arc
<Mutex
<Self>>) -> Option
<i64> {
157 let mut result
= None
;
159 let data
= locker
.lock().unwrap();
161 for (_k
, v
) in &data
.shared_guard_list
{
162 result
= match result
{
164 Some(x
) => if x
< *v { Some(x) }
else { Some(*v) }
,
171 /// Try to aquire a exclusive lock
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
> {
176 let mut data
= locker
.lock().unwrap();
179 bail
!("already locked exclusively");
182 if let Err(err
) = Self::try_lock(&data
.file
, libc
::F_WRLCK
) {
183 bail
!("unable to get exclusive lock - {}", err
);
186 data
.exclusive
= true;
188 Ok(ProcessLockExclusiveGuard { locker: locker.clone() }
)