blob: 91c67b215a1a91ba61cdd217e8626adf8f0ddc4d [file] [log] [blame]
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
// We use `yield` to test specific interleavings, so disable automatic preemption.
//@compile-flags: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-preemption-rate=0
use std::sync::{Arc, Barrier, Condvar, Mutex, Once, RwLock};
use std::thread;
use std::time::{Duration, Instant};
// We are expecting to sleep for 10ms. How long of a sleep we are accepting?
// Even with 1000ms we still see this test fail on macOS runners.
const MAX_SLEEP_TIME_MS: u64 = 2000;
// Check if Rust barriers are working.
/// This test is taken from the Rust documentation.
fn check_barriers() {
let mut handles = Vec::with_capacity(10);
let barrier = Arc::new(Barrier::new(10));
for _ in 0..10 {
let c = barrier.clone();
// The same messages will be printed together.
// You will NOT see any interleaving.
handles.push(thread::spawn(move || {
println!("before wait");
c.wait();
println!("after wait");
}));
}
// Wait for other threads to finish.
for handle in handles {
handle.join().unwrap();
}
}
// Check if Rust conditional variables are working.
/// The test taken from the Rust documentation.
fn check_conditional_variables_notify_one() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// Spawn a new thread.
let t = thread::spawn(move || {
thread::yield_now();
let (lock, cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
// We notify the condvar that the value has changed.
cvar.notify_one();
});
// Wait for the thread to fully start up.
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
started = cvar.wait(started).unwrap();
}
t.join().unwrap();
}
/// Test that waiting on a conditional variable with a timeout does not
/// deadlock.
fn check_conditional_variables_timed_wait_timeout() {
let lock = Mutex::new(());
let cvar = Condvar::new();
let guard = lock.lock().unwrap();
let now = Instant::now();
let (_guard, timeout) = cvar.wait_timeout(guard, Duration::from_millis(10)).unwrap();
assert!(timeout.timed_out());
let elapsed_time = now.elapsed().as_millis();
assert!(10 <= elapsed_time && elapsed_time <= MAX_SLEEP_TIME_MS.into());
}
/// Test that signaling a conditional variable when waiting with a timeout works
/// as expected.
fn check_conditional_variables_timed_wait_notimeout() {
let pair = Arc::new((Mutex::new(()), Condvar::new()));
let pair2 = pair.clone();
let (lock, cvar) = &*pair;
let guard = lock.lock().unwrap();
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(1)); // Make sure the other thread is waiting by the time we call `notify`.
let (_lock, cvar) = &*pair2;
cvar.notify_one();
});
let (_guard, timeout) =
cvar.wait_timeout(guard, Duration::from_millis(MAX_SLEEP_TIME_MS)).unwrap();
assert!(!timeout.timed_out());
handle.join().unwrap();
}
// Check if locks are working.
fn check_mutex() {
let data = Arc::new(Mutex::new(0));
let mut threads = Vec::new();
for _ in 0..3 {
let data = Arc::clone(&data);
let thread = thread::spawn(move || {
let mut data = data.lock().unwrap();
thread::yield_now();
*data += 1;
});
threads.push(thread);
}
for thread in threads {
thread.join().unwrap();
}
assert!(data.try_lock().is_ok());
let data = Arc::try_unwrap(data).unwrap().into_inner().unwrap();
assert_eq!(data, 3);
}
fn check_rwlock_write() {
let data = Arc::new(RwLock::new(0));
let mut threads = Vec::new();
for _ in 0..3 {
let thread = thread::spawn({
let data = Arc::clone(&data);
move || {
let mut data = data.write().unwrap();
thread::yield_now();
*data += 1;
}
});
threads.push(thread);
let readthread = thread::spawn({
let data = Arc::clone(&data);
move || {
let data = data.read().unwrap();
thread::yield_now();
assert!(*data >= 0 && *data <= 3);
}
});
threads.push(readthread);
}
for thread in threads {
thread.join().unwrap();
}
assert!(data.try_write().is_ok());
let data = Arc::try_unwrap(data).unwrap().into_inner().unwrap();
assert_eq!(data, 3);
}
fn check_rwlock_read_no_deadlock() {
let l1 = Arc::new(RwLock::new(0));
let l2 = Arc::new(RwLock::new(0));
let l1_copy = Arc::clone(&l1);
let l2_copy = Arc::clone(&l2);
// acquire l1 and hold it until after the other thread is done
let _guard1 = l1.read().unwrap();
let handle = thread::spawn(move || {
// acquire l2 before the other thread
let _guard2 = l2_copy.read().unwrap();
thread::yield_now();
let _guard1 = l1_copy.read().unwrap();
});
thread::yield_now();
let _guard2 = l2.read().unwrap();
handle.join().unwrap();
}
// Check if Rust once statics are working.
static mut VAL: usize = 0;
static INIT: Once = Once::new();
fn get_cached_val() -> usize {
unsafe {
INIT.call_once(|| {
VAL = expensive_computation();
});
VAL
}
}
fn expensive_computation() -> usize {
let mut i = 1;
let mut c = 1;
while i < 1000 {
i *= c;
c += 1;
}
i
}
/// The test taken from the Rust documentation.
fn check_once() {
let handles: Vec<_> = (0..10)
.map(|_| {
thread::spawn(|| {
thread::yield_now();
let val = get_cached_val();
assert_eq!(val, 5040);
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
fn park_timeout() {
let start = Instant::now();
thread::park_timeout(Duration::from_millis(10));
// Normally, waiting in park/park_timeout may spuriously wake up early, but we
// know Miri's timed synchronization primitives do not do that.
let elapsed = start.elapsed();
assert!(
(10..MAX_SLEEP_TIME_MS.into()).contains(&elapsed.as_millis()),
"bad sleep time: {elapsed:?}"
);
}
fn park_unpark() {
let t1 = thread::current();
let t2 = thread::spawn(move || {
thread::park();
thread::sleep(Duration::from_millis(10));
t1.unpark();
});
let start = Instant::now();
t2.thread().unpark();
thread::park();
// Normally, waiting in park/park_timeout may spuriously wake up early, but we
// know Miri's timed synchronization primitives do not do that.
let elapsed = start.elapsed();
assert!(
(10..MAX_SLEEP_TIME_MS.into()).contains(&elapsed.as_millis()),
"bad sleep time: {elapsed:?}"
);
t2.join().unwrap();
}
fn main() {
check_mutex();
check_rwlock_write();
check_rwlock_read_no_deadlock();
check_once();
park_timeout();
park_unpark();
check_barriers();
check_conditional_variables_notify_one();
check_conditional_variables_timed_wait_timeout();
check_conditional_variables_timed_wait_notimeout();
}