blob: a28f08c3bb1a0a883780c260d50d6e54a1560bcf [file] [log] [blame]
//@only-target: darwin
//@compile-flags: -Zmiri-deterministic-concurrency
use std::time::{Duration, Instant};
use std::{io, ptr, thread};
fn wake_nobody() {
let futex = 0;
// Wake 1 waiter. Expect ENOENT as nobody is waiting.
unsafe {
assert_eq!(
libc::os_sync_wake_by_address_any(
ptr::from_ref(&futex).cast_mut().cast(),
size_of::<i32>(),
libc::OS_SYNC_WAKE_BY_ADDRESS_NONE
),
-1
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOENT);
}
}
fn wake_dangling() {
let futex = Box::new(0);
let ptr = ptr::from_ref(&futex).cast_mut().cast();
drop(futex);
// Expect error since this is now "unmapped" memory.
unsafe {
assert_eq!(
libc::os_sync_wake_by_address_any(
ptr,
size_of::<i32>(),
libc::OS_SYNC_WAKE_BY_ADDRESS_NONE
),
-1
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ENOENT);
}
}
fn wait_wrong_val() {
let futex: i32 = 123;
// Only wait if the futex value is 456.
unsafe {
assert_eq!(
libc::os_sync_wait_on_address(
ptr::from_ref(&futex).cast_mut().cast(),
456,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_NONE
),
0,
);
}
}
fn wait_timeout() {
let start = Instant::now();
let futex: i32 = 123;
// Wait for 200ms, with nobody waking us up early.
unsafe {
assert_eq!(
libc::os_sync_wait_on_address_with_timeout(
ptr::from_ref(&futex).cast_mut().cast(),
123,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
libc::OS_CLOCK_MACH_ABSOLUTE_TIME,
200_000_000,
),
-1,
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
assert!((200..1000).contains(&start.elapsed().as_millis()));
}
fn wait_absolute_timeout() {
let start = Instant::now();
// Get the current monotonic timestamp.
#[allow(deprecated)]
let mut deadline = unsafe { libc::mach_absolute_time() };
// Add 200ms.
// What we should be doing here is call `mach_timebase_info` to determine the
// unit used for `deadline`, but we know what Miri returns for that function:
// the unit is nanoseconds.
deadline += 200_000_000;
let futex: i32 = 123;
// Wait for 200ms from now, with nobody waking us up early.
unsafe {
assert_eq!(
libc::os_sync_wait_on_address_with_deadline(
ptr::from_ref(&futex).cast_mut().cast(),
123,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
libc::OS_CLOCK_MACH_ABSOLUTE_TIME,
deadline,
),
-1,
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
assert!((200..1000).contains(&start.elapsed().as_millis()));
}
fn wait_wake() {
let start = Instant::now();
static mut FUTEX: i32 = 0;
let t = thread::spawn(move || {
thread::sleep(Duration::from_millis(200));
unsafe {
assert_eq!(
libc::os_sync_wake_by_address_any(
(&raw const FUTEX).cast_mut().cast(),
size_of::<i32>(),
libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
),
0,
);
}
});
unsafe {
assert_eq!(
libc::os_sync_wait_on_address(
(&raw const FUTEX).cast_mut().cast(),
0,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
),
0,
);
}
// When running this in stress-gc mode, things can take quite long.
// So the timeout is 3000 ms.
assert!((200..3000).contains(&start.elapsed().as_millis()));
t.join().unwrap();
}
fn wait_wake_multiple() {
let val = 0i32;
let futex = &val;
thread::scope(|s| {
// Spawn some threads and make them wait on the futex.
for i in 0..4 {
s.spawn(move || unsafe {
assert_eq!(
libc::os_sync_wait_on_address(
ptr::from_ref(futex).cast_mut().cast(),
0,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
),
// The last two threads will be woken at the same time,
// but for the first two threads the remaining number
// of waiters should be strictly decreasing.
if i < 2 { 3 - i } else { 0 },
);
});
thread::yield_now();
}
// Wake the threads up again.
unsafe {
assert_eq!(
libc::os_sync_wake_by_address_any(
ptr::from_ref(futex).cast_mut().cast(),
size_of::<i32>(),
libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
),
0
);
assert_eq!(
libc::os_sync_wake_by_address_any(
ptr::from_ref(futex).cast_mut().cast(),
size_of::<i32>(),
libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
),
0
);
// Wake both remaining threads at the same time.
assert_eq!(
libc::os_sync_wake_by_address_all(
ptr::from_ref(futex).cast_mut().cast(),
size_of::<i32>(),
libc::OS_SYNC_WAKE_BY_ADDRESS_NONE,
),
0
);
}
})
}
fn param_mismatch() {
let futex = 0;
thread::scope(|s| {
s.spawn(|| {
unsafe {
assert_eq!(
libc::os_sync_wait_on_address_with_timeout(
ptr::from_ref(&futex).cast_mut().cast(),
0,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_NONE,
libc::OS_CLOCK_MACH_ABSOLUTE_TIME,
400_000_000,
),
-1,
);
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
}
});
s.spawn(|| {
thread::yield_now();
unsafe {
assert_eq!(
libc::os_sync_wait_on_address(
ptr::from_ref(&futex).cast_mut().cast(),
0,
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_SHARED,
),
-1,
);
// This call fails because it uses the shared flag whereas the first waiter didn't.
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL);
}
});
thread::yield_now();
unsafe {
assert_eq!(
libc::os_sync_wake_by_address_any(
ptr::from_ref(&futex).cast_mut().cast(),
size_of::<i32>(),
libc::OS_SYNC_WAIT_ON_ADDRESS_SHARED,
),
-1,
);
// This call fails because it uses the shared flag whereas the waiter didn't.
assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EINVAL);
}
});
}
fn main() {
wake_nobody();
wake_dangling();
wait_wrong_val();
wait_timeout();
wait_absolute_timeout();
wait_wake();
wait_wake_multiple();
param_mismatch();
}