use core::cell::UnsafeCell;
use core::future::Future;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
use core::pin::Pin;
use core::sync::atomic::{AtomicBool, Ordering, fence};
use core::task::{Context, Poll, Waker};
use crossbeam_queue::SegQueue;
pub struct AsyncMutex<T> {
locked: AtomicBool,
waiters: SegQueue<Waker>,
value: UnsafeCell<T>,
}
unsafe impl<T: Send> Send for AsyncMutex<T> {}
unsafe impl<T: Send> Sync for AsyncMutex<T> {}
impl<T> AsyncMutex<T> {
#[must_use]
pub const fn new(value: T) -> Self {
Self {
locked: AtomicBool::new(false),
waiters: SegQueue::new(),
value: UnsafeCell::new(value),
}
}
pub async fn lock(&self) -> AsyncMutexGuard<'_, T> {
Lock { mutex: self }.await
}
#[must_use]
pub fn try_lock(&self) -> Option<AsyncMutexGuard<'_, T>> {
if self.acquire() {
Some(AsyncMutexGuard {
mutex: self,
_access: PhantomData,
})
} else {
None
}
}
fn acquire(&self) -> bool {
self.locked
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
}
struct Lock<'mutex, T> {
mutex: &'mutex AsyncMutex<T>,
}
impl<'mutex, T> Future for Lock<'mutex, T> {
type Output = AsyncMutexGuard<'mutex, T>;
fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let mutex = self.mutex;
if mutex.acquire() {
return Poll::Ready(AsyncMutexGuard {
mutex,
_access: PhantomData,
});
}
mutex.waiters.push(context.waker().clone());
fence(Ordering::SeqCst);
if mutex.acquire() {
return Poll::Ready(AsyncMutexGuard {
mutex,
_access: PhantomData,
});
}
Poll::Pending
}
}
pub struct AsyncMutexGuard<'mutex, T> {
mutex: &'mutex AsyncMutex<T>,
_access: PhantomData<&'mutex mut T>,
}
impl<T> Deref for AsyncMutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.mutex.value.get() }
}
}
impl<T> DerefMut for AsyncMutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.mutex.value.get() }
}
}
impl<T> Drop for AsyncMutexGuard<'_, T> {
fn drop(&mut self) {
self.mutex.locked.store(false, Ordering::Release);
fence(Ordering::SeqCst);
while let Some(waker) = self.mutex.waiters.pop() {
waker.wake();
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
extern crate std;
use super::*;
use core::task::{RawWaker, RawWakerVTable};
use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::vec::Vec;
use futures::executor::{ThreadPool, block_on};
use futures::task::SpawnExt;
#[test]
fn contended_counter_sums_exactly() {
const TASKS: usize = 16;
const ADDS: usize = 2_000;
let pool = ThreadPool::builder()
.pool_size(4)
.create()
.expect("thread pool");
let mutex = Arc::new(AsyncMutex::new(0usize));
let handles: Vec<_> = (0..TASKS)
.map(|_| {
let mutex = Arc::clone(&mutex);
pool.spawn_with_handle(async move {
for _ in 0..ADDS {
*mutex.lock().await += 1;
}
})
.expect("spawn task")
})
.collect();
for handle in handles {
block_on(handle);
}
assert_eq!(block_on(async { *mutex.lock().await }), TASKS * ADDS);
}
#[test]
fn try_lock_excludes_while_held() {
let mutex = AsyncMutex::new(7u32);
let guard = mutex.try_lock().expect("free lock acquires");
assert!(mutex.try_lock().is_none());
drop(guard);
assert!(mutex.try_lock().is_some());
}
fn counting_waker(count: &Arc<AtomicUsize>) -> Waker {
fn clone(pointer: *const ()) -> RawWaker {
let count = unsafe { Arc::from_raw(pointer.cast::<AtomicUsize>()) };
let cloned = Arc::clone(&count);
let _ = Arc::into_raw(count);
RawWaker::new(Arc::into_raw(cloned).cast(), &VTABLE)
}
fn wake(pointer: *const ()) {
let count = unsafe { Arc::from_raw(pointer.cast::<AtomicUsize>()) };
count.fetch_add(1, Ordering::SeqCst);
}
fn wake_by_ref(pointer: *const ()) {
let count = unsafe { Arc::from_raw(pointer.cast::<AtomicUsize>()) };
count.fetch_add(1, Ordering::SeqCst);
let _ = Arc::into_raw(count);
}
fn drop_waker(pointer: *const ()) {
drop(unsafe { Arc::from_raw(pointer.cast::<AtomicUsize>()) });
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
let raw = RawWaker::new(Arc::into_raw(Arc::clone(count)).cast(), &VTABLE);
unsafe { Waker::from_raw(raw) }
}
#[test]
fn register_then_recheck_race_wakes_and_acquires() {
let mutex = AsyncMutex::new(0u32);
let holder = mutex.try_lock().expect("first acquire");
let count = Arc::new(AtomicUsize::new(0));
let waker = counting_waker(&count);
let mut context = Context::from_waker(&waker);
let mut waiter = Box::pin(Lock { mutex: &mutex });
assert!(
waiter.as_mut().poll(&mut context).is_pending(),
"waiter yields while lock is held"
);
assert_eq!(count.load(Ordering::SeqCst), 0, "no premature wake");
drop(holder);
assert_eq!(count.load(Ordering::SeqCst), 1, "release wakes the waiter");
match waiter.as_mut().poll(&mut context) {
Poll::Ready(mut guard) => {
*guard += 1;
assert_eq!(*guard, 1);
}
Poll::Pending => panic!("waiter must acquire after release"),
}
}
}