#![cfg_attr(
not(target_os = "linux"),
expect(
clippy::unwrap_used,
reason = "ratchet MOIRAI-UNWRAP-1: pre-existing debt"
)
)]
use std::cell::UnsafeCell;
use std::fmt;
use std::hint;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::Ordering;
#[cfg(not(target_os = "linux"))]
use std::sync::atomic::AtomicBool;
#[cfg(target_os = "linux")]
use std::sync::atomic::AtomicI32;
const MAX_SPIN_ATTEMPTS: usize = 64;
#[cfg(target_os = "linux")]
mod futex {
const FUTEX_WAIT: i32 = 0;
const FUTEX_WAKE: i32 = 1;
pub fn futex_wait(addr: *const i32, expected: i32) -> i32 {
unsafe {
libc::syscall(
libc::SYS_futex,
addr,
FUTEX_WAIT,
expected,
std::ptr::null::<libc::timespec>(),
std::ptr::null::<i32>(),
0,
) as i32
}
}
pub fn futex_wake(addr: *const i32, num_waiters: i32) -> i32 {
unsafe {
libc::syscall(
libc::SYS_futex,
addr,
FUTEX_WAKE,
num_waiters,
std::ptr::null::<libc::timespec>(),
std::ptr::null::<i32>(),
0,
) as i32
}
}
}
pub struct FutexMutex<T> {
#[cfg(target_os = "linux")]
state: AtomicI32, #[cfg(not(target_os = "linux"))]
locked: AtomicBool,
#[cfg(not(target_os = "linux"))]
waiters: std::sync::atomic::AtomicUsize,
#[cfg(not(target_os = "linux"))]
fallback: std::sync::Mutex<()>,
#[cfg(not(target_os = "linux"))]
condvar: std::sync::Condvar,
data: UnsafeCell<T>,
}
impl<T> fmt::Debug for FutexMutex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(target_os = "linux")]
let state = self.state.load(Ordering::Relaxed);
#[cfg(not(target_os = "linux"))]
let locked = self.locked.load(Ordering::Relaxed);
let mut d = f.debug_struct("FutexMutex");
#[cfg(target_os = "linux")]
d.field("state", &state);
#[cfg(not(target_os = "linux"))]
d.field("locked", &locked);
d.finish_non_exhaustive()
}
}
unsafe impl<T: Send> Send for FutexMutex<T> {}
unsafe impl<T: Send> Sync for FutexMutex<T> {}
impl<T> FutexMutex<T> {
pub const fn new(data: T) -> Self {
Self {
#[cfg(target_os = "linux")]
state: AtomicI32::new(0),
#[cfg(not(target_os = "linux"))]
locked: AtomicBool::new(false),
#[cfg(not(target_os = "linux"))]
waiters: std::sync::atomic::AtomicUsize::new(0),
#[cfg(not(target_os = "linux"))]
fallback: std::sync::Mutex::new(()),
#[cfg(not(target_os = "linux"))]
condvar: std::sync::Condvar::new(),
data: UnsafeCell::new(data),
}
}
pub fn lock(&self) -> FutexMutexGuard<'_, T> {
for _ in 0..MAX_SPIN_ATTEMPTS {
if self.try_lock_immediate() {
return FutexMutexGuard {
mutex: self,
_phantom: std::marker::PhantomData,
};
}
hint::spin_loop();
}
self.lock_slow();
FutexMutexGuard {
mutex: self,
_phantom: std::marker::PhantomData,
}
}
pub fn try_lock(&self) -> Option<FutexMutexGuard<'_, T>> {
if self.try_lock_immediate() {
Some(FutexMutexGuard {
mutex: self,
_phantom: std::marker::PhantomData,
})
} else {
None
}
}
#[inline]
fn try_lock_immediate(&self) -> bool {
#[cfg(target_os = "linux")]
{
self.state
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
#[cfg(not(target_os = "linux"))]
{
if self.locked.load(Ordering::Relaxed) {
false
} else {
!self.locked.swap(true, Ordering::Acquire)
}
}
}
#[cold]
fn lock_slow(&self) {
#[cfg(target_os = "linux")]
{
let mut state = self.state.load(Ordering::Relaxed);
if state == 0 {
match self
.state
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
{
Ok(_) => return,
Err(s) => state = s,
}
}
loop {
if state != 2 {
state = self.state.swap(2, Ordering::Acquire);
if state == 0 {
return;
}
}
futex::futex_wait(self.state.as_ptr(), 2);
state = self.state.load(Ordering::Relaxed);
}
}
#[cfg(not(target_os = "linux"))]
{
self.waiters.fetch_add(1, Ordering::Relaxed);
std::sync::atomic::fence(Ordering::SeqCst);
let mut guard = self.fallback.lock().unwrap();
loop {
if !self.locked.swap(true, Ordering::Acquire) {
self.waiters.fetch_sub(1, Ordering::Relaxed);
return;
}
guard = self.condvar.wait(guard).unwrap();
}
}
}
fn unlock(&self) {
#[cfg(target_os = "linux")]
{
if self.state.swap(0, Ordering::Release) == 2 {
futex::futex_wake(self.state.as_ptr(), 1);
}
}
#[cfg(not(target_os = "linux"))]
{
self.locked.store(false, Ordering::Release);
std::sync::atomic::fence(Ordering::SeqCst);
if self.waiters.load(Ordering::Acquire) > 0 {
let _guard = self.fallback.lock().unwrap();
self.condvar.notify_one();
}
}
}
}
pub struct FutexMutexGuard<'a, T> {
mutex: &'a FutexMutex<T>,
_phantom: std::marker::PhantomData<T>,
}
impl<'a, T> Drop for FutexMutexGuard<'a, T> {
fn drop(&mut self) {
self.mutex.unlock();
}
}
impl<'a, T> Deref for FutexMutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.mutex.data.get() }
}
}
impl<'a, T> DerefMut for FutexMutexGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.mutex.data.get() }
}
}
#[cfg(test)]
mod auto_traits {
use super::{FutexMutex, FutexMutexGuard};
use static_assertions::{assert_impl_all, assert_not_impl_any};
use std::cell::Cell;
assert_impl_all!(FutexMutex<u32>: Send, Sync);
assert_impl_all!(FutexMutexGuard<'static, u32>: Send, Sync);
#[allow(dead_code)]
fn guard_sync_requires_sync_data() {
assert_not_impl_any!(FutexMutexGuard<'static, Cell<u32>>: Sync);
}
}