use crate::sync::atomic::{
AtomicU32,
Ordering::{Acquire, Relaxed, Release},
};
use crate::sys::futex::zircon::{
zx_futex_wait, zx_futex_wake_single_owner, zx_handle_t, zx_thread_self, ZX_ERR_BAD_HANDLE,
ZX_ERR_BAD_STATE, ZX_ERR_INVALID_ARGS, ZX_ERR_TIMED_OUT, ZX_ERR_WRONG_TYPE, ZX_OK,
ZX_TIME_INFINITE,
};
const CONTESTED_BIT: u32 = 1;
const UNLOCKED: u32 = 0;
pub struct Mutex {
futex: AtomicU32,
}
#[inline]
fn to_state(owner: zx_handle_t) -> u32 {
owner
}
#[inline]
fn to_owner(state: u32) -> zx_handle_t {
state | CONTESTED_BIT
}
#[inline]
fn is_contested(state: u32) -> bool {
state & CONTESTED_BIT == 0
}
#[inline]
fn mark_contested(state: u32) -> u32 {
state & !CONTESTED_BIT
}
impl Mutex {
#[inline]
pub const fn new() -> Mutex {
Mutex { futex: AtomicU32::new(UNLOCKED) }
}
#[inline]
pub fn try_lock(&self) -> bool {
let thread_self = unsafe { zx_thread_self() };
self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed).is_ok()
}
#[inline]
pub fn lock(&self) {
let thread_self = unsafe { zx_thread_self() };
if let Err(state) =
self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed)
{
unsafe {
self.lock_contested(state, thread_self);
}
}
}
#[cold]
unsafe fn lock_contested(&self, mut state: u32, thread_self: zx_handle_t) {
let owned_state = mark_contested(to_state(thread_self));
loop {
let contested = mark_contested(state);
if is_contested(state)
|| self.futex.compare_exchange(state, contested, Relaxed, Relaxed).is_ok()
{
unsafe {
match zx_futex_wait(
&self.futex,
AtomicU32::new(contested),
to_owner(state),
ZX_TIME_INFINITE,
) {
ZX_OK | ZX_ERR_BAD_STATE | ZX_ERR_TIMED_OUT => (),
ZX_ERR_INVALID_ARGS | ZX_ERR_BAD_HANDLE | ZX_ERR_WRONG_TYPE => {
panic!(
"either the current thread is trying to lock a mutex it has
already locked, or the previous owner did not unlock the mutex
before exiting"
)
}
error => panic!("unexpected error in zx_futex_wait: {error}"),
}
}
}
match self.futex.compare_exchange(UNLOCKED, owned_state, Acquire, Relaxed) {
Ok(_) => return,
Err(updated) => state = updated,
}
}
}
#[inline]
pub unsafe fn unlock(&self) {
if is_contested(self.futex.swap(UNLOCKED, Release)) {
self.wake();
}
}
#[cold]
fn wake(&self) {
unsafe {
zx_futex_wake_single_owner(&self.futex);
}
}
}