Skip to main content

try_lock_portable_atomic/
lib.rs

1#![deny(missing_docs)]
2#![deny(missing_debug_implementations)]
3#![deny(warnings)]
4#![cfg_attr(not(test), no_std)]
5
6//! A light-weight lock guarded by an atomic boolean.
7//!
8//! Most efficient when contention is low, acquiring the lock is a single
9//! atomic swap, and releasing it just 1 more atomic swap.
10//!
11//! # Example
12//!
13//! ```
14//! use std::sync::Arc;
15//! use try_lock_portable_atomic::TryLock;
16//!
17//! // a thing we want to share
18//! struct Widget {
19//!     name: String,
20//! }
21//!
22//! // lock it up!
23//! let widget1 = Arc::new(TryLock::new(Widget {
24//!     name: "Spanner".into(),
25//! }));
26//!
27//! let widget2 = widget1.clone();
28//!
29//!
30//! // mutate the widget
31//! let mut locked = widget1.try_lock().expect("example isn't locked yet");
32//! locked.name.push_str(" Bundle");
33//!
34//! // hands off, buddy
35//! let not_locked = widget2.try_lock();
36//! assert!(not_locked.is_none(), "widget1 has the lock");
37//!
38//! // ok, you can have it
39//! drop(locked);
40//!
41//! let locked2 = widget2.try_lock().expect("widget1 lock is released");
42//!
43//! assert_eq!(locked2.name, "Spanner Bundle");
44//! ```
45
46#[cfg(test)]
47extern crate core;
48
49#[cfg(feature = "portable-atomic")]
50extern crate portable_atomic;
51
52use core::cell::UnsafeCell;
53use core::fmt;
54use core::ops::{Deref, DerefMut};
55#[cfg(feature = "portable-atomic")]
56use portable_atomic::AtomicBool;
57#[cfg(not(feature = "portable-atomic"))]
58use core::sync::atomic::AtomicBool;
59use core::sync::atomic::Ordering;
60use core::marker::PhantomData;
61
62/// A light-weight lock guarded by an atomic boolean.
63///
64/// Most efficient when contention is low, acquiring the lock is a single
65/// atomic swap, and releasing it just 1 more atomic swap.
66///
67/// It is only possible to try to acquire the lock, it is not possible to
68/// wait for the lock to become ready, like with a `Mutex`.
69#[derive(Default)]
70pub struct TryLock<T> {
71    is_locked: AtomicBool,
72    value: UnsafeCell<T>,
73}
74
75impl<T> TryLock<T> {
76    /// Create a `TryLock` around the value.
77    #[inline]
78    pub const fn new(val: T) -> TryLock<T> {
79        TryLock {
80            is_locked: AtomicBool::new(false),
81            value: UnsafeCell::new(val),
82        }
83    }
84
85    /// Try to acquire the lock of this value.
86    ///
87    /// If the lock is already acquired by someone else, this returns
88    /// `None`. You can try to acquire again whenever you want, perhaps
89    /// by spinning a few times, or by using some other means of
90    /// notification.
91    ///
92    /// # Note
93    ///
94    /// The default memory ordering is to use `Acquire` to lock, and `Release`
95    /// to unlock. If different ordering is required, use
96    /// [`try_lock_explicit`](TryLock::try_lock_explicit) or
97    /// [`try_lock_explicit_unchecked`](TryLock::try_lock_explicit_unchecked).
98    #[inline]
99    pub fn try_lock(&self) -> Option<Locked<'_, T>> {
100        unsafe {
101            self.try_lock_explicit_unchecked(Ordering::Acquire, Ordering::Release)
102        }
103    }
104
105    /// Try to acquire the lock of this value using the lock and unlock orderings.
106    ///
107    /// If the lock is already acquired by someone else, this returns
108    /// `None`. You can try to acquire again whenever you want, perhaps
109    /// by spinning a few times, or by using some other means of
110    /// notification.
111    #[inline]
112    #[deprecated(
113        since = "0.2.3",
114        note = "This method is actually unsafe because it unsafely allows \
115        the use of weaker memory ordering. Please use try_lock_explicit instead"
116    )]
117    pub fn try_lock_order(&self, lock_order: Ordering, unlock_order: Ordering) -> Option<Locked<'_, T>> {
118        unsafe {
119            self.try_lock_explicit_unchecked(lock_order, unlock_order)
120        }
121    }
122
123    /// Try to acquire the lock of this value using the specified lock and
124    /// unlock orderings.
125    ///
126    /// If the lock is already acquired by someone else, this returns
127    /// `None`. You can try to acquire again whenever you want, perhaps
128    /// by spinning a few times, or by using some other means of
129    /// notification.
130    ///
131    /// # Panic
132    ///
133    /// This method panics if `lock_order` is not any of `Acquire`, `AcqRel`,
134    /// and `SeqCst`, or `unlock_order` is not any of `Release` and `SeqCst`.
135    #[inline]
136    pub fn try_lock_explicit(&self, lock_order: Ordering, unlock_order: Ordering) -> Option<Locked<'_, T>> {
137        match lock_order {
138            Ordering::Acquire |
139            Ordering::AcqRel |
140            Ordering::SeqCst => {}
141            _ => panic!("lock ordering must be `Acquire`, `AcqRel`, or `SeqCst`"),
142        }
143
144        match unlock_order {
145            Ordering::Release |
146            Ordering::SeqCst => {}
147            _ => panic!("unlock ordering must be `Release` or `SeqCst`"),
148        }
149
150        unsafe {
151            self.try_lock_explicit_unchecked(lock_order, unlock_order)
152        }
153    }
154
155    /// Try to acquire the lock of this value using the specified lock and
156    /// unlock orderings without checking that the specified orderings are
157    /// strong enough to be safe.
158    ///
159    /// If the lock is already acquired by someone else, this returns
160    /// `None`. You can try to acquire again whenever you want, perhaps
161    /// by spinning a few times, or by using some other means of
162    /// notification.
163    ///
164    /// # Safety
165    ///
166    /// Unlike [`try_lock_explicit`], this method is unsafe because it does not
167    /// check that the given memory orderings are strong enough to prevent data
168    /// race.
169    ///
170    /// [`try_lock_explicit`]: Self::try_lock_explicit
171    #[inline]
172    pub unsafe fn try_lock_explicit_unchecked(&self, lock_order: Ordering, unlock_order: Ordering) -> Option<Locked<'_, T>> {
173        if !self.is_locked.swap(true, lock_order) {
174            Some(Locked {
175                lock: self,
176                order: unlock_order,
177                _p: PhantomData,
178            })
179        } else {
180            None
181        }
182    }
183
184    /// Take the value back out of the lock when this is the sole owner.
185    #[inline]
186    pub fn into_inner(self) -> T {
187        debug_assert!(!self.is_locked.load(Ordering::Relaxed), "TryLock was mem::forgotten");
188        self.value.into_inner()
189    }
190}
191
192unsafe impl<T: Send> Send for TryLock<T> {}
193unsafe impl<T: Send> Sync for TryLock<T> {}
194
195impl<T: fmt::Debug> fmt::Debug for TryLock<T> {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197
198        // Used if the TryLock cannot acquire the lock.
199        struct LockedPlaceholder;
200
201        impl fmt::Debug for LockedPlaceholder {
202            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203                f.write_str("<locked>")
204            }
205        }
206
207        let mut builder = f.debug_struct("TryLock");
208        if let Some(locked) = self.try_lock() {
209            builder.field("value", &*locked);
210        } else {
211            builder.field("value", &LockedPlaceholder);
212        }
213        builder.finish()
214    }
215}
216
217/// A locked value acquired from a `TryLock`.
218///
219/// The type represents an exclusive view at the underlying value. The lock is
220/// released when this type is dropped.
221///
222/// This type derefs to the underlying value.
223#[must_use = "TryLock will immediately unlock if not used"]
224pub struct Locked<'a, T: 'a> {
225    lock: &'a TryLock<T>,
226    order: Ordering,
227    /// Suppresses Send and Sync autotraits for `struct Locked`.
228    _p: PhantomData<*mut T>,
229}
230
231impl<'a, T> Deref for Locked<'a, T> {
232    type Target = T;
233    #[inline]
234    fn deref(&self) -> &T {
235        unsafe { &*self.lock.value.get() }
236    }
237}
238
239impl<'a, T> DerefMut for Locked<'a, T> {
240    #[inline]
241    fn deref_mut(&mut self) -> &mut T {
242        unsafe { &mut *self.lock.value.get() }
243    }
244}
245
246impl<'a, T> Drop for Locked<'a, T> {
247    #[inline]
248    fn drop(&mut self) {
249        self.lock.is_locked.store(false, self.order);
250    }
251}
252
253impl<'a, T: fmt::Debug> fmt::Debug for Locked<'a, T> {
254    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
255        fmt::Debug::fmt(&**self, f)
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::TryLock;
262
263    #[test]
264    fn fmt_debug() {
265        let lock = TryLock::new(5);
266        assert_eq!(format!("{:?}", lock), "TryLock { value: 5 }");
267
268        let locked = lock.try_lock().unwrap();
269        assert_eq!(format!("{:?}", locked), "5");
270
271        assert_eq!(format!("{:?}", lock), "TryLock { value: <locked> }");
272    }
273}