maybe_fut/api/sync/
rwlock.rs

1mod read_guard;
2mod write_guard;
3
4pub use self::read_guard::RwLockReadGuard;
5pub use self::write_guard::RwLockWriteGuard;
6use crate::maybe_fut_constructor_sync;
7
8/// A reader-writer lock.
9///
10/// This type of lock allows a number of readers or at most one writer at any point in time.
11/// The write portion of this lock typically allows modification of the underlying data (exclusive access)
12/// and the read portion of this lock typically allows for read-only access (shared access).
13#[derive(Debug, Unwrap)]
14#[unwrap_types(
15    std(std::sync::RwLock),
16    tokio(tokio::sync::RwLock),
17    tokio_gated("tokio-sync")
18)]
19pub struct RwLock<T>(RwLockInner<T>)
20where
21    T: Sized;
22
23#[derive(Debug)]
24enum RwLockInner<T: Sized> {
25    Std(std::sync::RwLock<T>),
26    #[cfg(tokio_sync)]
27    #[cfg_attr(docsrs, doc(cfg(feature = "tokio-sync")))]
28    Tokio(tokio::sync::RwLock<T>),
29}
30
31impl<T> From<std::sync::RwLock<T>> for RwLock<T>
32where
33    T: Sized,
34{
35    fn from(rwlock: std::sync::RwLock<T>) -> Self {
36        RwLock(RwLockInner::Std(rwlock))
37    }
38}
39
40#[cfg(tokio_sync)]
41#[cfg_attr(docsrs, doc(cfg(feature = "tokio-sync")))]
42impl<T> From<tokio::sync::RwLock<T>> for RwLock<T> {
43    fn from(rwlock: tokio::sync::RwLock<T>) -> Self {
44        RwLock(RwLockInner::Tokio(rwlock))
45    }
46}
47
48impl<T> RwLock<T>
49where
50    T: Sized,
51{
52    maybe_fut_constructor_sync!(
53        /// Creates a new instance of an [`RwLock`] which is unlocked.
54        new(t: T) -> Self,
55        std::sync::RwLock::new,
56        tokio::sync::RwLock::new,
57        tokio_sync
58    );
59
60    /// Clear the poisoned state from a read-write lock.
61    ///
62    /// If the lock is poisoned, it will remain poisoned until this function is called.
63    /// This allows recovering from a poisoned state and marking that it has recovered.
64    /// For example, if the value is overwritten by a known-good value, then the lock can be marked as un-poisoned.
65    ///
66    /// If the inner lock is a Tokio lock, this function will do nothing.
67    pub fn clear_poison(&self) {
68        #[allow(irrefutable_let_patterns)]
69        if let RwLockInner::Std(lock) = &self.0 {
70            lock.clear_poison();
71        }
72    }
73
74    /// Returns `true` if the lock is poisoned.
75    pub fn is_poisoned(&self) -> bool {
76        match &self.0 {
77            RwLockInner::Std(lock) => lock.is_poisoned(),
78            #[cfg(tokio_sync)]
79            RwLockInner::Tokio(_) => false, // Tokio locks are not poisoned
80        }
81    }
82
83    /// Locks this RwLock with shared read access, blocking the current thread until it can be acquired.
84    pub async fn read(
85        &self,
86    ) -> Result<RwLockReadGuard<'_, T>, std::sync::PoisonError<std::sync::RwLockReadGuard<'_, T>>>
87    {
88        match &self.0 {
89            RwLockInner::Std(lock) => Ok(RwLockReadGuard::from(lock.read()?)),
90            #[cfg(tokio_sync)]
91            RwLockInner::Tokio(lock) => Ok(RwLockReadGuard::from(lock.read().await)),
92        }
93    }
94
95    /// Attempts to lock this RwLock with shared read access, returning immediately if it cannot be acquired.
96    pub async fn try_read(
97        &self,
98    ) -> Result<RwLockReadGuard<'_, T>, std::sync::TryLockError<std::sync::RwLockReadGuard<'_, T>>>
99    {
100        match &self.0 {
101            RwLockInner::Std(lock) => Ok(RwLockReadGuard::from(lock.try_read()?)),
102            #[cfg(tokio_sync)]
103            RwLockInner::Tokio(lock) => Ok(RwLockReadGuard::from(
104                lock.try_read()
105                    .map_err(|_| std::sync::TryLockError::WouldBlock)?,
106            )),
107        }
108    }
109
110    /// Locks this RwLock with exclusive write access, blocking the current thread until it can be acquired.
111    pub async fn write(
112        &self,
113    ) -> Result<RwLockWriteGuard<'_, T>, std::sync::PoisonError<std::sync::RwLockWriteGuard<'_, T>>>
114    {
115        match &self.0 {
116            RwLockInner::Std(lock) => Ok(RwLockWriteGuard::from(lock.write()?)),
117            #[cfg(tokio_sync)]
118            RwLockInner::Tokio(lock) => Ok(RwLockWriteGuard::from(lock.write().await)),
119        }
120    }
121
122    /// Attempts to lock this RwLock with exclusive write access, returning immediately if it cannot be acquired.
123    pub async fn try_write(
124        &self,
125    ) -> Result<RwLockWriteGuard<'_, T>, std::sync::TryLockError<std::sync::RwLockWriteGuard<'_, T>>>
126    {
127        match &self.0 {
128            RwLockInner::Std(lock) => Ok(RwLockWriteGuard::from(lock.try_write()?)),
129            #[cfg(tokio_sync)]
130            RwLockInner::Tokio(lock) => Ok(RwLockWriteGuard::from(
131                lock.try_write()
132                    .map_err(|_| std::sync::TryLockError::WouldBlock)?,
133            )),
134        }
135    }
136}
137
138impl<T> From<T> for RwLock<T> {
139    fn from(t: T) -> Self {
140        RwLock::new(t)
141    }
142}
143
144impl<T> Default for RwLock<T>
145where
146    T: Default,
147{
148    fn default() -> Self {
149        RwLock::new(T::default())
150    }
151}
152
153#[cfg(test)]
154mod test {
155
156    use super::*;
157    use crate::SyncRuntime;
158
159    #[test]
160    fn test_rwlock_default_sync() {
161        let rwlock: RwLock<i32> = RwLock::default();
162        assert!(matches!(rwlock.0, RwLockInner::Std(_)));
163    }
164
165    #[cfg(tokio_sync)]
166    #[tokio::test]
167    async fn test_rwlock_default_tokio() {
168        let rwlock: RwLock<i32> = RwLock::default();
169        assert!(matches!(rwlock.0, RwLockInner::Tokio(_)));
170    }
171
172    #[test]
173    fn test_rwlock_from_sync() {
174        let std_rwlock = std::sync::RwLock::new(42);
175        let rwlock: RwLock<i32> = RwLock::from(std_rwlock);
176        assert!(matches!(rwlock.0, RwLockInner::Std(_)));
177    }
178
179    #[cfg(tokio_sync)]
180    #[tokio::test]
181    async fn test_rwlock_from_tokio() {
182        let tokio_rwlock = tokio::sync::RwLock::new(42);
183        let rwlock: RwLock<i32> = RwLock::from(tokio_rwlock);
184        assert!(matches!(rwlock.0, RwLockInner::Tokio(_)));
185    }
186
187    #[test]
188    fn test_rwlock_new_sync() {
189        let rwlock = RwLock::new(42);
190        assert!(matches!(rwlock.0, RwLockInner::Std(_)));
191    }
192
193    #[cfg(tokio_sync)]
194    #[tokio::test]
195    async fn test_rwlock_new_tokio() {
196        let rwlock = RwLock::new(42);
197        assert!(matches!(rwlock.0, RwLockInner::Tokio(_)));
198    }
199
200    #[test]
201    fn test_rwlock_clear_poison() {
202        let rwlock = RwLock::new(42);
203        assert!(!rwlock.is_poisoned());
204        rwlock.clear_poison();
205        assert!(!rwlock.is_poisoned());
206    }
207
208    #[test]
209    fn test_rwlock_read() {
210        let rwlock = RwLock::new(42);
211        let read_guard = SyncRuntime::block_on(rwlock.read()).unwrap();
212        assert_eq!(*read_guard, 42);
213    }
214
215    #[cfg(tokio_sync)]
216    #[tokio::test]
217    async fn test_rwlock_read_tokio() {
218        let rwlock = RwLock::new(42);
219        let read_guard = rwlock.read().await.unwrap();
220        assert_eq!(*read_guard, 42);
221    }
222
223    #[test]
224    fn test_rwlock_try_read() {
225        let rwlock = RwLock::new(42);
226        let read_guard = SyncRuntime::block_on(rwlock.try_read()).unwrap();
227        assert_eq!(*read_guard, 42);
228    }
229
230    #[cfg(tokio_sync)]
231    #[tokio::test]
232    async fn test_rwlock_try_read_tokio() {
233        let rwlock = RwLock::new(42);
234        let read_guard = rwlock.try_read().await.unwrap();
235        assert_eq!(*read_guard, 42);
236    }
237
238    #[test]
239    fn test_rwlock_write() {
240        let rwlock = RwLock::new(42);
241        let mut write_guard = SyncRuntime::block_on(rwlock.write()).unwrap();
242        *write_guard = 43;
243        assert_eq!(*write_guard, 43);
244
245        // Test that the lock is still held after the write
246        drop(write_guard);
247        let read_guard = SyncRuntime::block_on(rwlock.read()).unwrap();
248        assert_eq!(*read_guard, 43);
249    }
250
251    #[cfg(tokio_sync)]
252    #[tokio::test]
253    async fn test_rwlock_write_tokio() {
254        let rwlock = RwLock::new(42);
255        let mut write_guard = rwlock.write().await.unwrap();
256        *write_guard = 43;
257        assert_eq!(*write_guard, 43);
258
259        // Test that the lock is still held after the write
260        drop(write_guard);
261        let read_guard = rwlock.read().await.unwrap();
262        assert_eq!(*read_guard, 43);
263    }
264
265    #[test]
266    fn test_rwlock_try_write() {
267        let rwlock = RwLock::new(42);
268        let mut write_guard = SyncRuntime::block_on(rwlock.try_write()).unwrap();
269        *write_guard = 43;
270        assert_eq!(*write_guard, 43);
271
272        // Test that the lock is still held after the write
273        drop(write_guard);
274        let read_guard = SyncRuntime::block_on(rwlock.read()).unwrap();
275        assert_eq!(*read_guard, 43);
276    }
277
278    #[cfg(tokio_sync)]
279    #[tokio::test]
280    async fn test_rwlock_try_write_tokio() {
281        let rwlock = RwLock::new(42);
282        let mut write_guard = rwlock.try_write().await.unwrap();
283        *write_guard = 43;
284        assert_eq!(*write_guard, 43);
285
286        // Test that the lock is still held after the write
287        drop(write_guard);
288        let read_guard = rwlock.read().await.unwrap();
289        assert_eq!(*read_guard, 43);
290    }
291}