#![warn(unused_must_use)]
use core::sync::atomic::{AtomicU32, Ordering};
#[derive(thiserror::Error, strum::IntoStaticStr, Debug, Copy, Clone, Eq, PartialEq)]
pub enum TimeoutError {
#[error("Timeout")]
Timeout,
}
impl From<TimeoutError> for bun_core::Error {
fn from(_: TimeoutError) -> Self {
bun_core::err!("Timeout")
}
}
#[cold]
pub fn wait(ptr: &AtomicU32, expect: u32, timeout_ns: Option<u64>) -> Result<(), TimeoutError> {
if let Some(t) = timeout_ns {
if t == 0 {
if ptr.load(Ordering::SeqCst) != expect {
return Ok(());
}
return Err(TimeoutError::Timeout);
}
}
imp::wait(ptr, expect, timeout_ns)
}
#[cold]
pub fn wait_forever(ptr: &AtomicU32, expect: u32) {
loop {
match imp::wait(ptr, expect, None) {
Err(TimeoutError::Timeout) => continue,
Ok(()) => break,
}
}
}
#[cold]
pub fn wake(ptr: &AtomicU32, max_waiters: u32) {
wake_raw(core::ptr::from_ref(ptr), max_waiters);
}
#[cold]
pub(crate) fn wake_raw(ptr: *const AtomicU32, max_waiters: u32) {
if max_waiters == 0 {
return;
}
imp::wake(ptr, max_waiters);
}
#[cfg(target_vendor = "apple")]
use darwin_impl as imp;
#[cfg(target_os = "freebsd")]
use freebsd_impl as imp;
#[cfg(any(target_os = "linux", target_os = "android"))]
use linux_impl as imp;
#[cfg(not(any(
windows,
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_arch = "wasm32",
)))]
use unsupported_impl as imp;
#[cfg(target_arch = "wasm32")]
use wasm_impl as imp;
#[cfg(windows)]
use windows_impl as imp;
#[cfg(not(any(
windows,
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_arch = "wasm32",
)))]
mod unsupported_impl {
use super::*;
pub(super) fn wait(
_ptr: &AtomicU32,
_expect: u32,
_timeout: Option<u64>,
) -> Result<(), TimeoutError> {
unsupported()
}
pub(super) fn wake(_ptr: *const AtomicU32, _max_waiters: u32) {
unsupported()
}
fn unsupported() -> ! {
unreachable!("Unsupported operating system for Futex");
}
}
#[cfg(windows)]
mod windows_impl {
use super::*;
use bun_sys::windows;
use core::ffi::c_void;
pub(super) fn wait(
ptr: &AtomicU32,
expect: u32,
timeout: Option<u64>,
) -> Result<(), TimeoutError> {
let timeout_value: windows::LARGE_INTEGER;
let timeout_ptr: *const windows::LARGE_INTEGER = match timeout {
Some(delay) => {
timeout_value = -windows::LARGE_INTEGER::try_from(delay / 100).unwrap();
&timeout_value
}
None => core::ptr::null(),
};
let rc = unsafe {
windows::ntdll::RtlWaitOnAddress(
ptr.as_ptr().cast::<c_void>(),
(&expect as *const u32).cast::<c_void>(),
core::mem::size_of::<u32>(),
timeout_ptr,
)
};
match rc {
windows::NTSTATUS::SUCCESS => Ok(()),
windows::NTSTATUS::TIMEOUT => {
debug_assert!(timeout.is_some());
Err(TimeoutError::Timeout)
}
_ => panic!("Unexpected RtlWaitOnAddress() return code"),
}
}
pub(super) fn wake(ptr: *const AtomicU32, max_waiters: u32) {
let address: *const c_void = ptr.cast();
debug_assert!(max_waiters != 0);
unsafe {
match max_waiters {
1 => windows::ntdll::RtlWakeAddressSingle(address),
_ => windows::ntdll::RtlWakeAddressAll(address),
}
}
}
}
#[cfg(target_vendor = "apple")]
mod darwin_impl {
use super::*;
use bun_core::time::NS_PER_US;
use bun_sys::darwin as c;
use core::ffi::c_void;
pub(super) fn wait(
ptr: &AtomicU32,
expect: u32,
timeout: Option<u64>,
) -> Result<(), TimeoutError> {
let supports_ulock_wait2: bool = true;
let mut timeout_ns: u64 = 0;
if let Some(delay) = timeout {
debug_assert!(delay != 0); timeout_ns = delay;
}
let mut timeout_overflowed = false;
let addr: *const c_void = ptr.as_ptr().cast();
let flags = c::UL {
op: c::ULOp::COMPARE_AND_WAIT,
no_errno: true,
..Default::default()
};
let status = unsafe {
'blk: {
if supports_ulock_wait2 {
break 'blk c::__ulock_wait2(flags, addr, expect as u64, timeout_ns, 0);
}
let timeout_us = match u32::try_from(timeout_ns / NS_PER_US) {
Ok(v) => v,
Err(_) => {
timeout_overflowed = true;
u32::MAX
}
};
c::__ulock_wait(flags, addr, expect as u64, timeout_us)
}
};
if status >= 0 {
return Ok(());
}
match c::E::from_raw((-status) as u16) {
c::E::EINTR => Ok(()),
c::E::EFAULT => Ok(()),
c::E::ETIMEDOUT => {
debug_assert!(timeout.is_some());
if !timeout_overflowed {
return Err(TimeoutError::Timeout);
}
Ok(())
}
_ => panic!("Unexpected __ulock_wait() return code"),
}
}
pub(super) fn wake(ptr: *const AtomicU32, max_waiters: u32) {
let flags = c::UL {
op: c::ULOp::COMPARE_AND_WAIT,
no_errno: true,
wake_all: max_waiters > 1,
..Default::default()
};
loop {
let addr: *const c_void = ptr.cast();
let status = unsafe { c::__ulock_wake(flags, addr, 0) };
if status >= 0 {
return;
}
match c::E::from_raw((-status) as u16) {
c::E::EINTR => continue, c::E::EFAULT => panic!("__ulock_wake() returned EFAULT unexpectedly"), c::E::ENOENT => return, c::E::EALREADY => panic!("__ulock_wake() returned EALREADY unexpectedly"), _ => panic!("Unexpected __ulock_wake() return code"),
}
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux_impl {
use super::*;
use bun_core::time::NS_PER_S;
pub(super) fn wait(
ptr: &AtomicU32,
expect: u32,
timeout: Option<u64>,
) -> Result<(), TimeoutError> {
use bun_sys::linux;
let mut ts: linux::timespec = unsafe { bun_core::ffi::zeroed_unchecked() };
if let Some(timeout_ns) = timeout {
ts.sec = <_>::try_from(timeout_ns / NS_PER_S).unwrap();
ts.nsec = <_>::try_from(timeout_ns % NS_PER_S).unwrap();
}
let rc = unsafe {
linux::futex_4arg(
ptr.as_ptr().cast(),
linux::FutexOp {
cmd: linux::FutexCmd::WAIT,
private: true,
},
expect,
if timeout.is_some() {
&raw const ts
} else {
core::ptr::null()
},
)
};
match linux::E::init(rc) {
linux::E::SUCCESS => Ok(()), linux::E::INTR => Ok(()), linux::E::AGAIN => Ok(()), linux::E::TIMEDOUT => {
debug_assert!(timeout.is_some());
Err(TimeoutError::Timeout)
}
linux::E::INVAL => Ok(()), linux::E::FAULT => panic!("futex_wait() returned EFAULT unexpectedly"), err => {
panic!(
"Unexpected futex_wait() return code: {} - {}",
rc,
<&'static str>::from(err),
);
}
}
}
pub(super) fn wake(ptr: *const AtomicU32, max_waiters: u32) {
use bun_sys::linux;
let val: u32 = match i32::try_from(max_waiters) {
Ok(v) => v as u32,
Err(_) => i32::MAX as u32,
};
let rc = unsafe {
linux::futex_3arg(
ptr.cast(),
linux::FutexOp {
cmd: linux::FutexCmd::WAKE,
private: true,
},
val,
)
};
match linux::E::init(rc) {
linux::E::SUCCESS => {} linux::E::INVAL => {} linux::E::FAULT => {} _ => panic!("Unexpected futex_wake() return code"),
}
}
}
#[cfg(target_os = "freebsd")]
mod freebsd_impl {
use super::*;
use bun_core::time::NS_PER_S;
use bun_sys::E;
use core::ffi::{c_int, c_ulong, c_void};
pub(super) fn wait(
ptr: &AtomicU32,
expect: u32,
timeout: Option<u64>,
) -> Result<(), TimeoutError> {
let mut tm_size: usize = 0;
let mut tm: libc::_umtx_time = bun_core::ffi::zeroed();
let mut tm_ptr: *mut c_void = core::ptr::null_mut();
if let Some(timeout_ns) = timeout {
tm._flags = 0; tm._clockid = libc::CLOCK_MONOTONIC as u32;
tm._timeout.tv_sec = <_>::try_from(timeout_ns / NS_PER_S).unwrap();
tm._timeout.tv_nsec = <_>::try_from(timeout_ns % NS_PER_S).unwrap();
tm_size = core::mem::size_of::<libc::_umtx_time>();
tm_ptr = (&mut tm as *mut libc::_umtx_time).cast();
}
let rc = unsafe {
libc::_umtx_op(
ptr.as_ptr().cast::<c_void>(),
libc::UMTX_OP_WAIT_UINT_PRIVATE,
expect as c_ulong,
tm_size as *mut c_void,
tm_ptr,
)
};
match bun_sys::get_errno(rc) {
E::SUCCESS => Ok(()),
E::EFAULT => panic!("_umtx_op() WAIT returned EFAULT unexpectedly"),
E::EINVAL => Ok(()), E::ETIMEDOUT => {
debug_assert!(timeout.is_some());
Err(TimeoutError::Timeout)
}
E::EINTR => Ok(()), _ => panic!("Unexpected _umtx_op() WAIT return code"),
}
}
pub(super) fn wake(ptr: *const AtomicU32, max_waiters: u32) {
let n: c_ulong = max_waiters.min(c_int::MAX as u32) as c_ulong;
let rc = unsafe {
libc::_umtx_op(
ptr.cast::<c_void>().cast_mut(),
libc::UMTX_OP_WAKE_PRIVATE,
n,
core::ptr::null_mut(), core::ptr::null_mut(), )
};
match bun_sys::get_errno(rc) {
E::SUCCESS => {}
E::EFAULT => {} E::EINVAL => panic!("_umtx_op() WAKE returned EINVAL unexpectedly"),
_ => panic!("Unexpected _umtx_op() WAKE return code"),
}
}
}
#[cfg(target_arch = "wasm32")]
mod wasm_impl {
use super::*;
pub(crate) fn wait(
ptr: &AtomicU32,
expect: u32,
timeout: Option<u64>,
) -> Result<(), TimeoutError> {
#[cfg(not(target_feature = "atomics"))]
compile_error!("WASI target missing cpu feature 'atomics'");
let to: i64 = match timeout {
Some(to) => i64::try_from(to).expect("int cast"),
None => -1,
};
let result = unsafe {
core::arch::wasm32::memory_atomic_wait32(ptr.as_ptr().cast::<i32>(), expect as i32, to)
};
match result {
0 => Ok(()), 1 => Ok(()), 2 => Err(TimeoutError::Timeout),
_ => panic!("Unexpected memory.atomic.wait32() return code"),
}
}
pub fn wake(ptr: *const AtomicU32, max_waiters: u32) {
#[cfg(not(target_feature = "atomics"))]
compile_error!("WASI target missing cpu feature 'atomics'");
debug_assert!(max_waiters != 0);
let woken_count = unsafe {
core::arch::wasm32::memory_atomic_notify(ptr.cast::<i32>().cast_mut(), max_waiters)
};
let _ = woken_count; }
}
pub(crate) struct Deadline {
timeout: Option<u64>,
started: std::time::Instant,
}
impl Deadline {
pub(crate) fn init(expires_in_ns: Option<u64>) -> Deadline {
Deadline {
timeout: expires_in_ns,
started: std::time::Instant::now(),
}
}
#[cold]
pub(crate) fn wait(&mut self, ptr: &AtomicU32, expect: u32) -> Result<(), TimeoutError> {
let Some(timeout_ns) = self.timeout else {
wait_forever(ptr, expect);
return Ok(());
};
let elapsed_ns = u64::try_from(self.started.elapsed().as_nanos()).unwrap_or(u64::MAX);
let until_timeout_ns = timeout_ns.saturating_sub(elapsed_ns);
wait(ptr, expect, Some(until_timeout_ns))
}
}