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}