cargo / async-lock / audit
cargo : async-lock @ 3.4.2
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

src/mutex.rs

763 lines · rust

use core::borrow::Borrow;use core::cell::UnsafeCell;use core::fmt;use core::marker::{PhantomData, PhantomPinned};use core::ops::{Deref, DerefMut};use core::pin::Pin;use core::task::{ready, Poll};use alloc::sync::Arc;// We don't use loom::UnsafeCell as that doesn't work with the Mutex API.use crate::sync::atomic::{AtomicUsize, Ordering};#[cfg(all(feature = "std", not(target_family = "wasm")))]use std::time::{Duration, Instant};use event_listener::{Event, EventListener};use event_listener_strategy::{easy_wrapper, EventListenerFuture};/// An async mutex.////// The locking mechanism uses eventual fairness to ensure locking will be fair on average without/// sacrificing performance. This is done by forcing a fair lock whenever a lock operation is/// starved for longer than 0.5 milliseconds.////// # Examples////// ```/// # futures_lite::future::block_on(async {/// use async_lock::Mutex;////// let m = Mutex::new(1);////// let mut guard = m.lock().await;/// *guard = 2;////// assert!(m.try_lock().is_none());/// drop(guard);/// assert_eq!(*m.try_lock().unwrap(), 2);/// # })/// ```pub struct Mutex<T: ?Sized> {    /// Current state of the mutex.    ///    /// The least significant bit is set to 1 if the mutex is locked.    /// The other bits hold the number of starved lock operations.    state: AtomicUsize,    /// Lock operations waiting for the mutex to be released.    lock_ops: Event,    /// The value inside the mutex.    data: UnsafeCell<T>,}unsafe impl<T: Send + ?Sized> Send for Mutex<T> {}unsafe impl<T: Send + ?Sized> Sync for Mutex<T> {}impl<T> Mutex<T> {    const_fn! {        const_if: #[cfg(not(loom))];        /// Creates a new async mutex.        ///        /// # Examples        ///        /// ```        /// use async_lock::Mutex;        ///        /// let mutex = Mutex::new(0);        /// ```        pub const fn new(data: T) -> Mutex<T> {            Mutex {                state: AtomicUsize::new(0),                lock_ops: Event::new(),                data: UnsafeCell::new(data),            }        }    }    /// Consumes the mutex, returning the underlying data.    ///    /// # Examples    ///    /// ```    /// use async_lock::Mutex;    ///    /// let mutex = Mutex::new(10);    /// assert_eq!(mutex.into_inner(), 10);    /// ```    pub fn into_inner(self) -> T {        self.data.into_inner()    }}impl<T: ?Sized> Mutex<T> {    /// Acquires the mutex.    ///    /// Returns a guard that releases the mutex when dropped.    ///    /// # Examples    ///    /// ```    /// # futures_lite::future::block_on(async {    /// use async_lock::Mutex;    ///    /// let mutex = Mutex::new(10);    /// let guard = mutex.lock().await;    /// assert_eq!(*guard, 10);    /// # })    /// ```    #[inline]    pub fn lock(&self) -> Lock<'_, T> {        Lock::_new(LockInner {            mutex: self,            acquire_slow: None,        })    }    /// Acquires the mutex using the blocking strategy.    ///    /// Returns a guard that releases the mutex when dropped.    ///    /// # Blocking    ///    /// Rather than using asynchronous waiting, like the [`lock`][Mutex::lock] method,    /// this method will block the current thread until the lock is acquired.    ///    /// This method should not be used in an asynchronous context. It is intended to be    /// used in a way that a mutex can be used in both asynchronous and synchronous contexts.    /// Calling this method in an asynchronous context may result in a deadlock.    ///    /// # Examples    ///    /// ```    /// use async_lock::Mutex;    ///    /// let mutex = Mutex::new(10);    /// let guard = mutex.lock_blocking();    /// assert_eq!(*guard, 10);    /// ```    #[cfg(all(feature = "std", not(target_family = "wasm")))]    #[inline]    pub fn lock_blocking(&self) -> MutexGuard<'_, T> {        self.lock().wait()    }    /// Attempts to acquire the mutex.    ///    /// If the mutex could not be acquired at this time, then [`None`] is returned. Otherwise, a    /// guard is returned that releases the mutex when dropped.    ///    /// # Examples    ///    /// ```    /// use async_lock::Mutex;    ///    /// let mutex = Mutex::new(10);    /// if let Some(guard) = mutex.try_lock() {    ///     assert_eq!(*guard, 10);    /// }    /// # ;    /// ```    #[inline]    pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {        if self            .state            .compare_exchange(0, 1, Ordering::Acquire, Ordering::Acquire)            .is_ok()        {            Some(MutexGuard(self))        } else {            None        }    }    /// Returns a mutable reference to the underlying data.    ///    /// Since this call borrows the mutex mutably, no actual locking takes place -- the mutable    /// borrow statically guarantees the mutex is not already acquired.    ///    /// # Examples    ///    /// ```    /// # futures_lite::future::block_on(async {    /// use async_lock::Mutex;    ///    /// let mut mutex = Mutex::new(0);    /// *mutex.get_mut() = 10;    /// assert_eq!(*mutex.lock().await, 10);    /// # })    /// ```    pub fn get_mut(&mut self) -> &mut T {        self.data.get_mut()    }    /// Unlocks the mutex directly.    ///    /// # Safety    ///    /// This function is intended to be used only in the case where the mutex is locked,    /// and the guard is subsequently forgotten. Calling this while you don't hold a lock    /// on the mutex will likely lead to UB.    pub(crate) unsafe fn unlock_unchecked(&self) {        // Remove the last bit and notify a waiting lock operation.        self.state.fetch_sub(1, Ordering::Release);        self.lock_ops.notify(1);    }}impl<T: ?Sized> Mutex<T> {    /// Acquires the mutex and clones a reference to it.    ///    /// Returns an owned guard that releases the mutex when dropped.    ///    /// # Examples    ///    /// ```    /// # futures_lite::future::block_on(async {    /// use async_lock::Mutex;    /// use std::sync::Arc;    ///    /// let mutex = Arc::new(Mutex::new(10));    /// let guard = mutex.lock_arc().await;    /// assert_eq!(*guard, 10);    /// # })    /// ```    #[inline]    pub fn lock_arc(self: &Arc<Self>) -> LockArc<T> {        LockArc::_new(LockArcInnards::Unpolled {            mutex: Some(self.clone()),        })    }    /// Acquires the mutex and clones a reference to it using the blocking strategy.    ///    /// Returns an owned guard that releases the mutex when dropped.    ///    /// # Blocking    ///    /// Rather than using asynchronous waiting, like the [`lock_arc`][Mutex::lock_arc] method,    /// this method will block the current thread until the lock is acquired.    ///    /// This method should not be used in an asynchronous context. It is intended to be    /// used in a way that a mutex can be used in both asynchronous and synchronous contexts.    /// Calling this method in an asynchronous context may result in a deadlock.    ///    /// # Examples    ///    /// ```    /// use async_lock::Mutex;    /// use std::sync::Arc;    ///    /// let mutex = Arc::new(Mutex::new(10));    /// let guard = mutex.lock_arc_blocking();    /// assert_eq!(*guard, 10);    /// ```    #[cfg(all(feature = "std", not(target_family = "wasm")))]    #[inline]    pub fn lock_arc_blocking(self: &Arc<Self>) -> MutexGuardArc<T> {        self.lock_arc().wait()    }    /// Attempts to acquire the mutex and clone a reference to it.    ///    /// If the mutex could not be acquired at this time, then [`None`] is returned. Otherwise, an    /// owned guard is returned that releases the mutex when dropped.    ///    /// # Examples    ///    /// ```    /// use async_lock::Mutex;    /// use std::sync::Arc;    ///    /// let mutex = Arc::new(Mutex::new(10));    /// if let Some(guard) = mutex.try_lock() {    ///     assert_eq!(*guard, 10);    /// }    /// # ;    /// ```    #[inline]    pub fn try_lock_arc(self: &Arc<Self>) -> Option<MutexGuardArc<T>> {        if self            .state            .compare_exchange(0, 1, Ordering::Acquire, Ordering::Acquire)            .is_ok()        {            Some(MutexGuardArc(self.clone()))        } else {            None        }    }}impl<T: fmt::Debug + ?Sized> fmt::Debug for Mutex<T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        struct Locked;        impl fmt::Debug for Locked {            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {                f.write_str("<locked>")            }        }        match self.try_lock() {            None => f.debug_struct("Mutex").field("data", &Locked).finish(),            Some(guard) => f.debug_struct("Mutex").field("data", &&*guard).finish(),        }    }}impl<T> From<T> for Mutex<T> {    fn from(val: T) -> Mutex<T> {        Mutex::new(val)    }}impl<T: Default> Default for Mutex<T> {    fn default() -> Mutex<T> {        Mutex::new(Default::default())    }}easy_wrapper! {    /// The future returned by [`Mutex::lock`].    pub struct Lock<'a, T: ?Sized>(LockInner<'a, T> => MutexGuard<'a, T>);    #[cfg(all(feature = "std", not(target_family = "wasm")))]    pub(crate) wait();}pin_project_lite::pin_project! {    /// Inner future for acquiring the mutex.    struct LockInner<'a, T: ?Sized> {        // Reference to the mutex.        mutex: &'a Mutex<T>,        // The future that waits for the mutex to become available.        #[pin]        acquire_slow: Option<AcquireSlow<&'a Mutex<T>, T>>,    }}unsafe impl<T: Send + ?Sized> Send for Lock<'_, T> {}unsafe impl<T: Sync + ?Sized> Sync for Lock<'_, T> {}impl<T: ?Sized> fmt::Debug for Lock<'_, T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        f.write_str("Lock { .. }")    }}impl<'a, T: ?Sized> EventListenerFuture for LockInner<'a, T> {    type Output = MutexGuard<'a, T>;    #[inline]    fn poll_with_strategy<'x, S: event_listener_strategy::Strategy<'x>>(        self: Pin<&mut Self>,        strategy: &mut S,        context: &mut S::Context,    ) -> Poll<Self::Output> {        let mut this = self.project();        // This may seem weird, but the borrow checker complains otherwise.        if this.acquire_slow.is_none() {            match this.mutex.try_lock() {                Some(guard) => return Poll::Ready(guard),                None => {                    this.acquire_slow.set(Some(AcquireSlow::new(this.mutex)));                }            }        }        ready!(this            .acquire_slow            .as_pin_mut()            .unwrap()            .poll_with_strategy(strategy, context));        Poll::Ready(MutexGuard(this.mutex))    }}easy_wrapper! {    /// The future returned by [`Mutex::lock_arc`].    pub struct LockArc<T: ?Sized>(LockArcInnards<T> => MutexGuardArc<T>);    #[cfg(all(feature = "std", not(target_family = "wasm")))]    pub(crate) wait();}pin_project_lite::pin_project! {    #[project = LockArcInnardsProj]    enum LockArcInnards<T: ?Sized> {        /// We have not tried to poll the fast path yet.        Unpolled { mutex: Option<Arc<Mutex<T>>> },        /// We are acquiring the mutex through the slow path.        AcquireSlow {            #[pin]            inner: AcquireSlow<Arc<Mutex<T>>, T>        },    }}unsafe impl<T: Send + ?Sized> Send for LockArc<T> {}unsafe impl<T: Sync + ?Sized> Sync for LockArc<T> {}impl<T: ?Sized> fmt::Debug for LockArcInnards<T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        f.write_str("LockArc { .. }")    }}impl<T: ?Sized> EventListenerFuture for LockArcInnards<T> {    type Output = MutexGuardArc<T>;    fn poll_with_strategy<'a, S: event_listener_strategy::Strategy<'a>>(        mut self: Pin<&mut Self>,        strategy: &mut S,        context: &mut S::Context,    ) -> Poll<Self::Output> {        // Set the inner future if needed.        if let LockArcInnardsProj::Unpolled { mutex } = self.as_mut().project() {            let mutex = mutex.take().expect("mutex taken more than once");            // Try the fast path before trying to register slowly.            if let Some(guard) = mutex.try_lock_arc() {                return Poll::Ready(guard);            }            // Set the inner future to the slow acquire path.            self.as_mut().set(LockArcInnards::AcquireSlow {                inner: AcquireSlow::new(mutex),            });        }        // Poll the inner future.        let value = match self.project() {            LockArcInnardsProj::AcquireSlow { inner } => {                ready!(inner.poll_with_strategy(strategy, context))            }            _ => unreachable!(),        };        Poll::Ready(MutexGuardArc(value))    }}pin_project_lite::pin_project! {    /// Future for acquiring the mutex slowly.    struct AcquireSlow<B: Borrow<Mutex<T>>, T: ?Sized> {        // Reference to the mutex.        mutex: Option<B>,        // The event listener waiting on the mutex.        listener: Option<EventListener>,        // The point at which the mutex lock was started.        start: Start,        // This lock operation is starving.        starved: bool,        // Capture the `T` lifetime.        #[pin]        _marker: PhantomData<T>,        // Keeping this type `!Unpin` enables future optimizations.        #[pin]        _pin: PhantomPinned    }    impl<T: ?Sized, B: Borrow<Mutex<T>>> PinnedDrop for AcquireSlow<B, T> {        fn drop(this: Pin<&mut Self>) {            // Make sure the starvation counter is decremented.            this.take_mutex();        }    }}/// `pin_project_lite` doesn't support `#[cfg]` yet, so we have to do this manually.struct Start {    #[cfg(all(feature = "std", not(target_family = "wasm")))]    start: Option<Instant>,}impl<T: ?Sized, B: Borrow<Mutex<T>>> AcquireSlow<B, T> {    /// Create a new `AcquireSlow` future.    #[cold]    fn new(mutex: B) -> Self {        AcquireSlow {            mutex: Some(mutex),            listener: None,            start: Start {                #[cfg(all(feature = "std", not(target_family = "wasm")))]                start: None,            },            starved: false,            _marker: PhantomData,            _pin: PhantomPinned,        }    }    /// Take the mutex reference out, decrementing the counter if necessary.    fn take_mutex(self: Pin<&mut Self>) -> Option<B> {        let this = self.project();        let mutex = this.mutex.take();        if *this.starved {            if let Some(mutex) = mutex.as_ref() {                // Decrement this counter before we exit.                mutex.borrow().state.fetch_sub(2, Ordering::Release);            }        }        mutex    }}impl<T: ?Sized, B: Unpin + Borrow<Mutex<T>>> EventListenerFuture for AcquireSlow<B, T> {    type Output = B;    #[cold]    fn poll_with_strategy<'a, S: event_listener_strategy::Strategy<'a>>(        mut self: Pin<&mut Self>,        strategy: &mut S,        context: &mut S::Context,    ) -> Poll<Self::Output> {        let this = self.as_mut().project();        #[cfg(all(feature = "std", not(target_family = "wasm")))]        let start = *this.start.start.get_or_insert_with(Instant::now);        let mutex = Borrow::<Mutex<T>>::borrow(            this.mutex.as_ref().expect("future polled after completion"),        );        // Only use this hot loop if we aren't currently starved.        if !*this.starved {            loop {                // Start listening for events.                if this.listener.is_none() {                    *this.listener = Some(mutex.lock_ops.listen());                    // Try locking if nobody is being starved.                    match mutex                        .state                        .compare_exchange(0, 1, Ordering::Acquire, Ordering::Acquire)                        .unwrap_or_else(|x| x)                    {                        // Lock acquired!                        0 => return Poll::Ready(self.take_mutex().unwrap()),                        // Lock is held and nobody is starved.                        1 => {}                        // Somebody is starved.                        _ => break,                    }                } else {                    ready!(strategy.poll(this.listener, context));                    // Try locking if nobody is being starved.                    match mutex                        .state                        .compare_exchange(0, 1, Ordering::Acquire, Ordering::Acquire)                        .unwrap_or_else(|x| x)                    {                        // Lock acquired!                        0 => return Poll::Ready(self.take_mutex().unwrap()),                        // Lock is held and nobody is starved.                        1 => {}                        // Somebody is starved.                        _ => {                            // Notify the first listener in line because we probably received a                            // notification that was meant for a starved task.                            mutex.lock_ops.notify(1);                            break;                        }                    }                    // If waiting for too long, fall back to a fairer locking strategy that will prevent                    // newer lock operations from starving us forever.                    #[cfg(all(feature = "std", not(target_family = "wasm")))]                    if start.elapsed() > Duration::from_micros(500) {                        break;                    }                }            }            // Increment the number of starved lock operations.            if mutex.state.fetch_add(2, Ordering::Release) > usize::MAX / 2 {                // In case of potential overflow, abort.                crate::abort();            }            // Indicate that we are now starving and will use a fairer locking strategy.            *this.starved = true;        }        // Fairer locking loop.        loop {            if this.listener.is_none() {                // Start listening for events.                *this.listener = Some(mutex.lock_ops.listen());                // Try locking if nobody else is being starved.                match mutex                    .state                    .compare_exchange(2, 2 | 1, Ordering::Acquire, Ordering::Acquire)                    .unwrap_or_else(|x| x)                {                    // Lock acquired!                    2 => return Poll::Ready(self.take_mutex().unwrap()),                    // Lock is held by someone.                    s if s % 2 == 1 => {}                    // Lock is available.                    _ => {                        // Be fair: notify the first listener and then go wait in line.                        mutex.lock_ops.notify(1);                    }                }            } else {                // Wait for a notification.                ready!(strategy.poll(this.listener, context));                // Try acquiring the lock without waiting for others.                if mutex.state.fetch_or(1, Ordering::Acquire) % 2 == 0 {                    return Poll::Ready(self.take_mutex().unwrap());                }            }        }    }}/// A guard that releases the mutex when dropped.#[clippy::has_significant_drop]pub struct MutexGuard<'a, T: ?Sized>(&'a Mutex<T>);unsafe impl<T: Send + ?Sized> Send for MutexGuard<'_, T> {}unsafe impl<T: Sync + ?Sized> Sync for MutexGuard<'_, T> {}impl<'a, T: ?Sized> MutexGuard<'a, T> {    /// Returns a reference to the mutex a guard came from.    ///    /// # Examples    ///    /// ```    /// # futures_lite::future::block_on(async {    /// use async_lock::{Mutex, MutexGuard};    ///    /// let mutex = Mutex::new(10i32);    /// let guard = mutex.lock().await;    /// dbg!(MutexGuard::source(&guard));    /// # })    /// ```    pub fn source(guard: &MutexGuard<'a, T>) -> &'a Mutex<T> {        guard.0    }}impl<T: ?Sized> Drop for MutexGuard<'_, T> {    #[inline]    fn drop(&mut self) {        // SAFETY: we are dropping the mutex guard, therefore unlocking the mutex.        unsafe {            self.0.unlock_unchecked();        }    }}impl<T: fmt::Debug + ?Sized> fmt::Debug for MutexGuard<'_, T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        fmt::Debug::fmt(&**self, f)    }}impl<T: fmt::Display + ?Sized> fmt::Display for MutexGuard<'_, T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        (**self).fmt(f)    }}impl<T: ?Sized> Deref for MutexGuard<'_, T> {    type Target = T;    fn deref(&self) -> &T {        unsafe { &*self.0.data.get() }    }}impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {    fn deref_mut(&mut self) -> &mut T {        unsafe { &mut *self.0.data.get() }    }}/// An owned guard that releases the mutex when dropped.#[clippy::has_significant_drop]pub struct MutexGuardArc<T: ?Sized>(Arc<Mutex<T>>);unsafe impl<T: Send + ?Sized> Send for MutexGuardArc<T> {}unsafe impl<T: Sync + ?Sized> Sync for MutexGuardArc<T> {}impl<T: ?Sized> MutexGuardArc<T> {    /// Returns a reference to the mutex a guard came from.    ///    /// # Examples    ///    /// ```    /// # futures_lite::future::block_on(async {    /// use async_lock::{Mutex, MutexGuardArc};    /// use std::sync::Arc;    ///    /// let mutex = Arc::new(Mutex::new(10i32));    /// let guard = mutex.lock_arc().await;    /// dbg!(MutexGuardArc::source(&guard));    /// # })    /// ```    pub fn source(guard: &Self) -> &Arc<Mutex<T>>    where        // Required because `MutexGuardArc` implements `Sync` regardless of whether `T` is `Send`,        // but this method allows dropping `T` from a different thead than it was created in.        T: Send,    {        &guard.0    }}impl<T: ?Sized> Drop for MutexGuardArc<T> {    #[inline]    fn drop(&mut self) {        // SAFETY: we are dropping the mutex guard, therefore unlocking the mutex.        unsafe {            self.0.unlock_unchecked();        }    }}impl<T: fmt::Debug + ?Sized> fmt::Debug for MutexGuardArc<T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        fmt::Debug::fmt(&**self, f)    }}impl<T: fmt::Display + ?Sized> fmt::Display for MutexGuardArc<T> {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        (**self).fmt(f)    }}impl<T: ?Sized> Deref for MutexGuardArc<T> {    type Target = T;    fn deref(&self) -> &T {        unsafe { &*self.0.data.get() }    }}impl<T: ?Sized> DerefMut for MutexGuardArc<T> {    fn deref_mut(&mut self) -> &mut T {        unsafe { &mut *self.0.data.get() }    }}