use ::core::fmt;
use ::core::marker::PhantomData;
pub use ::core::sync::atomic::{
AtomicBool, AtomicI8, AtomicI16, AtomicI32, AtomicIsize, AtomicPtr, AtomicU8, AtomicU16,
AtomicU32, AtomicUsize, Ordering, compiler_fence, fence,
};
#[cfg(target_has_atomic = "64")]
pub use ::core::sync::atomic::{AtomicI64, AtomicU64};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Backend {
Single,
NativeStd,
NativeRaw,
WasmAtomics,
}
impl Backend {
pub const fn name(self) -> &'static str {
match self {
Backend::Single => "single",
Backend::NativeStd => "native-std",
Backend::NativeRaw => "native-raw",
Backend::WasmAtomics => "wasm-atomics",
}
}
pub const fn is_threaded(self) -> bool {
!matches!(self, Backend::Single)
}
}
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
pub const BACKEND: Backend = if cfg!(all(feature = "std", not(target_family = "wasm"))) {
Backend::NativeStd
} else {
Backend::Single
};
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LockRank(pub u16);
impl LockRank {
pub const UNCHECKED: LockRank = LockRank(0x0000);
pub const MACHINE: LockRank = LockRank(0x1000);
pub const TOPOLOGY: LockRank = LockRank(0x2000);
pub const SCHED: LockRank = LockRank(0x3000);
pub const BUS: LockRank = LockRank(0x4000);
pub const DEVICE: LockRank = LockRank(0x5000);
pub const WIRE: LockRank = LockRank(0x6000);
pub const POOL: LockRank = LockRank(0x7000);
pub const LEAF: LockRank = LockRank(0xffff);
pub const fn new(rank: u16) -> LockRank {
LockRank(rank)
}
pub const fn name(self) -> Option<&'static str> {
Some(match self {
LockRank::UNCHECKED => "UNCHECKED",
LockRank::MACHINE => "MACHINE",
LockRank::TOPOLOGY => "TOPOLOGY",
LockRank::SCHED => "SCHED",
LockRank::BUS => "BUS",
LockRank::DEVICE => "DEVICE",
LockRank::WIRE => "WIRE",
LockRank::POOL => "POOL",
LockRank::LEAF => "LEAF",
_ => return None,
})
}
#[must_use = "the rank is held until the returned guard is dropped"]
pub fn enter(self) -> RankGuard {
#[cfg(debug_assertions)]
if self != LockRank::UNCHECKED {
if let Some(held) = held_rank()
&& self <= held
{
panic!("lock order violation: acquiring {self} while holding {held}");
}
rank_track::push(self.0);
}
RankGuard {
#[cfg(debug_assertions)]
rank: self,
_not_send: PhantomData,
}
}
#[must_use = "the rank is held until the returned guard is dropped"]
pub fn enter_nonblocking(self) -> RankGuard {
#[cfg(debug_assertions)]
if self != LockRank::UNCHECKED {
rank_track::push(self.0);
}
RankGuard {
#[cfg(debug_assertions)]
rank: self,
_not_send: PhantomData,
}
}
}
impl fmt::Debug for LockRank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name() {
Some(name) => write!(f, "LockRank({name})"),
None => write!(f, "LockRank({:#06x})", self.0),
}
}
}
impl fmt::Display for LockRank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name() {
Some(name) => f.write_str(name),
None => write!(f, "rank {:#06x}", self.0),
}
}
}
#[derive(Debug)]
pub struct RankGuard {
#[cfg(debug_assertions)]
rank: LockRank,
_not_send: PhantomData<*const ()>,
}
impl Drop for RankGuard {
fn drop(&mut self) {
#[cfg(debug_assertions)]
if self.rank != LockRank::UNCHECKED {
rank_track::remove(self.rank.0);
}
}
}
pub fn held_rank() -> Option<LockRank> {
#[cfg(debug_assertions)]
{
rank_track::max().map(LockRank)
}
#[cfg(not(debug_assertions))]
{
None
}
}
pub fn violates_lock_order(rank: LockRank) -> bool {
if rank == LockRank::UNCHECKED {
return false;
}
matches!(held_rank(), Some(held) if rank <= held)
}
#[cfg(all(debug_assertions, any(feature = "std", test)))]
mod rank_track {
#[cfg(all(test, not(feature = "std")))]
extern crate std;
use ::core::cell::RefCell;
const CAPACITY: usize = 32;
struct Held {
ranks: [u16; CAPACITY],
depth: usize,
}
impl Held {
const fn new() -> Held {
Held {
ranks: [0; CAPACITY],
depth: 0,
}
}
}
std::thread_local! {
static HELD: RefCell<Held> = const { RefCell::new(Held::new()) };
}
fn with<R: Default>(f: impl FnOnce(&mut Held) -> R) -> R {
HELD.try_with(|held| f(&mut held.borrow_mut()))
.unwrap_or_default()
}
pub(super) fn push(rank: u16) {
with(|held| {
assert!(
held.depth < CAPACITY,
"more than {CAPACITY} locks held at once; the lock graph is the bug"
);
held.ranks[held.depth] = rank;
held.depth += 1;
});
}
pub(super) fn remove(rank: u16) {
with(|held| {
if let Some(at) = held.ranks[..held.depth].iter().rposition(|&r| r == rank) {
held.depth -= 1;
held.ranks[at] = held.ranks[held.depth];
}
});
}
pub(super) fn max() -> Option<u16> {
with(|held| held.ranks[..held.depth].iter().copied().max())
}
}
#[cfg(all(
debug_assertions,
not(any(feature = "std", test)),
any(
target_os = "none",
all(target_family = "wasm", not(target_feature = "atomics"))
)
))]
mod rank_track {
use ::core::sync::atomic::{AtomicU16, AtomicUsize, Ordering};
const CAPACITY: usize = 32;
static RANKS: [AtomicU16; CAPACITY] = [const { AtomicU16::new(0) }; CAPACITY];
static DEPTH: AtomicUsize = AtomicUsize::new(0);
pub(super) fn push(rank: u16) {
let depth = DEPTH.load(Ordering::Relaxed);
assert!(
depth < CAPACITY,
"more than {CAPACITY} locks held at once; the lock graph is the bug"
);
RANKS[depth].store(rank, Ordering::Relaxed);
DEPTH.store(depth + 1, Ordering::Relaxed);
}
pub(super) fn remove(rank: u16) {
let depth = DEPTH.load(Ordering::Relaxed);
for at in (0..depth).rev() {
if RANKS[at].load(Ordering::Relaxed) == rank {
let last = RANKS[depth - 1].load(Ordering::Relaxed);
RANKS[at].store(last, Ordering::Relaxed);
DEPTH.store(depth - 1, Ordering::Relaxed);
return;
}
}
}
pub(super) fn max() -> Option<u16> {
let depth = DEPTH.load(Ordering::Relaxed);
(0..depth).map(|at| RANKS[at].load(Ordering::Relaxed)).max()
}
}
#[cfg(all(
debug_assertions,
not(any(feature = "std", test)),
not(any(
target_os = "none",
all(target_family = "wasm", not(target_feature = "atomics"))
))
))]
mod rank_track {
pub(super) fn push(_rank: u16) {}
pub(super) fn remove(_rank: u16) {}
pub(super) fn max() -> Option<u16> {
None
}
}
#[allow(unsafe_code, dead_code, unreachable_pub)]
pub(crate) mod single {
use super::{LockRank, RankGuard};
use ::core::cell::UnsafeCell;
use ::core::fmt;
use ::core::marker::PhantomData;
use ::core::ops::{Deref, DerefMut};
use ::core::sync::atomic::{AtomicBool, AtomicIsize, AtomicU8, Ordering};
mod exclusion {
use ::core::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
#[cfg(target_has_atomic = "8")]
#[inline]
pub(super) fn claim(flag: &AtomicBool) -> bool {
flag.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
}
#[cfg(not(target_has_atomic = "8"))]
#[inline]
pub(super) fn claim(flag: &AtomicBool) -> bool {
if flag.load(Ordering::Acquire) {
return true;
}
flag.store(true, Ordering::Relaxed);
false
}
#[inline]
pub(super) fn release(flag: &AtomicBool) {
flag.store(false, Ordering::Release);
}
#[cfg(target_has_atomic = "ptr")]
#[inline]
pub(super) fn share(state: &AtomicIsize) -> bool {
let mut seen = state.load(Ordering::Relaxed);
loop {
if seen < 0 {
return false;
}
match state.compare_exchange_weak(
seen,
seen + 1,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => return true,
Err(actual) => seen = actual,
}
}
}
#[cfg(not(target_has_atomic = "ptr"))]
#[inline]
pub(super) fn share(state: &AtomicIsize) -> bool {
let seen = state.load(Ordering::Acquire);
if seen < 0 {
return false;
}
state.store(seen + 1, Ordering::Relaxed);
true
}
#[cfg(target_has_atomic = "ptr")]
#[inline]
pub(super) fn unshare(state: &AtomicIsize) {
state.fetch_sub(1, Ordering::Release);
}
#[cfg(not(target_has_atomic = "ptr"))]
#[inline]
pub(super) fn unshare(state: &AtomicIsize) {
let seen = state.load(Ordering::Relaxed);
state.store(seen - 1, Ordering::Release);
}
#[cfg(target_has_atomic = "ptr")]
#[inline]
pub(super) fn seize(state: &AtomicIsize) -> bool {
state
.compare_exchange(0, -1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
#[cfg(not(target_has_atomic = "ptr"))]
#[inline]
pub(super) fn seize(state: &AtomicIsize) -> bool {
if state.load(Ordering::Acquire) != 0 {
return false;
}
state.store(-1, Ordering::Relaxed);
true
}
#[inline]
pub(super) fn relinquish(state: &AtomicIsize) {
state.store(0, Ordering::Release);
}
}
pub struct Mutex<T: ?Sized> {
rank: LockRank,
locked: AtomicBool,
data: UnsafeCell<T>,
}
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
impl<T> Mutex<T> {
pub const fn new(value: T) -> Mutex<T> {
Mutex::with_rank(LockRank::LEAF, value)
}
pub const fn with_rank(rank: LockRank, value: T) -> Mutex<T> {
Mutex {
rank,
locked: AtomicBool::new(false),
data: UnsafeCell::new(value),
}
}
pub fn into_inner(self) -> T {
self.data.into_inner()
}
}
impl<T: ?Sized> Mutex<T> {
pub fn rank(&self) -> LockRank {
self.rank
}
pub fn lock(&self) -> MutexGuard<'_, T> {
let rank = self.rank.enter();
assert!(
!exclusion::claim(&self.locked),
"recursive lock of a `single` Mutex ({}): this deadlocks on a threaded backend",
self.rank
);
MutexGuard {
lock: self,
_rank: rank,
_not_send: PhantomData,
}
}
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
if exclusion::claim(&self.locked) {
return None;
}
Some(MutexGuard {
lock: self,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn get_mut(&mut self) -> &mut T {
self.data.get_mut()
}
}
impl<T: Default> Default for Mutex<T> {
fn default() -> Mutex<T> {
Mutex::new(T::default())
}
}
impl<T> From<T> for Mutex<T> {
fn from(value: T) -> Mutex<T> {
Mutex::new(value)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Mutex");
s.field("rank", &self.rank);
match self.try_lock() {
Some(guard) => s.field("data", &&*guard).finish(),
None => s.field("data", &"<locked>").finish(),
}
}
}
pub struct MutexGuard<'a, T: ?Sized> {
lock: &'a Mutex<T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.data.get() }
}
}
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
fn drop(&mut self) {
exclusion::release(&self.lock.locked);
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
pub struct RwLock<T: ?Sized> {
rank: LockRank,
state: AtomicIsize,
data: UnsafeCell<T>,
}
unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
impl<T> RwLock<T> {
pub const fn new(value: T) -> RwLock<T> {
RwLock::with_rank(LockRank::LEAF, value)
}
pub const fn with_rank(rank: LockRank, value: T) -> RwLock<T> {
RwLock {
rank,
state: AtomicIsize::new(0),
data: UnsafeCell::new(value),
}
}
pub fn into_inner(self) -> T {
self.data.into_inner()
}
}
impl<T: ?Sized> RwLock<T> {
pub fn rank(&self) -> LockRank {
self.rank
}
pub fn read(&self) -> RwLockReadGuard<'_, T> {
let rank = self.rank.enter();
assert!(
exclusion::share(&self.state),
"read of a `single` RwLock ({}) held for writing: this deadlocks on a threaded \
backend",
self.rank
);
RwLockReadGuard {
lock: self,
_rank: rank,
_not_send: PhantomData,
}
}
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
let rank = self.rank.enter();
assert!(
exclusion::seize(&self.state),
"write of a `single` RwLock ({}) that is already held: this deadlocks on a \
threaded backend",
self.rank
);
RwLockWriteGuard {
lock: self,
_rank: rank,
_not_send: PhantomData,
}
}
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
if !exclusion::share(&self.state) {
return None;
}
Some(RwLockReadGuard {
lock: self,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
if !exclusion::seize(&self.state) {
return None;
}
Some(RwLockWriteGuard {
lock: self,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn get_mut(&mut self) -> &mut T {
self.data.get_mut()
}
}
impl<T: Default> Default for RwLock<T> {
fn default() -> RwLock<T> {
RwLock::new(T::default())
}
}
impl<T> From<T> for RwLock<T> {
fn from(value: T) -> RwLock<T> {
RwLock::new(value)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLock<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("RwLock");
s.field("rank", &self.rank);
match self.try_read() {
Some(guard) => s.field("data", &&*guard).finish(),
None => s.field("data", &"<locked>").finish(),
}
}
}
pub struct RwLockReadGuard<'a, T: ?Sized> {
lock: &'a RwLock<T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}
impl<T: ?Sized> Drop for RwLockReadGuard<'_, T> {
fn drop(&mut self) {
exclusion::unshare(&self.lock.state);
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockReadGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
pub struct RwLockWriteGuard<'a, T: ?Sized> {
lock: &'a RwLock<T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}
impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.data.get() }
}
}
impl<T: ?Sized> Drop for RwLockWriteGuard<'_, T> {
fn drop(&mut self) {
exclusion::relinquish(&self.lock.state);
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockWriteGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[derive(Debug)]
pub struct Once {
state: AtomicU8,
}
const INCOMPLETE: u8 = 0;
const RUNNING: u8 = 1;
const COMPLETE: u8 = 2;
impl Once {
pub const fn new() -> Once {
Once {
state: AtomicU8::new(INCOMPLETE),
}
}
pub fn call_once(&self, f: impl FnOnce()) {
match self.state.load(Ordering::Relaxed) {
INCOMPLETE => {
self.state.store(RUNNING, Ordering::Relaxed);
f();
self.state.store(COMPLETE, Ordering::Relaxed);
}
COMPLETE => {}
_ => panic!("Once re-entered, or a previous initialiser panicked"),
}
}
pub fn is_completed(&self) -> bool {
self.state.load(Ordering::Relaxed) == COMPLETE
}
}
impl Default for Once {
fn default() -> Once {
Once::new()
}
}
#[derive(Debug)]
pub struct Pool {
requested_workers: usize,
}
impl Pool {
pub fn new(workers: usize) -> Pool {
Pool {
requested_workers: workers,
}
}
pub fn workers(&self) -> usize {
0
}
pub fn requested_workers(&self) -> usize {
self.requested_workers
}
pub fn submit<F, T>(&self, job: F) -> Handle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
Handle { value: Some(job()) }
}
pub fn quiesce(&self) {}
}
#[derive(Debug)]
pub struct Handle<T> {
value: Option<T>,
}
impl<T> Handle<T> {
pub fn join(mut self) -> T {
self.value
.take()
.expect("a `single` job's result is produced at submit time")
}
pub fn is_finished(&self) -> bool {
true
}
}
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
pub mod native_std {
use super::{LockRank, RankGuard};
use ::core::fmt;
use ::core::marker::PhantomData;
use ::core::ops::{Deref, DerefMut};
use std::boxed::Box;
use std::collections::VecDeque;
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::sync::{Arc, Condvar, PoisonError, TryLockError};
use std::thread::{self, JoinHandle};
use std::vec::Vec;
pub struct Mutex<T: ?Sized> {
rank: LockRank,
inner: std::sync::Mutex<T>,
}
impl<T> Mutex<T> {
pub const fn new(value: T) -> Mutex<T> {
Mutex::with_rank(LockRank::LEAF, value)
}
pub const fn with_rank(rank: LockRank, value: T) -> Mutex<T> {
Mutex {
rank,
inner: std::sync::Mutex::new(value),
}
}
pub fn into_inner(self) -> T {
self.inner
.into_inner()
.unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: ?Sized> Mutex<T> {
pub fn rank(&self) -> LockRank {
self.rank
}
pub fn lock(&self) -> MutexGuard<'_, T> {
let rank = self.rank.enter();
MutexGuard {
inner: self.inner.lock().unwrap_or_else(PoisonError::into_inner),
_rank: rank,
_not_send: PhantomData,
}
}
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
let inner = match self.inner.try_lock() {
Ok(inner) => inner,
Err(TryLockError::Poisoned(poison)) => poison.into_inner(),
Err(TryLockError::WouldBlock) => return None,
};
Some(MutexGuard {
inner,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: Default> Default for Mutex<T> {
fn default() -> Mutex<T> {
Mutex::new(T::default())
}
}
impl<T> From<T> for Mutex<T> {
fn from(value: T) -> Mutex<T> {
Mutex::new(value)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Mutex");
s.field("rank", &self.rank);
match self.try_lock() {
Some(guard) => s.field("data", &&*guard).finish(),
None => s.field("data", &"<locked>").finish(),
}
}
}
pub struct MutexGuard<'a, T: ?Sized> {
inner: std::sync::MutexGuard<'a, T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
pub struct RwLock<T: ?Sized> {
rank: LockRank,
inner: std::sync::RwLock<T>,
}
impl<T> RwLock<T> {
pub const fn new(value: T) -> RwLock<T> {
RwLock::with_rank(LockRank::LEAF, value)
}
pub const fn with_rank(rank: LockRank, value: T) -> RwLock<T> {
RwLock {
rank,
inner: std::sync::RwLock::new(value),
}
}
pub fn into_inner(self) -> T {
self.inner
.into_inner()
.unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: ?Sized> RwLock<T> {
pub fn rank(&self) -> LockRank {
self.rank
}
pub fn read(&self) -> RwLockReadGuard<'_, T> {
let rank = self.rank.enter();
RwLockReadGuard {
inner: self.inner.read().unwrap_or_else(PoisonError::into_inner),
_rank: rank,
_not_send: PhantomData,
}
}
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
let rank = self.rank.enter();
RwLockWriteGuard {
inner: self.inner.write().unwrap_or_else(PoisonError::into_inner),
_rank: rank,
_not_send: PhantomData,
}
}
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
let inner = match self.inner.try_read() {
Ok(inner) => inner,
Err(TryLockError::Poisoned(poison)) => poison.into_inner(),
Err(TryLockError::WouldBlock) => return None,
};
Some(RwLockReadGuard {
inner,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn try_write(&self) -> Option<RwLockWriteGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
let inner = match self.inner.try_write() {
Ok(inner) => inner,
Err(TryLockError::Poisoned(poison)) => poison.into_inner(),
Err(TryLockError::WouldBlock) => return None,
};
Some(RwLockWriteGuard {
inner,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut().unwrap_or_else(PoisonError::into_inner)
}
}
impl<T: Default> Default for RwLock<T> {
fn default() -> RwLock<T> {
RwLock::new(T::default())
}
}
impl<T> From<T> for RwLock<T> {
fn from(value: T) -> RwLock<T> {
RwLock::new(value)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLock<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("RwLock");
s.field("rank", &self.rank);
match self.try_read() {
Some(guard) => s.field("data", &&*guard).finish(),
None => s.field("data", &"<locked>").finish(),
}
}
}
pub struct RwLockReadGuard<'a, T: ?Sized> {
inner: std::sync::RwLockReadGuard<'a, T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockReadGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
pub struct RwLockWriteGuard<'a, T: ?Sized> {
inner: std::sync::RwLockWriteGuard<'a, T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockWriteGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[derive(Debug)]
pub struct Once {
inner: std::sync::Once,
}
impl Once {
pub const fn new() -> Once {
Once {
inner: std::sync::Once::new(),
}
}
pub fn call_once(&self, f: impl FnOnce()) {
self.inner.call_once(f);
}
pub fn is_completed(&self) -> bool {
self.inner.is_completed()
}
}
impl Default for Once {
fn default() -> Once {
Once::new()
}
}
type Job = Box<dyn FnOnce() + Send + 'static>;
struct Shared {
state: std::sync::Mutex<State>,
work: Condvar,
idle: Condvar,
}
struct State {
jobs: VecDeque<Job>,
running: usize,
shutdown: bool,
}
pub struct Pool {
shared: Arc<Shared>,
workers: Vec<JoinHandle<()>>,
requested_workers: usize,
}
impl Pool {
pub fn new(workers: usize) -> Pool {
let shared = Arc::new(Shared {
state: std::sync::Mutex::new(State {
jobs: VecDeque::new(),
running: 0,
shutdown: false,
}),
work: Condvar::new(),
idle: Condvar::new(),
});
let mut threads = Vec::with_capacity(workers);
for index in 0..workers {
let shared = Arc::clone(&shared);
let built = thread::Builder::new()
.name(std::format!("rsemu-pool-{index}"))
.spawn(move || worker(&shared));
match built {
Ok(handle) => threads.push(handle),
Err(_) => break,
}
}
Pool {
shared,
workers: threads,
requested_workers: workers,
}
}
pub fn workers(&self) -> usize {
self.workers.len()
}
pub fn requested_workers(&self) -> usize {
self.requested_workers
}
pub fn submit<F, T>(&self, job: F) -> Handle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let slot = Arc::new(Slot {
value: std::sync::Mutex::new(None),
done: Condvar::new(),
});
if self.workers.is_empty() {
let value = job();
*slot.value.lock().unwrap_or_else(PoisonError::into_inner) = Some(Ok(value));
return Handle { slot };
}
let filled = Arc::clone(&slot);
let boxed: Job = Box::new(move || {
let result = catch_unwind(AssertUnwindSafe(job));
let mut value = filled.value.lock().unwrap_or_else(PoisonError::into_inner);
*value = Some(result);
drop(value);
filled.done.notify_all();
});
let mut state = self
.shared
.state
.lock()
.unwrap_or_else(PoisonError::into_inner);
state.jobs.push_back(boxed);
drop(state);
self.shared.work.notify_one();
Handle { slot }
}
pub fn quiesce(&self) {
let mut state = self
.shared
.state
.lock()
.unwrap_or_else(PoisonError::into_inner);
while !state.jobs.is_empty() || state.running > 0 {
state = self
.shared
.idle
.wait(state)
.unwrap_or_else(PoisonError::into_inner);
}
}
}
impl Drop for Pool {
fn drop(&mut self) {
{
let mut state = self
.shared
.state
.lock()
.unwrap_or_else(PoisonError::into_inner);
state.shutdown = true;
}
self.shared.work.notify_all();
for worker in self.workers.drain(..) {
let _ = worker.join();
}
}
}
impl fmt::Debug for Pool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pending = self
.shared
.state
.try_lock()
.map(|state| state.jobs.len())
.ok();
f.debug_struct("Pool")
.field("workers", &self.workers.len())
.field("requested_workers", &self.requested_workers)
.field("pending", &pending)
.finish()
}
}
fn worker(shared: &Arc<Shared>) {
loop {
let mut state = shared.state.lock().unwrap_or_else(PoisonError::into_inner);
let job = loop {
if let Some(job) = state.jobs.pop_front() {
break job;
}
if state.shutdown {
return;
}
state = shared
.work
.wait(state)
.unwrap_or_else(PoisonError::into_inner);
};
state.running += 1;
drop(state);
job();
let mut state = shared.state.lock().unwrap_or_else(PoisonError::into_inner);
state.running -= 1;
let quiet = state.running == 0 && state.jobs.is_empty();
drop(state);
if quiet {
shared.idle.notify_all();
}
}
}
struct Slot<T> {
value: std::sync::Mutex<Option<thread::Result<T>>>,
done: Condvar,
}
pub struct Handle<T> {
slot: Arc<Slot<T>>,
}
impl<T> Handle<T> {
pub fn join(self) -> T {
let mut value = self
.slot
.value
.lock()
.unwrap_or_else(PoisonError::into_inner);
loop {
if let Some(result) = value.take() {
drop(value);
return match result {
Ok(value) => value,
Err(panic) => resume_unwind(panic),
};
}
value = self
.slot
.done
.wait(value)
.unwrap_or_else(PoisonError::into_inner);
}
}
pub fn is_finished(&self) -> bool {
self.slot
.value
.try_lock()
.map(|value| value.is_some())
.unwrap_or(false)
}
}
impl<T> fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Handle")
.field("finished", &self.is_finished())
.finish()
}
}
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
pub use native_std::{
Handle, Mutex, MutexGuard, Once, Pool, RwLock, RwLockReadGuard, RwLockWriteGuard,
};
#[cfg(not(all(feature = "std", not(target_family = "wasm"))))]
pub use single::{
Handle, Mutex, MutexGuard, Once, Pool, RwLock, RwLockReadGuard, RwLockWriteGuard,
};
pub struct Global<T: ?Sized> {
rank: LockRank,
inner: Mutex<T>,
}
impl<T> Global<T> {
pub const fn new(value: T) -> Global<T> {
Global::with_rank(LockRank::LEAF, value)
}
pub const fn with_rank(rank: LockRank, value: T) -> Global<T> {
Global {
rank,
inner: Mutex::with_rank(LockRank::UNCHECKED, value),
}
}
pub fn into_inner(self) -> T {
self.inner.into_inner()
}
}
impl<T: ?Sized> Global<T> {
pub fn rank(&self) -> LockRank {
self.rank
}
pub fn lock(&self) -> GlobalGuard<'_, T> {
let rank = self.rank.enter();
GlobalGuard {
inner: self.wait(),
_rank: rank,
_not_send: PhantomData,
}
}
pub fn try_lock(&self) -> Option<GlobalGuard<'_, T>> {
let rank = self.rank.enter_nonblocking();
Some(GlobalGuard {
inner: self.inner.try_lock()?,
_rank: rank,
_not_send: PhantomData,
})
}
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut()
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
fn wait(&self) -> MutexGuard<'_, T> {
self.inner.lock()
}
#[cfg(not(all(feature = "std", not(target_family = "wasm"))))]
fn wait(&self) -> MutexGuard<'_, T> {
loop {
if let Some(guard) = self.inner.try_lock() {
return guard;
}
::core::hint::spin_loop();
}
}
}
impl<T: Default> Default for Global<T> {
fn default() -> Global<T> {
Global::new(T::default())
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for Global<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Global");
s.field("rank", &self.rank);
match self.try_lock() {
Some(guard) => s.field("data", &&*guard).finish(),
None => s.field("data", &"<locked>").finish(),
}
}
}
pub struct GlobalGuard<'a, T: ?Sized> {
inner: MutexGuard<'a, T>,
_rank: RankGuard,
_not_send: PhantomData<*const ()>,
}
impl<T: ?Sized> ::core::ops::Deref for GlobalGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: ?Sized> ::core::ops::DerefMut for GlobalGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for GlobalGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::sync::Arc;
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
extern crate std;
#[cfg(all(feature = "std", not(target_family = "wasm")))]
use ::core::sync::atomic::{AtomicUsize, Ordering};
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn the_seam_is_send_and_sync_on_every_backend() {
assert_send_sync::<Global<u64>>();
assert_send_sync::<single::Mutex<u64>>();
assert_send_sync::<single::RwLock<u64>>();
assert_send_sync::<single::Once>();
assert_send_sync::<single::Pool>();
assert_send_sync::<single::Handle<u64>>();
#[cfg(all(feature = "std", not(target_family = "wasm")))]
{
assert_send_sync::<native_std::Mutex<u64>>();
assert_send_sync::<native_std::RwLock<u64>>();
assert_send_sync::<native_std::Once>();
assert_send_sync::<native_std::Pool>();
assert_send_sync::<native_std::Handle<u64>>();
}
}
#[test]
fn the_selected_backend_names_itself() {
assert_eq!(BACKEND.is_threaded(), BACKEND != Backend::Single);
#[cfg(all(feature = "std", not(target_family = "wasm")))]
assert_eq!(BACKEND, Backend::NativeStd);
#[cfg(not(all(feature = "std", not(target_family = "wasm"))))]
assert_eq!(BACKEND, Backend::Single);
}
#[test]
fn the_ladder_is_strictly_ordered_outermost_first() {
let ladder = [
LockRank::MACHINE,
LockRank::TOPOLOGY,
LockRank::SCHED,
LockRank::BUS,
LockRank::DEVICE,
LockRank::WIRE,
LockRank::POOL,
LockRank::LEAF,
];
for pair in ladder.windows(2) {
assert!(
pair[0] < pair[1],
"{:?} must precede {:?}",
pair[0],
pair[1]
);
}
assert!(LockRank::UNCHECKED < LockRank::MACHINE);
assert!(ladder.iter().all(|rank| rank.name().is_some()));
assert!(LockRank::new(0x1234).name().is_none());
}
#[cfg(debug_assertions)]
#[test]
fn holding_a_rank_forbids_that_rank_and_every_coarser_one() {
assert_eq!(held_rank(), None);
let outer = LockRank::TOPOLOGY.enter();
assert!(violates_lock_order(LockRank::MACHINE), "coarser");
assert!(violates_lock_order(LockRank::TOPOLOGY), "the same rank");
assert!(!violates_lock_order(LockRank::DEVICE), "finer");
assert!(!violates_lock_order(LockRank::UNCHECKED), "exempt");
assert_eq!(held_rank(), Some(LockRank::TOPOLOGY));
let inner = LockRank::DEVICE.enter();
assert_eq!(held_rank(), Some(LockRank::DEVICE));
drop(outer);
assert_eq!(held_rank(), Some(LockRank::DEVICE));
drop(inner);
assert_eq!(held_rank(), None);
}
#[cfg(debug_assertions)]
#[test]
fn a_leaf_holds_nothing_under_it_not_even_another_leaf() {
let leaf = LockRank::LEAF.enter();
assert!(violates_lock_order(LockRank::LEAF));
assert!(violates_lock_order(LockRank::DEVICE));
drop(leaf);
}
#[cfg(debug_assertions)]
#[test]
fn unchecked_ranks_neither_record_nor_check() {
let a = LockRank::UNCHECKED.enter();
assert_eq!(held_rank(), None, "an exempt rank is not recorded");
let b = LockRank::UNCHECKED.enter();
assert_eq!(held_rank(), None);
drop((a, b));
}
#[cfg(debug_assertions)]
#[test]
fn a_try_lock_records_its_rank_without_checking_the_order() {
let fine = LockRank::DEVICE.enter();
let out_of_order = LockRank::BUS.enter_nonblocking();
assert_eq!(held_rank(), Some(LockRank::DEVICE));
assert!(violates_lock_order(LockRank::BUS));
drop((out_of_order, fine));
assert_eq!(held_rank(), None);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "lock order violation")]
fn acquiring_out_of_order_panics_naming_both_ranks() {
let _device = LockRank::DEVICE.enter();
let _bus = LockRank::BUS.enter();
}
#[cfg(not(debug_assertions))]
#[test]
fn rank_tracking_costs_nothing_in_release() {
let _held = LockRank::DEVICE.enter();
assert_eq!(held_rank(), None);
assert!(!violates_lock_order(LockRank::MACHINE));
}
macro_rules! backend_suite {
($name:ident, $backend:path) => {
mod $name {
use super::assert_send_sync;
use ::core::sync::atomic::{AtomicUsize, Ordering};
use alloc::sync::Arc;
use alloc::vec::Vec;
use $backend as sync;
use $crate::core::sync::{LockRank, held_rank, violates_lock_order};
#[test]
fn a_guard_is_exclusive_and_releases_on_drop() {
let cell = sync::Mutex::new(7u32);
{
let mut guard = cell.lock();
*guard += 1;
assert!(
cell.try_lock().is_none(),
"a held lock must refuse a second acquisition"
);
}
assert_eq!(*cell.lock(), 8);
assert_eq!(cell.into_inner(), 8);
}
#[test]
fn try_lock_is_the_portable_reentrancy_probe() {
let state = sync::Mutex::new(0u8);
let outer = state.try_lock().expect("uncontended");
assert!(state.try_lock().is_none());
drop(outer);
assert!(state.try_lock().is_some());
}
#[test]
fn rwlock_shares_readers_and_excludes_a_writer() {
let cell = sync::RwLock::new(5u32);
{
let a = cell.try_read().expect("first reader");
let b = cell.try_read().expect("second reader");
assert_eq!(*a + *b, 10);
assert!(cell.try_write().is_none(), "a reader excludes a writer");
}
{
let mut w = cell.try_write().expect("uncontended");
*w = 6;
assert!(cell.try_read().is_none(), "a writer excludes a reader");
}
assert_eq!(*cell.read(), 6);
}
#[test]
fn once_runs_exactly_once() {
let once = sync::Once::new();
let mut runs = 0u32;
assert!(!once.is_completed());
once.call_once(|| runs += 1);
once.call_once(|| runs += 1);
assert_eq!(runs, 1);
assert!(once.is_completed());
}
#[test]
fn joining_in_submission_order_is_deterministic() {
let pool = sync::Pool::new(4);
let handles: Vec<_> = (0..16u64).map(|i| pool.submit(move || i * i)).collect();
let results: Vec<u64> = handles.into_iter().map(|h| h.join()).collect();
let expected: Vec<u64> = (0..16u64).map(|i| i * i).collect();
assert_eq!(results, expected);
}
#[test]
fn dropped_handles_still_run_and_quiesce_waits_for_them() {
let pool = sync::Pool::new(3);
let done = Arc::new(AtomicUsize::new(0));
for _ in 0..32 {
let done = Arc::clone(&done);
pool.submit(move || done.fetch_add(1, Ordering::SeqCst));
}
pool.quiesce();
assert_eq!(done.load(Ordering::SeqCst), 32);
}
#[test]
fn a_zero_worker_pool_runs_jobs_inline() {
let pool = sync::Pool::new(0);
assert_eq!(pool.workers(), 0);
assert_eq!(pool.requested_workers(), 0);
let handle = pool.submit(|| 42u8);
assert!(
handle.is_finished(),
"an inline job is done before submit returns"
);
assert_eq!(handle.join(), 42);
}
#[test]
fn ranked_locks_nest_in_ladder_order() {
let outer = sync::Mutex::with_rank(LockRank::TOPOLOGY, 1u32);
let inner = sync::Mutex::with_rank(LockRank::DEVICE, 2u32);
let a = outer.lock();
let b = inner.lock();
assert_eq!(*a + *b, 3);
assert_eq!(outer.rank(), LockRank::TOPOLOGY);
assert_eq!(inner.rank(), LockRank::DEVICE);
#[cfg(debug_assertions)]
assert_eq!(held_rank(), Some(LockRank::DEVICE));
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "lock order violation")]
fn nesting_against_the_ladder_panics() {
let bus = sync::Mutex::with_rank(LockRank::BUS, 0u32);
let sched = sync::Mutex::with_rank(LockRank::SCHED, 0u32);
let _inner = bus.lock();
let _outer = sched.lock();
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "lock order violation")]
fn recursive_locking_is_caught_as_an_order_violation() {
let device = sync::Mutex::with_rank(LockRank::DEVICE, 0u32);
let _first = device.lock();
let _second = device.lock();
}
#[test]
fn a_lock_releases_its_rank_when_the_guard_drops() {
let device = sync::Mutex::with_rank(LockRank::DEVICE, 0u32);
{
let _held = device.lock();
assert!(violates_lock_order(LockRank::DEVICE) == cfg!(debug_assertions));
}
assert!(!violates_lock_order(LockRank::DEVICE));
assert_eq!(held_rank(), None);
}
#[test]
fn the_state_hash_does_not_depend_on_the_worker_count() {
let baseline = state_hash(0);
for workers in [1, 2, 4, 7] {
assert_eq!(state_hash(workers), baseline, "with {workers} workers");
}
}
#[test]
fn types_are_send_and_sync() {
assert_send_sync::<sync::Mutex<u64>>();
assert_send_sync::<sync::RwLock<u64>>();
assert_send_sync::<sync::Pool>();
}
pub(super) fn state_hash(workers: usize) -> u64 {
let ready = sync::Once::new();
let seed = sync::RwLock::with_rank(LockRank::MACHINE, 0u64);
ready.call_once(|| *seed.write() = 0x9e37_79b9_7f4a_7c15);
let base = *seed.read();
let pool = sync::Pool::new(workers);
let accumulator = Arc::new(sync::Mutex::with_rank(LockRank::DEVICE, 0u64));
let handles: Vec<_> = (0..24u64)
.map(|i| {
let accumulator = Arc::clone(&accumulator);
pool.submit(move || {
let step = base.wrapping_mul(i.wrapping_add(1)) ^ (i << 7);
let mut acc = accumulator.lock();
*acc = acc.wrapping_add(step);
step
})
})
.collect();
let mut hash = base;
for handle in handles {
hash = hash.rotate_left(7) ^ handle.join();
}
pool.quiesce();
hash ^ *accumulator.lock()
}
}
};
}
backend_suite!(single_backend, crate::core::sync::single);
#[cfg(all(feature = "std", not(target_family = "wasm")))]
backend_suite!(native_std_backend, crate::core::sync::native_std);
#[cfg(all(feature = "std", not(target_family = "wasm")))]
#[test]
fn single_and_native_std_agree_on_the_state_hash() {
for workers in [0, 1, 4] {
assert_eq!(
single_backend::state_hash(workers),
native_std_backend::state_hash(workers),
"backends diverged with {workers} workers"
);
}
}
#[test]
#[cfg_attr(debug_assertions, should_panic(expected = "lock order violation"))]
#[cfg_attr(not(debug_assertions), should_panic(expected = "recursive lock"))]
fn single_reports_a_recursive_lock_instead_of_hanging() {
let device = single::Mutex::new(0u32);
let _first = device.lock();
let _second = device.lock();
}
#[test]
#[should_panic(expected = "recursive lock")]
fn single_catches_a_job_that_re_enters_its_submitters_critical_section() {
let pool = single::Pool::new(0);
let state = Arc::new(single::Mutex::with_rank(LockRank::UNCHECKED, 0u32));
let held = state.lock();
let inner = Arc::clone(&state);
let _ = pool.submit(move || *inner.lock());
drop(held);
}
#[test]
fn single_serialises_submissions_completely() {
let pool = single::Pool::new(8);
assert_eq!(pool.workers(), 0, "`single` has no workers to report");
assert_eq!(pool.requested_workers(), 8, "but it remembers the request");
let order = Arc::new(single::Mutex::new(Vec::new()));
let mut handles = Vec::new();
for i in 0..8u64 {
let order = Arc::clone(&order);
handles.push(pool.submit(move || {
order.lock().push(i);
i
}));
}
assert_eq!(*order.lock(), (0..8u64).collect::<Vec<_>>());
assert!(handles.iter().all(single::Handle::is_finished));
}
#[cfg(all(feature = "std", not(target_family = "wasm")))]
mod threaded {
use super::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::thread;
#[test]
fn a_mutex_actually_excludes_across_worker_threads() {
let pool = native_std::Pool::new(4);
assert_eq!(pool.workers(), 4);
let counter = Arc::new(native_std::Mutex::new(0u64));
let handles: Vec<_> = (0..8)
.map(|_| {
let counter = Arc::clone(&counter);
pool.submit(move || {
for _ in 0..2_000 {
*counter.lock() += 1;
}
})
})
.collect();
for handle in handles {
handle.join();
}
assert_eq!(*counter.lock(), 16_000);
}
#[test]
fn jobs_run_off_the_submitting_thread_when_there_are_workers() {
let here = thread::current().id();
let pool = native_std::Pool::new(2);
assert_ne!(pool.submit(move || thread::current().id()).join(), here);
let inline = native_std::Pool::new(0);
assert_eq!(inline.submit(move || thread::current().id()).join(), here);
}
#[test]
fn a_panicking_job_is_reported_at_join_and_the_pool_survives() {
let pool = native_std::Pool::new(1);
let handle = pool.submit(|| panic!("job exploded"));
let caught = catch_unwind(AssertUnwindSafe(move || handle.join()));
assert!(
caught.is_err(),
"the panic must surface at join, not vanish"
);
assert_eq!(pool.submit(|| 5u32).join(), 5);
}
#[cfg(debug_assertions)]
#[test]
fn rank_tracking_is_per_thread() {
let outer = LockRank::MACHINE.enter();
let pool = native_std::Pool::new(1);
let observed = pool
.submit(|| {
let inner = LockRank::MACHINE.enter();
let seen = held_rank();
drop(inner);
seen
})
.join();
assert_eq!(observed, Some(LockRank::MACHINE));
assert_eq!(held_rank(), Some(LockRank::MACHINE));
drop(outer);
}
#[test]
fn quiesce_waits_for_work_already_in_flight() {
let pool = native_std::Pool::new(2);
let done = Arc::new(AtomicUsize::new(0));
for _ in 0..8 {
let done = Arc::clone(&done);
pool.submit(move || {
let mut acc = 0u64;
for i in 0..200_000u64 {
acc = acc.wrapping_add(i);
}
::core::hint::black_box(acc);
done.fetch_add(1, Ordering::SeqCst);
});
}
pool.quiesce();
assert_eq!(done.load(Ordering::SeqCst), 8);
}
}
#[test]
fn a_global_guard_is_exclusive_and_releases_on_drop() {
let cell = Global::new(7u32);
{
let mut guard = cell.lock();
*guard += 1;
assert!(
cell.try_lock().is_none(),
"a held lock must refuse a second acquisition"
);
}
assert_eq!(*cell.lock(), 8);
assert_eq!(cell.rank(), LockRank::LEAF);
let mut owned = Global::with_rank(LockRank::MACHINE, 1u8);
assert_eq!(owned.rank(), LockRank::MACHINE);
*owned.get_mut() = 2;
assert_eq!(owned.into_inner(), 2);
assert_eq!(cell.into_inner(), 8);
}
#[test]
fn a_static_global_survives_the_whole_harness_hammering_it() {
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
static TABLE: Global<BTreeMap<String, u64>> = Global::new(BTreeMap::new());
const THREADS: u64 = 8;
const KEYS: u64 = 250;
std::thread::scope(|scope| {
for _ in 0..THREADS {
scope.spawn(|| {
for key in 0..KEYS {
let mut table = TABLE.lock();
*table.entry(format!("k{key}")).or_insert(0) += 1;
}
});
}
});
let table = TABLE.lock();
assert_eq!(table.len() as u64, KEYS);
assert!(
table.values().all(|&seen| seen == THREADS),
"every key must have been incremented once per thread"
);
}
#[test]
fn no_static_in_this_crate_holds_a_lock_meant_for_machine_state() {
use alloc::format;
use alloc::string::String;
use std::path::Path;
fn declared_type(line: &str) -> Option<&str> {
let line = line.trim_start();
if line.starts_with("//") {
return None;
}
let rest = ["pub ", "pub(crate) ", "pub(super) ", "pub(in crate) "]
.iter()
.find_map(|vis| line.strip_prefix(vis))
.unwrap_or(line);
let rest = rest.strip_prefix("static ")?;
let (_, ty) = rest.split_once(':')?;
Some(ty.split('=').next().unwrap_or(ty))
}
fn scan(dir: &Path, found: &mut Vec<String>) {
let entries = std::fs::read_dir(dir).expect("the crate's own source is readable");
for entry in entries {
let path = entry.expect("a readable directory entry").path();
if path.is_dir() {
scan(&path, found);
continue;
}
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let text = std::fs::read_to_string(&path).expect("a readable source file");
let lines: Vec<&str> = text.lines().collect();
for (at, line) in lines.iter().enumerate() {
if declared_type(line).is_none() {
continue;
}
let window = lines[at..lines.len().min(at + 3)].join(" ");
let Some(ty) = declared_type(&window) else {
continue;
};
if ty.contains("Mutex<") || ty.contains("RwLock<") {
found.push(format!("{}:{}:{}", path.display(), at + 1, line.trim()));
}
}
}
}
let mut found = Vec::new();
scan(
Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src")),
&mut found,
);
assert!(
found.is_empty(),
"a `static` may not hold a lock meant for machine state; use \
`core::sync::Global`, which waits for another thread instead of \
reporting it as a deadlock:\n {}",
found.join("\n ")
);
}
}