use crossbeam::utils::{Backoff, CachePadded};
use std::cell::UnsafeCell;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::atomic;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
pub struct SpinLockGuard<'spin_lock, T: ?Sized> {
spin_lock: &'spin_lock SpinLock<T>,
}
impl<'spin_lock, T: ?Sized> SpinLockGuard<'spin_lock, T> {
#[inline(always)]
pub(crate) fn new(spin_lock: &'spin_lock SpinLock<T>) -> Self {
Self { spin_lock }
}
#[inline(always)]
pub fn spin_lock(&self) -> &SpinLock<T> {
self.spin_lock
}
#[inline(always)]
pub fn unlock(self) {}
#[inline(always)]
pub unsafe fn leak(self) -> *const CachePadded<AtomicBool> {
&ManuallyDrop::new(self).spin_lock.is_locked
}
#[inline(always)]
pub unsafe fn leak_to_atomic(self) -> &'spin_lock CachePadded<AtomicBool> {
&ManuallyDrop::new(self).spin_lock.is_locked
}
}
impl<T: ?Sized> Deref for SpinLockGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.spin_lock.value.get() }
}
}
impl<T: ?Sized> DerefMut for SpinLockGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.spin_lock.value.get() }
}
}
impl<T: ?Sized> Drop for SpinLockGuard<'_, T> {
fn drop(&mut self) {
unsafe { self.spin_lock.unlock() };
}
}
pub struct SpinLock<T: ?Sized> {
is_locked: CachePadded<AtomicBool>,
value: UnsafeCell<T>,
}
impl<T: ?Sized> SpinLock<T> {
pub const fn new(value: T) -> Self
where
T: Sized,
{
Self {
is_locked: CachePadded::new(AtomicBool::new(false)),
value: UnsafeCell::new(value),
}
}
#[inline(always)]
pub fn lock(&self) -> SpinLockGuard<T> {
let backoff = Backoff::new();
loop {
if let Some(guard) = self.try_lock() {
atomic::fence(Acquire);
return guard;
}
backoff.spin();
}
}
#[inline(always)]
pub fn try_lock(&self) -> Option<SpinLockGuard<T>> {
if self
.is_locked
.compare_exchange_weak(false, true, Acquire, Relaxed)
.is_ok()
{
Some(SpinLockGuard::new(self))
} else {
None
}
}
#[inline(always)]
pub fn get_mut(&mut self) -> &mut T {
self.value.get_mut()
}
#[inline(always)]
pub unsafe fn unlock(&self) {
debug_assert!(self.is_locked.load(Acquire));
self.is_locked.store(false, Release);
}
#[inline(always)]
#[allow(
clippy::mut_from_ref,
reason = "The caller guarantees safety using this code"
)]
pub unsafe fn get_locked(&self) -> &mut T {
debug_assert!(self.is_locked.load(Acquire));
unsafe { &mut *self.value.get() }
}
}
unsafe impl<T: ?Sized + Send + Sync> Sync for SpinLock<T> {}
unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}
impl<T: ?Sized + UnwindSafe> UnwindSafe for SpinLock<T> {}
impl<T: ?Sized + RefUnwindSafe> RefUnwindSafe for SpinLock<T> {}
#[cfg(test)]
mod tests {
use crate as orengine;
use crate::sync::{AsyncWaitGroup, WaitGroup};
use crate::test::sched_future_to_another_thread;
use crate::utils::SpinLock;
use std::sync::Arc;
#[orengine::test::test_shared]
fn test_try_mutex() {
let mutex = Arc::new(SpinLock::new(false));
let mutex_clone = mutex.clone();
let lock_wg = Arc::new(WaitGroup::new());
let lock_wg_clone = lock_wg.clone();
let unlock_wg = Arc::new(WaitGroup::new());
let unlock_wg_clone = unlock_wg.clone();
let second_lock = Arc::new(WaitGroup::new());
let second_lock_clone = second_lock.clone();
lock_wg.add(1);
unlock_wg.add(1);
sched_future_to_another_thread(async move {
let mut value = mutex_clone.lock();
println!("1");
lock_wg_clone.done();
unlock_wg_clone.wait().await;
println!("4");
*value = true;
drop(value);
second_lock_clone.done();
println!("5");
});
lock_wg.wait().await;
println!("2");
let value = mutex.try_lock();
println!("3");
assert!(value.is_none());
second_lock.inc();
unlock_wg.done();
second_lock.wait().await;
let value = mutex.try_lock();
println!("6");
match value {
Some(v) => assert!(*v, "not waited"),
None => panic!("can't acquire lock"),
}
}
#[orengine::test::test_shared]
fn stress_test_mutex() {
const PAR: usize = 4;
const TRIES: usize = 1000;
fn work_with_lock(mutex: &SpinLock<usize>, wg: &WaitGroup) {
let mut lock = mutex.lock();
*lock += 1;
if *lock % 500 == 0 {
println!("{} of {}", *lock, TRIES * PAR);
}
lock.unlock();
wg.done();
}
for _ in 0..20 {
let mutex = Arc::new(SpinLock::new(0));
let wg = Arc::new(WaitGroup::new());
wg.add(PAR * TRIES);
for _ in 1..PAR {
let wg = wg.clone();
let mutex = mutex.clone();
sched_future_to_another_thread(async move {
for _ in 0..TRIES {
work_with_lock(&mutex, &wg);
}
});
}
for _ in 0..TRIES {
work_with_lock(&mutex, &wg);
}
wg.wait().await;
assert_eq!(*mutex.lock(), TRIES * PAR);
}
}
}