advisory_lock/
lib.rs

1//! Advisory lock provides simple and convenient API for using file locks.
2//!
3//! These are called advisory because they don't prevent other processes from
4//! accessing the files directly, bypassing the locks.
5//! However, if multiple processes agree on acquiring file locks, they should
6//! work as expected.
7//!
8//! The main entity of the crate is [`AdvisoryFileLock`] which is effectively
9//! a [`RwLock`] but for [`File`].
10//!
11//! Example:
12//! ```
13//! use std::fs::File;
14//! use advisory_lock::{AdvisoryFileLock, FileLockMode, FileLockError};
15//! #
16//! #
17//! // Create the file and obtain its exclusive advisory lock
18//! let exclusive_file = File::create("foo.txt").unwrap();
19//! exclusive_file.lock(FileLockMode::Exclusive)?;
20//!
21//! let shared_file = File::open("foo.txt")?;
22//!
23//! // Try to acquire the lock in non-blocking way
24//! assert!(matches!(shared_file.try_lock(FileLockMode::Shared), Err(FileLockError::AlreadyLocked)));
25//!
26//! exclusive_file.unlock()?;
27//!
28//! shared_file.try_lock(FileLockMode::Shared).expect("Works, because the exclusive lock was released");
29//!
30//! let shared_file_2 = File::open("foo.txt")?;
31//!
32//! shared_file_2.lock(FileLockMode::Shared).expect("Should be fine to have multiple shared locks");
33//!
34//! // Nope, now we have to wait until all shared locks are released...
35//! assert!(matches!(exclusive_file.try_lock(FileLockMode::Exclusive), Err(FileLockError::AlreadyLocked)));
36//!
37//! // We can unlock them explicitly and handle the potential error
38//! shared_file.unlock()?;
39//! // Or drop the lock, such that we `log::error!()` if it happens and discard it
40//! drop(shared_file_2);
41//!
42//! exclusive_file.lock(FileLockMode::Exclusive).expect("All other locks should have been released");
43//! #
44//! # std::fs::remove_file("foo.txt")?;
45//! # Ok::<(), Box<dyn std::error::Error>>(())
46//! ```
47//!
48//! [`AdvisoryFileLock`]: struct.AdvisoryFileLock.html
49//! [`RwLock`]: https://doc.rust-lang.org/stable/std/sync/struct.RwLock.html
50//! [`File`]: https://doc.rust-lang.org/stable/std/fs/struct.File.html
51use std::{fmt, io};
52
53#[cfg(windows)]
54mod windows;
55
56#[cfg(unix)]
57mod unix;
58
59/// An enumeration of possible errors which can occur while trying to acquire a lock.
60#[derive(Debug)]
61pub enum FileLockError {
62    /// The file is already locked by other process.
63    AlreadyLocked,
64    /// The error occurred during I/O operations.
65    Io(io::Error),
66}
67
68impl fmt::Display for FileLockError {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            FileLockError::AlreadyLocked => f.write_str("the file is already locked"),
72            FileLockError::Io(err) => write!(f, "I/O error: {}", err),
73        }
74    }
75}
76
77impl std::error::Error for FileLockError {}
78
79/// An enumeration of types which represents how to acquire an advisory lock.
80#[derive(Copy, Clone, Eq, PartialEq, Debug)]
81pub enum FileLockMode {
82    /// Obtain an exclusive file lock.
83    Exclusive,
84    /// Obtain a shared file lock.
85    Shared,
86}
87
88/// An advisory lock for files.
89///
90/// An advisory lock provides a mutual-exclusion mechanism among processes which explicitly
91/// acquires and releases the lock. Processes that are not aware of the lock will ignore it.
92///
93/// `AdvisoryFileLock` provides following features:
94/// - Blocking or non-blocking operations.
95/// - Shared or exclusive modes.
96/// - All operations are thread-safe.
97///
98/// ## Notes
99///
100/// `AdvisoryFileLock` has following limitations:
101/// - Locks are allowed only on files, but not directories.
102pub trait AdvisoryFileLock {
103    /// Acquire the advisory file lock.
104    ///
105    /// `lock` is blocking; it will block the current thread until it succeeds or errors.
106    fn lock(&self, file_lock_mode: FileLockMode) -> Result<(), FileLockError>;
107    /// Try to acquire the advisory file lock.
108    ///
109    /// `try_lock` returns immediately.
110    fn try_lock(&self, file_lock_mode: FileLockMode) -> Result<(), FileLockError>;
111    /// Unlock this advisory file lock.
112    fn unlock(&self) -> Result<(), FileLockError>;
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::env::temp_dir;
119    use std::fs::File;
120
121    #[test]
122    fn simple_shared_lock() {
123        let mut test_file = temp_dir();
124        test_file.push("shared_lock");
125        File::create(&test_file).unwrap();
126        {
127            let f1 = File::open(&test_file).unwrap();
128            f1.lock(FileLockMode::Shared).unwrap();
129            let f2 = File::open(&test_file).unwrap();
130            f2.lock(FileLockMode::Shared).unwrap();
131        }
132        std::fs::remove_file(&test_file).unwrap();
133    }
134
135    #[test]
136    fn simple_exclusive_lock() {
137        let mut test_file = temp_dir();
138        test_file.push("exclusive_lock");
139        File::create(&test_file).unwrap();
140        {
141            let f1 = File::open(&test_file).unwrap();
142            f1.lock(FileLockMode::Exclusive).unwrap();
143            let f2 = File::open(&test_file).unwrap();
144            assert!(f2.try_lock(FileLockMode::Exclusive).is_err());
145        }
146        std::fs::remove_file(&test_file).unwrap();
147    }
148
149    #[test]
150    fn simple_shared_exclusive_lock() {
151        let mut test_file = temp_dir();
152        test_file.push("shared_exclusive_lock");
153        File::create(&test_file).unwrap();
154        {
155            let f1 = File::open(&test_file).unwrap();
156            f1.lock(FileLockMode::Shared).unwrap();
157            let f2 = File::open(&test_file).unwrap();
158            assert!(matches!(
159                f2.try_lock(FileLockMode::Exclusive),
160                Err(FileLockError::AlreadyLocked)
161            ));
162        }
163        std::fs::remove_file(&test_file).unwrap();
164    }
165
166    #[test]
167    fn simple_exclusive_shared_lock() {
168        let mut test_file = temp_dir();
169        test_file.push("exclusive_shared_lock");
170        File::create(&test_file).unwrap();
171        {
172            let f1 = File::open(&test_file).unwrap();
173            f1.lock(FileLockMode::Exclusive).unwrap();
174            let f2 = File::open(&test_file).unwrap();
175            assert!(f2.try_lock(FileLockMode::Shared).is_err());
176        }
177        std::fs::remove_file(&test_file).unwrap();
178    }
179}