use core::cell::UnsafeCell;
use core::fmt;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicBool, Ordering};
pub struct Mutex<T: ?Sized> {
locked: AtomicBool,
data: UnsafeCell<T>,
}
pub struct MutexGuard<'a, T: ?Sized> {
lock: &'a Mutex<T>,
}
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Send for MutexGuard<'_, T> {}
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
const SPIN_BACKOFF_CAP: u32 = 64;
impl<T> Mutex<T> {
#[must_use]
pub const fn new(data: T) -> Self {
Self {
locked: AtomicBool::new(false),
data: UnsafeCell::new(data),
}
}
}
impl<T: ?Sized> Mutex<T> {
#[inline]
pub fn lock(&self) -> MutexGuard<'_, T> {
let mut backoff = 1_u32;
loop {
if self
.locked
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
return MutexGuard { lock: self };
}
while self.locked.load(Ordering::Relaxed) {
for _ in 0..backoff {
core::hint::spin_loop();
}
backoff = (backoff.saturating_mul(2)).min(SPIN_BACKOFF_CAP);
}
}
}
#[inline]
#[allow(dead_code)] pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
if self
.locked
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
Some(MutexGuard { lock: self })
} else {
None
}
}
}
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
#[inline]
fn drop(&mut self) {
self.lock.locked.store(false, Ordering::Release);
}
}
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.data.get() }
}
}
impl<T: ?Sized> fmt::Debug for MutexGuard<'_, T> {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.debug_struct("MutexGuard").finish_non_exhaustive()
}
}
impl<T: ?Sized> fmt::Debug for Mutex<T> {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.debug_struct("Mutex").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::shadow_reuse)]
extern crate std;
use super::Mutex;
use alloc::vec::Vec;
#[test]
fn lock_unlock_roundtrip() {
let m = Mutex::new(0_u32);
{
let mut g = m.lock();
*g = 7;
}
assert_eq!(*m.lock(), 7);
}
#[test]
fn nested_critical_sections_sequential() {
let m = Mutex::new(Vec::<u8>::new());
{
let mut g = m.lock();
g.push(1);
g.push(2);
}
{
let mut g = m.lock();
g.push(3);
}
assert_eq!(m.lock().as_slice(), &[1, 2, 3]);
}
#[test]
fn guard_drop_releases() {
let m = Mutex::new(0_u32);
let g = m.lock();
drop(g);
let _ = m.lock();
}
#[test]
fn try_lock_none_while_held() {
let m = Mutex::new(0_u32);
let _g = m.lock();
assert!(m.try_lock().is_none());
}
#[test]
fn try_lock_some_when_free() {
let m = Mutex::new(1_u32);
let g = m.try_lock().expect("free");
assert_eq!(*g, 1);
}
#[test]
fn concurrent_increments() {
use std::sync::Arc;
use std::thread;
let m = Arc::new(Mutex::new(0_u32));
let mut handles = Vec::new();
for _ in 0..4 {
let lock = Arc::clone(&m);
handles.push(thread::spawn(move || {
for _ in 0..50 {
let mut g = lock.lock();
*g = g.wrapping_add(1);
}
}));
}
for h in handles {
assert!(h.join().is_ok());
}
assert_eq!(*m.lock(), 200);
}
#[test]
fn debug_does_not_lock() {
let m = Mutex::new(1_u32);
let _g = m.lock();
let _ = alloc::format!("{m:?}");
}
}