//! `SpinLock` implemented using `AtomicBool`
//! Just like Mutex except:
//!
//! 1. It uses CAS for locking, more efficient in low contention
//! 2. Use `.lock()` instead of `.lock().unwrap()` to retrieve the guard.
//! 3. It doesn't handle poison so data is still available on thread panic.
use std::cell::UnsafeCell;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, Ordering};

/// A spin lock that uses busy-waiting instead of OS blocking
#[derive(Default)]
pub struct SpinLock<T: ?Sized> {
    locked: AtomicBool,
    data: UnsafeCell<T>,
}

unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}
unsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}

/// RAII guard for a spin lock, automatically releases the lock when dropped
pub struct SpinLockGuard<'a, T: ?Sized + 'a> {
    // funny underscores due to how Deref/DerefMut currently work (they
    // disregard field privacy).
    __lock: &'a SpinLock<T>,
}

impl<'a, T: ?Sized + 'a> SpinLockGuard<'a, T> {
    /// Creates a new guard for the given lock
    pub fn new(pool: &'a SpinLock<T>) -> SpinLockGuard<'a, T> {
        Self { __lock: pool }
    }
}

unsafe impl<T: ?Sized + Sync> Sync for SpinLockGuard<'_, T> {}

impl<T> SpinLock<T> {
    /// Creates a new unlocked spin lock containing the given value
    pub fn new(t: T) -> SpinLock<T> {
        Self {
            locked: AtomicBool::new(false),
            data: UnsafeCell::new(t),
        }
    }
}

impl<T: ?Sized> SpinLock<T> {
    /// Acquires the lock, blocking the current thread until it succeeds
    pub fn lock(&self) -> SpinLockGuard<'_, T> {
        while self
            .locked
            .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
            .is_err()
        {
            core::hint::spin_loop();
        }
        SpinLockGuard::new(self)
    }
}

impl<T: ?Sized> Deref for SpinLockGuard<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        unsafe { &*self.__lock.data.get() }
    }
}

impl<T: ?Sized> DerefMut for SpinLockGuard<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        unsafe { &mut *self.__lock.data.get() }
    }
}

impl<T: ?Sized> Drop for SpinLockGuard<'_, T> {
    #[inline]
    fn drop(&mut self) {
        self.__lock.locked.store(false, Ordering::Release);
    }
}

#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
    use super::*;
    use std::sync::Arc;
    use std::sync::mpsc::channel;
    use std::thread;

    #[test]
    fn smoke() {
        let m = SpinLock::new(());
        drop(m.lock());
        drop(m.lock());
    }

    #[test]
    fn lots_and_lots() {
        const J: u32 = 1000;
        const K: u32 = 3;

        fn inc(m: &SpinLock<u32>) {
            for _ in 0..J {
                *m.lock() += 1;
            }
        }

        let m = Arc::new(SpinLock::new(0));
        let (tx, rx) = channel();
        for _ in 0..K {
            let tx2 = tx.clone();
            let m2 = m.clone();
            thread::spawn(move || {
                inc(&m2);
                tx2.send(()).unwrap();
            });
            let tx2 = tx.clone();
            let m2 = m.clone();
            thread::spawn(move || {
                inc(&m2);
                tx2.send(()).unwrap();
            });
        }

        drop(tx);
        for _ in 0..2 * K {
            rx.recv().unwrap();
        }
        assert_eq!(*m.lock(), J * K * 2);
    }

    #[test]
    fn test_mutex_unsized() {
        let mutex = SpinLock::new([1, 2, 3]);
        {
            let b = &mut *mutex.lock();
            b[0] = 4;
            b[2] = 5;
        }
        let comp: &[i32] = &[4, 2, 5];
        assert_eq!(&*mutex.lock(), comp);
    }
}
