blob: b10ded8e4d008f8192009fc1a25ae3ef4c9b8c7d [file] [log] [blame]
//@only-target: freebsd
//@compile-flags: -Zmiri-deterministic-concurrency -Zmiri-disable-isolation
use std::mem::{self, MaybeUninit};
use std::ptr::{self, addr_of};
use std::sync::atomic::AtomicU32;
use std::time::Instant;
use std::{io, thread};
fn wait_wake() {
fn wake_nobody() {
// Current thread waits on futex.
// New thread wakes up 0 threads waiting on that futex.
// Current thread should time out.
static mut FUTEX: u32 = 0;
let waker = thread::spawn(|| {
unsafe {
assert_eq!(
libc::_umtx_op(
addr_of!(FUTEX) as *mut _,
libc::UMTX_OP_WAKE_PRIVATE,
0, // wake up 0 waiters
ptr::null_mut::<libc::c_void>(),
ptr::null_mut::<libc::c_void>(),
),
0
);
}
});
// 10ms should be enough.
let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 10_000_000 };
let timeout_size_arg =
ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
unsafe {
assert_eq!(
libc::_umtx_op(
addr_of!(FUTEX) as *mut _,
libc::UMTX_OP_WAIT_UINT_PRIVATE,
0,
timeout_size_arg,
&mut timeout as *mut _ as _,
),
-1
);
// Main thread did not get woken up, so it timed out.
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
waker.join().unwrap();
}
fn wake_two_of_three() {
// We create 2 threads that wait on a futex with a 100ms timeout.
// The main thread wakes up 2 threads waiting on this futex and after this
// checks that only those threads woke up and the other one timed out.
static mut FUTEX: u32 = 0;
fn waiter() -> bool {
let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 100_000_000 };
let timeout_size_arg =
ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
unsafe {
libc::_umtx_op(
addr_of!(FUTEX) as *mut _,
libc::UMTX_OP_WAIT_UINT_PRIVATE,
0, // FUTEX is 0
timeout_size_arg,
&mut timeout as *mut _ as _,
);
// Return true if this thread woke up.
io::Error::last_os_error().raw_os_error().unwrap() != libc::ETIMEDOUT
}
}
let t1 = thread::spawn(waiter);
let t2 = thread::spawn(waiter);
let t3 = thread::spawn(waiter);
// Run all the waiters, so they can go to sleep.
thread::yield_now();
// Wake up 2 thread and make sure 1 is still waiting.
unsafe {
assert_eq!(
libc::_umtx_op(
addr_of!(FUTEX) as *mut _,
libc::UMTX_OP_WAKE_PRIVATE,
2,
ptr::null_mut::<libc::c_void>(),
ptr::null_mut::<libc::c_void>(),
),
0
);
}
// Treat the booleans as numbers to simplify checking how many threads were woken up.
let t1 = t1.join().unwrap() as usize;
let t2 = t2.join().unwrap() as usize;
let t3 = t3.join().unwrap() as usize;
let woken_up_count = t1 + t2 + t3;
assert!(woken_up_count == 2, "Expected 2 threads to wake up got: {woken_up_count}");
}
wake_nobody();
wake_two_of_three();
}
fn wake_dangling() {
let futex = Box::new(0);
let ptr: *const u32 = &*futex;
drop(futex);
// Expect error since this is now "unmapped" memory.
unsafe {
assert_eq!(
libc::_umtx_op(
ptr as *const AtomicU32 as *mut _,
libc::UMTX_OP_WAKE_PRIVATE,
0,
ptr::null_mut::<libc::c_void>(),
ptr::null_mut::<libc::c_void>(),
),
-1
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EFAULT);
}
}
fn wait_wrong_val() {
let futex: u32 = 123;
// Wait with a wrong value just returns 0
unsafe {
assert_eq!(
libc::_umtx_op(
ptr::from_ref(&futex).cast_mut().cast(),
libc::UMTX_OP_WAIT_UINT_PRIVATE,
456,
ptr::null_mut::<libc::c_void>(),
ptr::null_mut::<libc::c_void>(),
),
0
);
}
}
fn wait_relative_timeout() {
fn without_timespec() {
let start = Instant::now();
let futex: u32 = 123;
let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 };
let timeout_size_arg =
ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
// Wait for 200ms, with nobody waking us up early
unsafe {
assert_eq!(
libc::_umtx_op(
ptr::from_ref(&futex).cast_mut().cast(),
libc::UMTX_OP_WAIT_UINT_PRIVATE,
123,
timeout_size_arg,
&mut timeout as *mut _ as _,
),
-1
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
assert!((200..1000).contains(&start.elapsed().as_millis()));
}
fn with_timespec() {
let futex: u32 = 123;
let mut timeout = libc::_umtx_time {
_timeout: libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 },
_flags: 0,
_clockid: libc::CLOCK_MONOTONIC as u32,
};
let timeout_size_arg =
ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::_umtx_time>());
let start = Instant::now();
// Wait for 200ms, with nobody waking us up early
unsafe {
assert_eq!(
libc::_umtx_op(
ptr::from_ref(&futex).cast_mut().cast(),
libc::UMTX_OP_WAIT_UINT_PRIVATE,
123,
timeout_size_arg,
&mut timeout as *mut _ as _,
),
-1
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
assert!((200..1000).contains(&start.elapsed().as_millis()));
}
without_timespec();
with_timespec();
}
fn wait_absolute_timeout() {
let start = Instant::now();
// Get the current monotonic timestamp as timespec.
let mut timeout = unsafe {
let mut now: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
assert_eq!(libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()), 0);
now.assume_init()
};
// Add 200ms.
timeout.tv_nsec += 200_000_000;
if timeout.tv_nsec > 1_000_000_000 {
timeout.tv_nsec -= 1_000_000_000;
timeout.tv_sec += 1;
}
// Create umtx_timeout struct with that absolute timeout.
let umtx_timeout = libc::_umtx_time {
_timeout: timeout,
_flags: libc::UMTX_ABSTIME,
_clockid: libc::CLOCK_MONOTONIC as u32,
};
let umtx_timeout_ptr = &umtx_timeout as *const _;
let umtx_timeout_size = ptr::without_provenance_mut(mem::size_of_val(&umtx_timeout));
let futex: u32 = 123;
// Wait for 200ms from now, with nobody waking us up early.
unsafe {
assert_eq!(
libc::_umtx_op(
ptr::from_ref(&futex).cast_mut().cast(),
libc::UMTX_OP_WAIT_UINT_PRIVATE,
123,
umtx_timeout_size,
umtx_timeout_ptr as *mut _,
),
-1
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
assert!((200..1000).contains(&start.elapsed().as_millis()));
}
fn main() {
wait_wake();
wake_dangling();
wait_wrong_val();
wait_relative_timeout();
wait_absolute_timeout();
}