diff --git a/Cargo.toml b/Cargo.toml index 211c6a0..d6fb3e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qadapt" -version = "1.0.1" +version = "1.0.2" authors = ["Bradlee Speice "] description = "The Quick And Dirty Allocation Profiling Tool" license = "Apache-2.0" @@ -19,9 +19,9 @@ edition = "2018" maintenance = { status = "actively-developed" } [dependencies] +spin = "0.5" thread-id = "3.3" -qadapt-macro = { version = "1.0.0", path = "./qadapt-macro" } -qadapt-spin = { version = "1.0.0", path = "./qadapt-spin" } +qadapt-macro = { version = "1.0.2", path = "./qadapt-macro" } [dev-dependencies] futures = "0.1" diff --git a/qadapt-macro/Cargo.toml b/qadapt-macro/Cargo.toml index efef290..1f7360e 100644 --- a/qadapt-macro/Cargo.toml +++ b/qadapt-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qadapt-macro" -version = "1.0.1" +version = "1.0.2" authors = ["Bradlee Speice "] description = "The Quick And Dirty Allocation Profiling Tool - Support Macros" license = "Apache-2.0" diff --git a/qadapt-spin/.gitignore b/qadapt-spin/.gitignore deleted file mode 100644 index 6936990..0000000 --- a/qadapt-spin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock diff --git a/qadapt-spin/Cargo.toml b/qadapt-spin/Cargo.toml deleted file mode 100644 index f1c63af..0000000 --- a/qadapt-spin/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] - -name = "qadapt-spin" -version = "1.0.1" -authors = [ "Mathijs van de Nes ", - "John Ericson " ] -license = "MIT" -repository = "https://github.com/mvdnes/spin-rs.git" -documentation = "https://mvdnes.github.io/rust-docs/spin-rs/spin/index.html" -keywords = ["spinlock", "mutex", "rwlock"] -description = """ -Synchronization primitives based on spinning. -They may contain data, are usable without `std`, -and static initializers are available. -""" diff --git a/qadapt-spin/LICENSE b/qadapt-spin/LICENSE deleted file mode 100644 index b2d7f7b..0000000 --- a/qadapt-spin/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Mathijs van de Nes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/qadapt-spin/README.md b/qadapt-spin/README.md deleted file mode 100644 index 9fe749f..0000000 --- a/qadapt-spin/README.md +++ /dev/null @@ -1,4 +0,0 @@ -qadapt-spin -=========== - -Placeholder for a stable-compatible [`spin-rs`](https://github.com/mvdnes/spin-rs) crate. diff --git a/qadapt-spin/examples/debug.rs b/qadapt-spin/examples/debug.rs deleted file mode 100644 index 64654f6..0000000 --- a/qadapt-spin/examples/debug.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate spin; - -fn main() { - let mutex = spin::Mutex::new(42); - println!("{:?}", mutex); - { - let x = mutex.lock(); - println!("{:?}, {:?}", mutex, *x); - } - - let rwlock = spin::RwLock::new(42); - println!("{:?}", rwlock); - { - let x = rwlock.read(); - println!("{:?}, {:?}", rwlock, *x); - } - { - let x = rwlock.write(); - println!("{:?}, {:?}", rwlock, *x); - } -} diff --git a/qadapt-spin/script/doc-upload.cfg b/qadapt-spin/script/doc-upload.cfg deleted file mode 100644 index d78088a..0000000 --- a/qadapt-spin/script/doc-upload.cfg +++ /dev/null @@ -1,3 +0,0 @@ -PROJECT_NAME=spin-rs -DOCS_REPO=mvdnes/rust-docs.git -DOC_RUST_VERSION=nightly diff --git a/qadapt-spin/src/lib.rs b/qadapt-spin/src/lib.rs deleted file mode 100644 index 8901dcc..0000000 --- a/qadapt-spin/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![crate_type = "lib"] -#![warn(missing_docs)] - -//! Synchronization primitives based on spinning - -#![no_std] - -#[cfg(test)] -#[macro_use] -extern crate std; - -pub use mutex::*; -pub use once::*; -pub use rw_lock::*; - -mod mutex; -mod once; -mod rw_lock; diff --git a/qadapt-spin/src/mutex.rs b/qadapt-spin/src/mutex.rs deleted file mode 100644 index ce206f4..0000000 --- a/qadapt-spin/src/mutex.rs +++ /dev/null @@ -1,375 +0,0 @@ -use core::cell::UnsafeCell; -use core::default::Default; -use core::fmt; -use core::marker::Sync; -use core::ops::{Deref, DerefMut, Drop}; -use core::option::Option::{self, None, Some}; -use core::sync::atomic::{spin_loop_hint as cpu_relax, AtomicBool, Ordering, ATOMIC_BOOL_INIT}; - -/// This type provides MUTual EXclusion based on spinning. -/// -/// # Description -/// -/// The behaviour of these lock is similar to their namesakes in `std::sync`. they -/// differ on the following: -/// -/// - The lock will not be poisoned in case of failure; -/// -/// # Simple examples -/// -/// ``` -/// use spin; -/// let spin_mutex = spin::Mutex::new(0); -/// -/// // Modify the data -/// { -/// let mut data = spin_mutex.lock(); -/// *data = 2; -/// } -/// -/// // Read the data -/// let answer = -/// { -/// let data = spin_mutex.lock(); -/// *data -/// }; -/// -/// assert_eq!(answer, 2); -/// ``` -/// -/// # Thread-safety example -/// -/// ``` -/// use spin; -/// use std::sync::{Arc, Barrier}; -/// -/// let numthreads = 1000; -/// let spin_mutex = Arc::new(spin::Mutex::new(0)); -/// -/// // We use a barrier to ensure the readout happens after all writing -/// let barrier = Arc::new(Barrier::new(numthreads + 1)); -/// -/// for _ in (0..numthreads) -/// { -/// let my_barrier = barrier.clone(); -/// let my_lock = spin_mutex.clone(); -/// std::thread::spawn(move|| -/// { -/// let mut guard = my_lock.lock(); -/// *guard += 1; -/// -/// // Release the lock to prevent a deadlock -/// drop(guard); -/// my_barrier.wait(); -/// }); -/// } -/// -/// barrier.wait(); -/// -/// let answer = { *spin_mutex.lock() }; -/// assert_eq!(answer, numthreads); -/// ``` -pub struct Mutex { - lock: AtomicBool, - data: UnsafeCell, -} - -/// A guard to which the protected data can be accessed -/// -/// When the guard falls out of scope it will release the lock. -#[derive(Debug)] -pub struct MutexGuard<'a, T: ?Sized + 'a> { - lock: &'a AtomicBool, - data: &'a mut T, -} - -// Same unsafe impls as `std::sync::Mutex` -unsafe impl Sync for Mutex {} -unsafe impl Send for Mutex {} - -impl Mutex { - /// Creates a new spinlock wrapping the supplied data. - /// - /// May be used statically: - /// - /// ``` - /// use spin; - /// - /// static MUTEX: spin::Mutex<()> = spin::Mutex::new(()); - /// - /// fn demo() { - /// let lock = MUTEX.lock(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - pub const fn new(user_data: T) -> Mutex { - Mutex { - lock: ATOMIC_BOOL_INIT, - data: UnsafeCell::new(user_data), - } - } - - /// Consumes this mutex, returning the underlying data. - pub fn into_inner(self) -> T { - // We know statically that there are no outstanding references to - // `self` so there's no need to lock. - let Mutex { data, .. } = self; - data.into_inner() - } -} - -impl Mutex { - fn obtain_lock(&self) { - while self.lock.compare_and_swap(false, true, Ordering::Acquire) != false { - // Wait until the lock looks unlocked before retrying - while self.lock.load(Ordering::Relaxed) { - cpu_relax(); - } - } - } - - /// Locks the spinlock and returns a guard. - /// - /// The returned value may be dereferenced for data access - /// and the lock will be dropped when the guard falls out of scope. - /// - /// ``` - /// let mylock = spin::Mutex::new(0); - /// { - /// let mut data = mylock.lock(); - /// // The lock is now locked and the data can be accessed - /// *data += 1; - /// // The lock is implicitly dropped - /// } - /// - /// ``` - pub fn lock(&self) -> MutexGuard { - self.obtain_lock(); - MutexGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - } - } - - /// Force unlock the spinlock. - /// - /// This is *extremely* unsafe if the lock is not held by the current - /// thread. However, this can be useful in some instances for exposing the - /// lock to FFI that doesn't know how to deal with RAII. - /// - /// If the lock isn't held, this is a no-op. - pub unsafe fn force_unlock(&self) { - self.lock.store(false, Ordering::Release); - } - - /// Tries to lock the mutex. If it is already locked, it will return None. Otherwise it returns - /// a guard within Some. - pub fn try_lock(&self) -> Option> { - if self.lock.compare_and_swap(false, true, Ordering::Acquire) == false { - Some(MutexGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - }) - } else { - None - } - } -} - -impl fmt::Debug for Mutex { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try_lock() { - Some(guard) => write!(f, "Mutex {{ data: ") - .and_then(|()| (&*guard).fmt(f)) - .and_then(|()| write!(f, "}}")), - None => write!(f, "Mutex {{ }}"), - } - } -} - -impl Default for Mutex { - fn default() -> Mutex { - Mutex::new(Default::default()) - } -} - -impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { - type Target = T; - fn deref<'b>(&'b self) -> &'b T { - &*self.data - } -} - -impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { - fn deref_mut<'b>(&'b mut self) -> &'b mut T { - &mut *self.data - } -} - -impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { - /// The dropping of the MutexGuard will release the lock it was created from. - fn drop(&mut self) { - self.lock.store(false, Ordering::Release); - } -} - -#[cfg(test)] -mod tests { - use std::prelude::v1::*; - - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::thread; - - use super::*; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn smoke() { - let m = Mutex::new(()); - drop(m.lock()); - drop(m.lock()); - } - - #[test] - fn lots_and_lots() { - static M: Mutex<()> = Mutex::new(()); - static mut CNT: u32 = 0; - const J: u32 = 1000; - const K: u32 = 3; - - fn inc() { - for _ in 0..J { - unsafe { - let _g = M.lock(); - CNT += 1; - } - } - } - - let (tx, rx) = channel(); - for _ in 0..K { - let tx2 = tx.clone(); - thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - }); - let tx2 = tx.clone(); - thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - }); - } - - drop(tx); - for _ in 0..2 * K { - rx.recv().unwrap(); - } - assert_eq!(unsafe { CNT }, J * K * 2); - } - - #[test] - fn try_lock() { - let mutex = Mutex::new(42); - - // First lock succeeds - let a = mutex.try_lock(); - assert_eq!(a.as_ref().map(|r| **r), Some(42)); - - // Additional lock failes - let b = mutex.try_lock(); - assert!(b.is_none()); - - // After dropping lock, it succeeds again - ::core::mem::drop(a); - let c = mutex.try_lock(); - assert_eq!(c.as_ref().map(|r| **r), Some(42)); - } - - #[test] - fn test_into_inner() { - let m = Mutex::new(NonCopy(10)); - assert_eq!(m.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct Foo(Arc); - impl Drop for Foo { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let num_drops = Arc::new(AtomicUsize::new(0)); - let m = Mutex::new(Foo(num_drops.clone())); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - { - let _inner = m.into_inner(); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - } - assert_eq!(num_drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_mutex_arc_nested() { - // Tests nested mutexes and access - // to underlying data. - let arc = Arc::new(Mutex::new(1)); - let arc2 = Arc::new(Mutex::new(arc)); - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let lock = arc2.lock(); - let lock2 = lock.lock(); - assert_eq!(*lock2, 1); - tx.send(()).unwrap(); - }); - rx.recv().unwrap(); - } - - #[test] - fn test_mutex_arc_access_in_unwind() { - let arc = Arc::new(Mutex::new(1)); - let arc2 = arc.clone(); - let _ = thread::spawn(move || -> () { - struct Unwinder { - i: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - *self.i.lock() += 1; - } - } - let _u = Unwinder { i: arc2 }; - panic!(); - }) - .join(); - let lock = arc.lock(); - assert_eq!(*lock, 2); - } - - #[test] - fn test_mutex_unsized() { - let mutex: &Mutex<[i32]> = &Mutex::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); - } - - #[test] - fn test_mutex_force_lock() { - let lock = Mutex::new(()); - ::std::mem::forget(lock.lock()); - unsafe { - lock.force_unlock(); - } - assert!(lock.try_lock().is_some()); - } -} diff --git a/qadapt-spin/src/once.rs b/qadapt-spin/src/once.rs deleted file mode 100644 index 85370e7..0000000 --- a/qadapt-spin/src/once.rs +++ /dev/null @@ -1,297 +0,0 @@ -use core::cell::UnsafeCell; -use core::fmt; -use core::sync::atomic::{spin_loop_hint as cpu_relax, AtomicUsize, Ordering}; - -/// A synchronization primitive which can be used to run a one-time global -/// initialization. Unlike its std equivalent, this is generalized so that The -/// closure returns a value and it is stored. Once therefore acts something like -/// 1a future, too. -/// -/// # Examples -/// -/// ``` -/// use spin; -/// -/// static START: spin::Once<()> = spin::Once::new(); -/// -/// START.call_once(|| { -/// // run initialization here -/// }); -/// ``` -pub struct Once { - state: AtomicUsize, - data: UnsafeCell>, // TODO remove option and use mem::uninitialized -} - -impl fmt::Debug for Once { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try() { - Some(s) => write!(f, "Once {{ data: ") - .and_then(|()| s.fmt(f)) - .and_then(|()| write!(f, "}}")), - None => write!(f, "Once {{ }}"), - } - } -} - -// Same unsafe impls as `std::sync::RwLock`, because this also allows for -// concurrent reads. -unsafe impl Sync for Once {} -unsafe impl Send for Once {} - -// Four states that a Once can be in, encoded into the lower bits of `state` in -// the Once structure. -const INCOMPLETE: usize = 0x0; -const RUNNING: usize = 0x1; -const COMPLETE: usize = 0x2; -const PANICKED: usize = 0x3; - -use core::hint::unreachable_unchecked as unreachable; - -impl Once { - /// Initialization constant of `Once`. - pub const INIT: Self = Once { - state: AtomicUsize::new(INCOMPLETE), - data: UnsafeCell::new(None), - }; - - /// Creates a new `Once` value. - pub const fn new() -> Once { - Self::INIT - } - - fn force_get<'a>(&'a self) -> &'a T { - match unsafe { &*self.data.get() }.as_ref() { - None => unsafe { unreachable() }, - Some(p) => p, - } - } - - /// Performs an initialization routine once and only once. The given closure - /// will be executed if this is the first time `call_once` has been called, - /// and otherwise the routine will *not* be invoked. - /// - /// This method will block the calling thread if another initialization - /// routine is currently running. - /// - /// When this function returns, it is guaranteed that some initialization - /// has run and completed (it may not be the closure specified). The - /// returned pointer will point to the result from the closure that was - /// ran. - /// - /// # Examples - /// - /// ``` - /// use spin; - /// - /// static INIT: spin::Once = spin::Once::new(); - /// - /// fn get_cached_val() -> usize { - /// *INIT.call_once(expensive_computation) - /// } - /// - /// fn expensive_computation() -> usize { - /// // ... - /// # 2 - /// } - /// ``` - pub fn call_once<'a, F>(&'a self, builder: F) -> &'a T - where - F: FnOnce() -> T, - { - let mut status = self.state.load(Ordering::SeqCst); - - if status == INCOMPLETE { - status = self - .state - .compare_and_swap(INCOMPLETE, RUNNING, Ordering::SeqCst); - if status == INCOMPLETE { - // We init - // We use a guard (Finish) to catch panics caused by builder - let mut finish = Finish { - state: &self.state, - panicked: true, - }; - unsafe { *self.data.get() = Some(builder()) }; - finish.panicked = false; - - status = COMPLETE; - self.state.store(status, Ordering::SeqCst); - - // This next line is strictly an optomization - return self.force_get(); - } - } - - loop { - match status { - INCOMPLETE => unreachable!(), - RUNNING => { - // We spin - cpu_relax(); - status = self.state.load(Ordering::SeqCst) - } - PANICKED => panic!("Once has panicked"), - COMPLETE => return self.force_get(), - _ => unsafe { unreachable() }, - } - } - } - - /// Returns a pointer iff the `Once` was previously initialized - pub fn try<'a>(&'a self) -> Option<&'a T> { - match self.state.load(Ordering::SeqCst) { - COMPLETE => Some(self.force_get()), - _ => None, - } - } - - /// Like try, but will spin if the `Once` is in the process of being - /// initialized - pub fn wait<'a>(&'a self) -> Option<&'a T> { - loop { - match self.state.load(Ordering::SeqCst) { - INCOMPLETE => return None, - RUNNING => cpu_relax(), // We spin - COMPLETE => return Some(self.force_get()), - PANICKED => panic!("Once has panicked"), - _ => unsafe { unreachable() }, - } - } - } -} - -struct Finish<'a> { - state: &'a AtomicUsize, - panicked: bool, -} - -impl<'a> Drop for Finish<'a> { - fn drop(&mut self) { - if self.panicked { - self.state.store(PANICKED, Ordering::SeqCst); - } - } -} - -#[cfg(test)] -mod tests { - use std::prelude::v1::*; - - use super::Once; - use std::sync::mpsc::channel; - use std::thread; - - #[test] - fn smoke_once() { - static O: Once<()> = Once::new(); - let mut a = 0; - O.call_once(|| a += 1); - assert_eq!(a, 1); - O.call_once(|| a += 1); - assert_eq!(a, 1); - } - - #[test] - fn smoke_once_value() { - static O: Once = Once::new(); - let a = O.call_once(|| 1); - assert_eq!(*a, 1); - let b = O.call_once(|| 2); - assert_eq!(*b, 1); - } - - #[test] - fn stampede_once() { - static O: Once<()> = Once::new(); - static mut RUN: bool = false; - - let (tx, rx) = channel(); - for _ in 0..10 { - let tx = tx.clone(); - thread::spawn(move || { - for _ in 0..4 { - thread::yield_now() - } - unsafe { - O.call_once(|| { - assert!(!RUN); - RUN = true; - }); - assert!(RUN); - } - tx.send(()).unwrap(); - }); - } - - unsafe { - O.call_once(|| { - assert!(!RUN); - RUN = true; - }); - assert!(RUN); - } - - for _ in 0..10 { - rx.recv().unwrap(); - } - } - - #[test] - fn try() { - static INIT: Once = Once::new(); - - assert!(INIT.try().is_none()); - INIT.call_once(|| 2); - assert_eq!(INIT.try().map(|r| *r), Some(2)); - } - - #[test] - fn try_no_wait() { - static INIT: Once = Once::new(); - - assert!(INIT.try().is_none()); - thread::spawn(move || { - INIT.call_once(|| loop {}); - }); - assert!(INIT.try().is_none()); - } - - #[test] - fn wait() { - static INIT: Once = Once::new(); - - assert!(INIT.wait().is_none()); - INIT.call_once(|| 3); - assert_eq!(INIT.wait().map(|r| *r), Some(3)); - } - - #[test] - fn panic() { - use std::panic; - - static INIT: Once<()> = Once::new(); - - // poison the once - let t = panic::catch_unwind(|| { - INIT.call_once(|| panic!()); - }); - assert!(t.is_err()); - - // poisoning propagates - let t = panic::catch_unwind(|| { - INIT.call_once(|| {}); - }); - assert!(t.is_err()); - } - - #[test] - fn init_constant() { - static O: Once<()> = Once::INIT; - let mut a = 0; - O.call_once(|| a += 1); - assert_eq!(a, 1); - O.call_once(|| a += 1); - assert_eq!(a, 1); - } -} diff --git a/qadapt-spin/src/rw_lock.rs b/qadapt-spin/src/rw_lock.rs deleted file mode 100644 index 0f4cdbe..0000000 --- a/qadapt-spin/src/rw_lock.rs +++ /dev/null @@ -1,539 +0,0 @@ -use core::cell::UnsafeCell; -use core::default::Default; -use core::fmt; -use core::ops::{Deref, DerefMut}; -use core::sync::atomic::{spin_loop_hint as cpu_relax, AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; - -/// A reader-writer lock -/// -/// This type of lock allows a number of readers or at most one writer at any -/// point in time. The write portion of this lock typically allows modification -/// of the underlying data (exclusive access) and the read portion of this lock -/// typically allows for read-only access (shared access). -/// -/// The type parameter `T` represents the data that this lock protects. It is -/// required that `T` satisfies `Send` to be shared across tasks and `Sync` to -/// allow concurrent access through readers. The RAII guards returned from the -/// locking methods implement `Deref` (and `DerefMut` for the `write` methods) -/// to allow access to the contained of the lock. -/// -/// Based on -/// -/// -/// # Examples -/// -/// ``` -/// use spin; -/// -/// let lock = spin::RwLock::new(5); -/// -/// // many reader locks can be held at once -/// { -/// let r1 = lock.read(); -/// let r2 = lock.read(); -/// assert_eq!(*r1, 5); -/// assert_eq!(*r2, 5); -/// } // read locks are dropped at this point -/// -/// // only one write lock may be held, however -/// { -/// let mut w = lock.write(); -/// *w += 1; -/// assert_eq!(*w, 6); -/// } // write lock is dropped here -/// ``` -pub struct RwLock { - lock: AtomicUsize, - data: UnsafeCell, -} - -/// A guard to which the protected data can be read -/// -/// When the guard falls out of scope it will decrement the read count, -/// potentially releasing the lock. -#[derive(Debug)] -pub struct RwLockReadGuard<'a, T: 'a + ?Sized> { - lock: &'a AtomicUsize, - data: &'a T, -} - -/// A guard to which the protected data can be written -/// -/// When the guard falls out of scope it will release the lock. -#[derive(Debug)] -pub struct RwLockWriteGuard<'a, T: 'a + ?Sized> { - lock: &'a AtomicUsize, - data: &'a mut T, -} - -// Same unsafe impls as `std::sync::RwLock` -unsafe impl Send for RwLock {} -unsafe impl Sync for RwLock {} - -const USIZE_MSB: usize = ::core::isize::MIN as usize; - -impl RwLock { - /// Creates a new spinlock wrapping the supplied data. - /// - /// May be used statically: - /// - /// ``` - /// use spin; - /// - /// static RW_LOCK: spin::RwLock<()> = spin::RwLock::new(()); - /// - /// fn demo() { - /// let lock = RW_LOCK.read(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - #[inline] - pub const fn new(user_data: T) -> RwLock { - RwLock { - lock: ATOMIC_USIZE_INIT, - data: UnsafeCell::new(user_data), - } - } - - /// Consumes this `RwLock`, returning the underlying data. - pub fn into_inner(self) -> T { - // We know statically that there are no outstanding references to - // `self` so there's no need to lock. - let RwLock { data, .. } = self; - data.into_inner() - } -} - -impl RwLock { - /// Locks this rwlock with shared read access, blocking the current thread - /// until it can be acquired. - /// - /// The calling thread will be blocked until there are no more writers which - /// hold the lock. There may be other readers currently inside the lock when - /// this method returns. This method does not provide any guarantees with - /// respect to the ordering of whether contentious readers or writers will - /// acquire the lock first. - /// - /// Returns an RAII guard which will release this thread's shared access - /// once it is dropped. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// let mut data = mylock.read(); - /// // The lock is now locked and the data can be read - /// println!("{}", *data); - /// // The lock is dropped - /// } - /// ``` - #[inline] - pub fn read<'a>(&'a self) -> RwLockReadGuard<'a, T> { - // (funny do-while loop) - while { - // Old value, with write bit unset - let mut old; - - // Wait for for writer to go away before doing expensive atomic ops - // (funny do-while loop) - while { - old = self.lock.load(Ordering::Relaxed); - old & USIZE_MSB != 0 - } { - cpu_relax(); - } - - // unset write bit - old &= !USIZE_MSB; - - let new = old + 1; - debug_assert!(new != (!USIZE_MSB) & (!0)); - - self.lock.compare_and_swap(old, new, Ordering::SeqCst) != old - } { - cpu_relax(); - } - RwLockReadGuard { - lock: &self.lock, - data: unsafe { &*self.data.get() }, - } - } - - /// Attempt to acquire this lock with shared read access. - /// - /// This function will never block and will return immediately if `read` - /// would otherwise succeed. Returns `Some` of an RAII guard which will - /// release the shared access of this thread when dropped, or `None` if the - /// access could not be granted. This method does not provide any - /// guarantees with respect to the ordering of whether contentious readers - /// or writers will acquire the lock first. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// match mylock.try_read() { - /// Some(data) => { - /// // The lock is now locked and the data can be read - /// println!("{}", *data); - /// // The lock is dropped - /// }, - /// None => (), // no cigar - /// }; - /// } - /// ``` - #[inline] - pub fn try_read(&self) -> Option> { - // Old value, with write bit unset - let old = (!USIZE_MSB) & self.lock.load(Ordering::Relaxed); - - let new = old + 1; - debug_assert!(new != (!USIZE_MSB) & (!0)); - if self.lock.compare_and_swap(old, new, Ordering::SeqCst) == old { - Some(RwLockReadGuard { - lock: &self.lock, - data: unsafe { &*self.data.get() }, - }) - } else { - None - } - } - - /// Force decrement the reader count. - /// - /// This is *extremely* unsafe if there are outstanding `RwLockReadGuard`s - /// live, or if called more times than `read` has been called, but can be - /// useful in FFI contexts where the caller doesn't know how to deal with - /// RAII. - pub unsafe fn force_read_decrement(&self) { - debug_assert!(self.lock.load(Ordering::Relaxed) & (!USIZE_MSB) > 0); - self.lock.fetch_sub(1, Ordering::SeqCst); - } - - /// Force unlock exclusive write access. - /// - /// This is *extremely* unsafe if there are outstanding `RwLockWriteGuard`s - /// live, or if called when there are current readers, but can be useful in - /// FFI contexts where the caller doesn't know how to deal with RAII. - pub unsafe fn force_write_unlock(&self) { - debug_assert_eq!(self.lock.load(Ordering::Relaxed), USIZE_MSB); - self.lock.store(0, Ordering::Relaxed); - } - - /// Lock this rwlock with exclusive write access, blocking the current - /// thread until it can be acquired. - /// - /// This function will not return while other writers or other readers - /// currently have access to the lock. - /// - /// Returns an RAII guard which will drop the write access of this rwlock - /// when dropped. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// let mut data = mylock.write(); - /// // The lock is now locked and the data can be written - /// *data += 1; - /// // The lock is dropped - /// } - /// ``` - #[inline] - pub fn write<'a>(&'a self) -> RwLockWriteGuard<'a, T> { - loop { - // Old value, with write bit unset. - let old = (!USIZE_MSB) & self.lock.load(Ordering::Relaxed); - // Old value, with write bit set. - let new = USIZE_MSB | old; - if self.lock.compare_and_swap(old, new, Ordering::SeqCst) == old { - // Wait for readers to go away, then lock is ours. - while self.lock.load(Ordering::Relaxed) != USIZE_MSB { - cpu_relax(); - } - break; - } - } - RwLockWriteGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - } - } - - /// Attempt to lock this rwlock with exclusive write access. - /// - /// This function does not ever block, and it will return `None` if a call - /// to `write` would otherwise block. If successful, an RAII guard is - /// returned. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// match mylock.try_write() { - /// Some(mut data) => { - /// // The lock is now locked and the data can be written - /// *data += 1; - /// // The lock is implicitly dropped - /// }, - /// None => (), // no cigar - /// }; - /// } - /// ``` - #[inline] - pub fn try_write(&self) -> Option> { - if self.lock.compare_and_swap(0, USIZE_MSB, Ordering::SeqCst) == 0 { - Some(RwLockWriteGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - }) - } else { - None - } - } -} - -impl fmt::Debug for RwLock { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try_read() { - Some(guard) => write!(f, "RwLock {{ data: ") - .and_then(|()| (&*guard).fmt(f)) - .and_then(|()| write!(f, "}}")), - None => write!(f, "RwLock {{ }}"), - } - } -} - -impl Default for RwLock { - fn default() -> RwLock { - RwLock::new(Default::default()) - } -} - -impl<'rwlock, T: ?Sized> Deref for RwLockReadGuard<'rwlock, T> { - type Target = T; - - fn deref(&self) -> &T { - self.data - } -} - -impl<'rwlock, T: ?Sized> Deref for RwLockWriteGuard<'rwlock, T> { - type Target = T; - - fn deref(&self) -> &T { - self.data - } -} - -impl<'rwlock, T: ?Sized> DerefMut for RwLockWriteGuard<'rwlock, T> { - fn deref_mut(&mut self) -> &mut T { - self.data - } -} - -impl<'rwlock, T: ?Sized> Drop for RwLockReadGuard<'rwlock, T> { - fn drop(&mut self) { - debug_assert!(self.lock.load(Ordering::Relaxed) & (!USIZE_MSB) > 0); - self.lock.fetch_sub(1, Ordering::SeqCst); - } -} - -impl<'rwlock, T: ?Sized> Drop for RwLockWriteGuard<'rwlock, T> { - fn drop(&mut self) { - debug_assert_eq!(self.lock.load(Ordering::Relaxed), USIZE_MSB); - self.lock.store(0, Ordering::Relaxed); - } -} - -#[cfg(test)] -mod tests { - use std::prelude::v1::*; - - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::mpsc::channel; - use std::sync::Arc; - use std::thread; - - use super::*; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn smoke() { - let l = RwLock::new(()); - drop(l.read()); - drop(l.write()); - drop((l.read(), l.read())); - drop(l.write()); - } - - // TODO: needs RNG - //#[test] - //fn frob() { - // static R: RwLock = RwLock::new(); - // const N: usize = 10; - // const M: usize = 1000; - // - // let (tx, rx) = channel::<()>(); - // for _ in 0..N { - // let tx = tx.clone(); - // thread::spawn(move|| { - // let mut rng = rand::thread_rng(); - // for _ in 0..M { - // if rng.gen_weighted_bool(N) { - // drop(R.write()); - // } else { - // drop(R.read()); - // } - // } - // drop(tx); - // }); - // } - // drop(tx); - // let _ = rx.recv(); - // unsafe { R.destroy(); } - //} - - #[test] - fn test_rw_arc() { - let arc = Arc::new(RwLock::new(0)); - let arc2 = arc.clone(); - let (tx, rx) = channel(); - - thread::spawn(move || { - let mut lock = arc2.write(); - for _ in 0..10 { - let tmp = *lock; - *lock = -1; - thread::yield_now(); - *lock = tmp + 1; - } - tx.send(()).unwrap(); - }); - - // Readers try to catch the writer in the act - let mut children = Vec::new(); - for _ in 0..5 { - let arc3 = arc.clone(); - children.push(thread::spawn(move || { - let lock = arc3.read(); - assert!(*lock >= 0); - })); - } - - // Wait for children to pass their asserts - for r in children { - assert!(r.join().is_ok()); - } - - // Wait for writer to finish - rx.recv().unwrap(); - let lock = arc.read(); - assert_eq!(*lock, 10); - } - - #[test] - fn test_rw_arc_access_in_unwind() { - let arc = Arc::new(RwLock::new(1)); - let arc2 = arc.clone(); - let _ = thread::spawn(move || -> () { - struct Unwinder { - i: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - let mut lock = self.i.write(); - *lock += 1; - } - } - let _u = Unwinder { i: arc2 }; - panic!(); - }) - .join(); - let lock = arc.read(); - assert_eq!(*lock, 2); - } - - #[test] - fn test_rwlock_unsized() { - let rw: &RwLock<[i32]> = &RwLock::new([1, 2, 3]); - { - let b = &mut *rw.write(); - b[0] = 4; - b[2] = 5; - } - let comp: &[i32] = &[4, 2, 5]; - assert_eq!(&*rw.read(), comp); - } - - #[test] - fn test_rwlock_try_write() { - use std::mem::drop; - - let lock = RwLock::new(0isize); - let read_guard = lock.read(); - - let write_result = lock.try_write(); - match write_result { - None => (), - Some(_) => assert!( - false, - "try_write should not succeed while read_guard is in scope" - ), - } - - drop(read_guard); - } - - #[test] - fn test_into_inner() { - let m = RwLock::new(NonCopy(10)); - assert_eq!(m.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct Foo(Arc); - impl Drop for Foo { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let num_drops = Arc::new(AtomicUsize::new(0)); - let m = RwLock::new(Foo(num_drops.clone())); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - { - let _inner = m.into_inner(); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - } - assert_eq!(num_drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_force_read_decrement() { - let m = RwLock::new(()); - ::std::mem::forget(m.read()); - ::std::mem::forget(m.read()); - ::std::mem::forget(m.read()); - assert!(m.try_write().is_none()); - unsafe { - m.force_read_decrement(); - m.force_read_decrement(); - } - assert!(m.try_write().is_none()); - unsafe { - m.force_read_decrement(); - } - assert!(m.try_write().is_some()); - } - - #[test] - fn test_force_write_unlock() { - let m = RwLock::new(()); - ::std::mem::forget(m.write()); - assert!(m.try_read().is_none()); - unsafe { - m.force_write_unlock(); - } - assert!(m.try_read().is_some()); - } -} diff --git a/src/lib.rs b/src/lib.rs index ac8ff51..53ece0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,7 @@ use thread_id; // Re-export the proc macros to use by other code pub use qadapt_macro::*; -use qadapt_spin::RwLock; +use spin::RwLock; use std::alloc::GlobalAlloc; use std::alloc::Layout; use std::alloc::System;