async_fd_lock/
lib.rs

1//! Advisory reader-writer locks for files.
2//!
3//! # Notes on Advisory Locks
4//!
5//! "advisory locks" are locks which programs must opt-in to adhere to. This
6//! means that they can be used to coordinate file access, but not prevent
7//! access. Use this to coordinate file access between multiple instances of the
8//! same program. But do not use this to prevent actors from accessing or
9//! modifying files.
10//!
11//! # Example
12//!
13//! ```
14//! use std::path::PathBuf;
15//! use tokio::fs::File;
16//! use tokio::io::{AsyncReadExt, AsyncWriteExt};
17//! use async_fd_lock::{LockRead, LockWrite};
18//!
19//! # tokio_test::block_on(async {
20//! let dir = tempfile::tempdir().unwrap();
21//! let path = dir.path().join("foo.txt");
22//!
23//! // Lock it for writing.
24//! {
25//!     let mut write_guard = File::options()
26//!         .create_new(true)
27//!         .write(true)
28//!         .truncate(true)
29//!         .open(&path).await?
30//!         .lock_write().await?;
31//!     write_guard.write(b"bongo cat").await?;
32//! }
33//!
34//! // Lock it for reading.
35//! {
36//!     let mut read_guard_1 = File::open(&path).await?.lock_read().await?;
37//!     let mut read_guard_2 = File::open(&path).await?.lock_read().await?;
38//!     let byte_1 = read_guard_1.read_u8().await?;
39//!     let byte_2 = read_guard_2.read_u8().await?;
40//! }
41//! # std::io::Result::Ok(())
42//! # }).unwrap();
43//! ```
44#![forbid(future_incompatible)]
45#![deny(missing_debug_implementations, nonstandard_style)]
46#![cfg_attr(doc, warn(missing_docs))]
47
48use sys::AsOpenFileExt;
49
50mod read_guard;
51mod write_guard;
52
53pub(crate) mod error;
54pub(crate) mod sys;
55
56pub use error::*;
57#[cfg(feature = "async")]
58pub use nonblocking::*;
59pub use read_guard::RwLockReadGuard;
60pub use sys::AsOpenFile;
61pub use write_guard::RwLockWriteGuard;
62
63pub mod blocking {
64    use super::*;
65
66    pub trait LockRead: AsOpenFile + std::io::Read {
67        fn lock_read(self) -> LockReadResult<Self>
68        where
69            Self: Sized;
70
71        fn try_lock_read(self) -> LockReadResult<Self>
72        where
73            Self: Sized;
74    }
75
76    pub trait LockWrite: AsOpenFile + std::io::Write {
77        fn lock_write(self) -> LockWriteResult<Self>
78        where
79            Self: Sized;
80
81        fn try_lock_write(self) -> LockWriteResult<Self>
82        where
83            Self: Sized;
84    }
85
86    impl<T> LockRead for T
87    where
88        T: AsOpenFile + std::io::Read,
89    {
90        fn lock_read(self) -> LockReadResult<Self> {
91            match self.acquire_lock_blocking::<false, true>() {
92                Ok(guard) => Ok(RwLockReadGuard::new(self, guard)),
93                Err(error) => Err(LockError::new(self, error)),
94            }
95        }
96
97        fn try_lock_read(self) -> LockReadResult<Self> {
98            match self.acquire_lock_blocking::<false, false>() {
99                Ok(guard) => Ok(RwLockReadGuard::new(self, guard)),
100                Err(error) => Err(LockError::new(self, error)),
101            }
102        }
103    }
104
105    impl<T> LockWrite for T
106    where
107        T: AsOpenFile + std::io::Write,
108    {
109        fn lock_write(self) -> LockWriteResult<Self> {
110            match self.acquire_lock_blocking::<true, true>() {
111                Ok(guard) => Ok(RwLockWriteGuard::new(self, guard)),
112                Err(error) => Err(LockError::new(self, error)),
113            }
114        }
115
116        fn try_lock_write(self) -> LockWriteResult<Self> {
117            match self.acquire_lock_blocking::<true, false>() {
118                Ok(guard) => Ok(RwLockWriteGuard::new(self, guard)),
119                Err(error) => Err(LockError::new(self, error)),
120            }
121        }
122    }
123}
124
125#[cfg(feature = "async")]
126pub mod nonblocking {
127    use super::*;
128    use async_trait::async_trait;
129    use std::io;
130    use sys::{AsOpenFileExt, RwLockGuard};
131
132    async fn lock<const WRITE: bool, const BLOCK: bool, T>(
133        file: &T,
134    ) -> Result<RwLockGuard<<T as AsOpenFileExt>::OwnedOpenFile>, io::Error>
135    where
136        T: AsOpenFile + Sync + 'static,
137    {
138        let handle = file.borrow_open_file().try_clone_to_owned()?;
139        let (sync_send, async_recv) = tokio::sync::oneshot::channel();
140        tokio::task::spawn_blocking(move || {
141            let guard = handle.acquire_lock_blocking::<WRITE, BLOCK>();
142            let result = sync_send.send(guard);
143            drop(result); // If the guard cannot be sent to the async task, release the lock immediately.
144        });
145        async_recv
146            .await
147            .expect("the blocking task is not cancelable")
148    }
149
150    #[async_trait]
151    pub trait LockRead: AsOpenFile + tokio::io::AsyncRead {
152        async fn lock_read(self) -> LockReadResult<Self>
153        where
154            Self: Sized;
155
156        async fn try_lock_read(self) -> LockReadResult<Self>
157        where
158            Self: Sized;
159    }
160
161    #[async_trait]
162    pub trait LockWrite: AsOpenFile + tokio::io::AsyncWrite {
163        async fn lock_write(self) -> LockWriteResult<Self>
164        where
165            Self: Sized;
166
167        async fn try_lock_write(self) -> LockWriteResult<Self>
168        where
169            Self: Sized;
170    }
171
172    #[async_trait]
173    impl<T> LockRead for T
174    where
175        T: AsOpenFile + tokio::io::AsyncRead + Send + Sync + 'static,
176    {
177        async fn lock_read(self) -> LockReadResult<Self> {
178            match lock::<false, true, _>(&self).await {
179                Ok(guard) => Ok(RwLockReadGuard::new(self, guard)),
180                Err(error) => Err(LockError::new(self, error)),
181            }
182        }
183
184        async fn try_lock_read(self) -> LockReadResult<Self> {
185            match lock::<false, false, _>(&self).await {
186                Ok(guard) => Ok(RwLockReadGuard::new(self, guard)),
187                Err(error) => Err(LockError::new(self, error)),
188            }
189        }
190    }
191
192    #[async_trait]
193    impl<T> LockWrite for T
194    where
195        T: AsOpenFile + tokio::io::AsyncWrite + Send + Sync + 'static,
196    {
197        async fn lock_write(self) -> LockWriteResult<Self> {
198            match lock::<true, true, _>(&self).await {
199                Ok(guard) => Ok(RwLockWriteGuard::new(self, guard)),
200                Err(error) => Err(LockError::new(self, error)),
201            }
202        }
203
204        async fn try_lock_write(self) -> LockWriteResult<Self> {
205            match lock::<true, false, _>(&self).await {
206                Ok(guard) => Ok(RwLockWriteGuard::new(self, guard)),
207                Err(error) => return Err(LockError::new(self, error)),
208            }
209        }
210    }
211}