mod errors;
mod scope;
mod sys;
mod timeout;
pub mod op;
use op::OpAndCmp;
use std::marker::PhantomData;
use std::sync::atomic::AtomicU32;
use std::time::Duration;
use sys::{Error, FutexCall};
use timeout::as_timespec;
pub use errors::*;
pub use scope::{Private, Scope, Shared};
pub use timeout::Timeout;
#[repr(transparent)]
pub struct Futex<Scope> {
pub value: AtomicU32,
phantom: PhantomData<Scope>,
}
#[repr(transparent)]
pub struct PiFutex<Scope> {
pub value: AtomicU32,
phantom: PhantomData<Scope>,
}
pub trait AsFutex<S> {
fn as_futex(&self) -> &Futex<S>;
fn as_pi_futex(&self) -> &PiFutex<S>;
}
impl<S> AsFutex<S> for AtomicU32 {
#[must_use]
#[inline]
fn as_futex(&self) -> &Futex<S> {
unsafe { std::mem::transmute(self) }
}
#[inline]
#[must_use]
fn as_pi_futex(&self) -> &PiFutex<S> {
unsafe { std::mem::transmute(self) }
}
}
impl<S> Futex<S> {
#[inline]
pub const fn new(value: u32) -> Self {
Self {
value: AtomicU32::new(value),
phantom: PhantomData,
}
}
}
impl<S> PiFutex<S> {
#[inline]
pub const fn new(value: u32) -> Self {
Self {
value: AtomicU32::new(value),
phantom: PhantomData,
}
}
pub const WAITERS: u32 = 0x8000_0000;
pub const OWNER_DIED: u32 = 0x4000_0000;
pub const TID_MASK: u32 = 0x3fffffff;
}
impl<S> Default for Futex<S> {
fn default() -> Self {
Self::new(0)
}
}
impl<S> Default for PiFutex<S> {
fn default() -> Self {
Self::new(0)
}
}
impl<S: Scope> Futex<S> {
#[inline]
pub fn wait(&self, expected_value: u32) -> Result<(), WaitError> {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAIT + S::futex_flag())
.uaddr(&self.value)
.val(expected_value)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(WaitError::WrongValue),
Err(Error(libc::EINTR)) => Err(WaitError::Interrupted),
Err(e) => e.panic("FUTEX_WAIT"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn wait_for(&self, expected_value: u32, timeout: Duration) -> Result<(), TimedWaitError> {
let timeout = as_timespec(timeout);
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAIT + S::futex_flag())
.uaddr(&self.value)
.val(expected_value)
.timeout(&timeout)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TimedWaitError::WrongValue),
Err(Error(libc::EINTR)) => Err(TimedWaitError::Interrupted),
Err(Error(libc::ETIMEDOUT)) => Err(TimedWaitError::TimedOut),
Err(e) => e.panic("FUTEX_WAIT"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn wake(&self, n: i32) -> i32 {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAKE + S::futex_flag())
.uaddr(&self.value)
.val(n as u32)
.call()
};
match r {
Err(e) => e.panic("FUTEX_WAKE"),
Ok(v) => v,
}
}
#[inline]
pub fn requeue(&self, n_wake: i32, to: &Futex<S>, n_requeue: i32) -> i32 {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_REQUEUE + S::futex_flag())
.uaddr(&self.value)
.uaddr2(&to.value)
.val(n_wake as u32)
.val2(n_requeue as u32)
.call()
};
match r {
Err(e) => e.panic("FUTEX_REQUEUE"),
Ok(v) => v,
}
}
#[inline]
pub fn cmp_requeue(
&self,
expected_value: u32,
n_wake: i32,
to: &Futex<S>,
n_requeue: i32,
) -> Result<i32, WrongValueError> {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_CMP_REQUEUE + S::futex_flag())
.uaddr(&self.value)
.uaddr2(&to.value)
.val(n_wake as u32)
.val2(n_requeue as u32)
.val3(expected_value)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(WrongValueError::WrongValue),
Err(e) => e.panic("FUTEX_CMP_REQUEUE"),
Ok(v) => Ok(v),
}
}
#[inline]
pub fn wait_bitset(&self, expected_value: u32, bitset: u32) -> Result<(), WaitError> {
let r = unsafe {
FutexCall::new()
.uaddr(&self.value)
.futex_op(libc::FUTEX_WAIT_BITSET + S::futex_flag())
.val(expected_value)
.val3(bitset)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(WaitError::WrongValue),
Err(Error(libc::EINTR)) => Err(WaitError::Interrupted),
Err(e) => e.panic("FUTEX_WAIT_BITSET"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn wait_bitset_until(
&self,
expected_value: u32,
bitset: u32,
timeout: impl Timeout,
) -> Result<(), TimedWaitError> {
let timeout = timeout.as_timespec();
let r = unsafe {
FutexCall::new()
.uaddr(&self.value)
.futex_op(libc::FUTEX_WAIT_BITSET + timeout.0 + S::futex_flag())
.val(expected_value)
.val3(bitset)
.timeout(&timeout.1)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TimedWaitError::WrongValue),
Err(Error(libc::EINTR)) => Err(TimedWaitError::Interrupted),
Err(Error(libc::ETIMEDOUT)) => Err(TimedWaitError::TimedOut),
Err(e) => e.panic("FUTEX_WAIT_BITSET"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn wake_bitset(&self, n: i32, bitset: u32) -> i32 {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAKE_BITSET + S::futex_flag())
.uaddr(&self.value)
.val(n as u32)
.val3(bitset)
.call()
};
match r {
Err(e) => e.panic("FUTEX_WAKE_BITSET"),
Ok(v) => v,
}
}
#[inline]
pub fn wake_op(&self, n: i32, second: &Futex<S>, op: OpAndCmp, n2: i32) -> i32 {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAKE_OP + S::futex_flag())
.uaddr(&self.value)
.uaddr2(&second.value)
.val(n as u32)
.val2(n2 as u32)
.val3(op.raw_bits())
.call()
};
match r {
Err(e) => e.panic("FUTEX_WAKE_OP"),
Ok(v) => v,
}
}
#[inline]
pub fn cmp_requeue_pi(
&self,
expected_value: i32,
to: &PiFutex<S>,
n_requeue: i32,
) -> Result<i32, TryAgainError> {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_CMP_REQUEUE_PI + S::futex_flag())
.uaddr(&self.value)
.uaddr2(&to.value)
.val(1)
.val2(n_requeue as u32)
.val3(expected_value as u32)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TryAgainError::TryAgain),
Err(e) => e.panic("FUTEX_CMP_REQUEUE_PI"),
Ok(v) => Ok(v),
}
}
#[inline]
pub fn wait_requeue_pi(
&self,
expected_value: u32,
second: &PiFutex<S>,
) -> Result<(), RequeuePiError> {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAIT_REQUEUE_PI + S::futex_flag())
.uaddr(&self.value)
.uaddr2(&second.value)
.val(expected_value)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(RequeuePiError::TryAgain),
Err(e) => e.panic("FUTEX_WAIT_REQUEUE_PI"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn wait_requeue_pi_until(
&self,
expected_value: u32,
second: &PiFutex<S>,
timeout: impl Timeout,
) -> Result<(), TimedRequeuePiError> {
let timeout = timeout.as_timespec();
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_WAIT_REQUEUE_PI + timeout.0 + S::futex_flag())
.uaddr(&self.value)
.uaddr2(&second.value)
.val(expected_value)
.timeout(&timeout.1)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TimedRequeuePiError::TryAgain),
Err(Error(libc::ETIMEDOUT)) => Err(TimedRequeuePiError::TimedOut),
Err(e) => e.panic("FUTEX_WAIT_REQUEUE_PI"),
Ok(_) => Ok(()),
}
}
}
impl<S: Scope> PiFutex<S> {
#[inline]
pub fn lock_pi(&self) -> Result<(), TryAgainError> {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_LOCK_PI + S::futex_flag())
.uaddr(&self.value)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TryAgainError::TryAgain),
Err(e) => e.panic("FUTEX_LOCK_PI"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn lock_pi_until(&self, timeout: impl Timeout) -> Result<(), TimedLockError> {
let (clock, timespec) = timeout.as_timespec();
let op = if clock == libc::FUTEX_CLOCK_REALTIME {
libc::FUTEX_LOCK_PI
} else {
libc::FUTEX_LOCK_PI2
};
let r = unsafe {
FutexCall::new()
.futex_op(op + S::futex_flag())
.uaddr(&self.value)
.timeout(×pec)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TimedLockError::TryAgain),
Err(Error(libc::ETIMEDOUT)) => Err(TimedLockError::TimedOut),
Err(e) if op == libc::FUTEX_LOCK_PI2 => e.panic("FUTEX_LOCK_PI2"),
Err(e) => e.panic("FUTEX_LOCK_PI"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn trylock_pi(&self) -> Result<(), TryAgainError> {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_TRYLOCK_PI + S::futex_flag())
.uaddr(&self.value)
.call()
};
match r {
Err(Error(libc::EAGAIN)) => Err(TryAgainError::TryAgain),
Err(e) => e.panic("FUTEX_LOCK_PI"),
Ok(_) => Ok(()),
}
}
#[inline]
pub fn unlock_pi(&self) {
let r = unsafe {
FutexCall::new()
.futex_op(libc::FUTEX_UNLOCK_PI + S::futex_flag())
.uaddr(&self.value)
.call()
};
if let Err(e) = r {
e.panic("FUTEX_UNLOCK_PI");
}
}
}
impl<S> std::fmt::Debug for Futex<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Futex")
.field("scope", &std::any::type_name::<S>())
.field("value", &self.value)
.finish()
}
}
impl<S> std::fmt::Debug for PiFutex<S> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("PiFutex")
.field("scope", &std::any::type_name::<S>())
.field("value", &self.value)
.finish()
}
}