316 lines
8.2 KiB
Rust
316 lines
8.2 KiB
Rust
// ignore-windows: Concurrency on Windows is not supported yet.
|
|
// compile-flags: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-check-number-validity
|
|
|
|
use std::sync::{Arc, Barrier, Condvar, Mutex, Once, RwLock};
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
// 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.
|
|
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();
|
|
}
|
|
}
|
|
|
|
fn check_conditional_variables_notify_all() {
|
|
let pair = Arc::new(((Mutex::new(())), Condvar::new()));
|
|
|
|
// Spawn threads and block them on the conditional variable.
|
|
let handles: Vec<_> = (0..5)
|
|
.map(|_| {
|
|
let pair2 = pair.clone();
|
|
thread::spawn(move || {
|
|
let (lock, cvar) = &*pair2;
|
|
let guard = lock.lock().unwrap();
|
|
// Block waiting on the conditional variable.
|
|
let _ = cvar.wait(guard).unwrap();
|
|
})
|
|
})
|
|
.inspect(|_| {
|
|
thread::yield_now();
|
|
thread::yield_now();
|
|
})
|
|
.collect();
|
|
|
|
let (_, cvar) = &*pair;
|
|
// Unblock all threads.
|
|
cvar.notify_all();
|
|
|
|
for handle in handles {
|
|
handle.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(100)).unwrap();
|
|
assert!(timeout.timed_out());
|
|
let elapsed_time = now.elapsed().as_millis();
|
|
assert!(100 <= elapsed_time && elapsed_time <= 500);
|
|
}
|
|
|
|
/// 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 || {
|
|
let (_lock, cvar) = &*pair2;
|
|
cvar.notify_one();
|
|
});
|
|
|
|
let (_guard, timeout) = cvar.wait_timeout(guard, Duration::from_millis(500)).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 data = Arc::clone(&data);
|
|
let thread = thread::spawn(move || {
|
|
let mut data = data.write().unwrap();
|
|
thread::yield_now();
|
|
*data += 1;
|
|
});
|
|
threads.push(thread);
|
|
}
|
|
|
|
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);
|
|
let _guard1 = l1.read().unwrap();
|
|
let handle = thread::spawn(move || {
|
|
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 check_rwlock_unlock_bug1() {
|
|
// There was a bug where when un-read-locking an rwlock that still has other
|
|
// readers waiting, we'd accidentally also let a writer in.
|
|
// That caused an ICE.
|
|
let l = Arc::new(RwLock::new(0));
|
|
|
|
let r1 = l.read().unwrap();
|
|
let r2 = l.read().unwrap();
|
|
|
|
// Make a waiting writer.
|
|
let l2 = l.clone();
|
|
thread::spawn(move || {
|
|
let mut w = l2.write().unwrap();
|
|
*w += 1;
|
|
});
|
|
thread::yield_now();
|
|
|
|
drop(r1);
|
|
assert_eq!(*r2, 0);
|
|
thread::yield_now();
|
|
thread::yield_now();
|
|
thread::yield_now();
|
|
assert_eq!(*r2, 0);
|
|
drop(r2);
|
|
}
|
|
|
|
fn check_rwlock_unlock_bug2() {
|
|
// There was a bug where when un-read-locking an rwlock by letting the last reader leaver,
|
|
// we'd forget to wake up a writer.
|
|
// That meant the writer thread could never run again.
|
|
let l = Arc::new(RwLock::new(0));
|
|
|
|
let r = l.read().unwrap();
|
|
|
|
// Make a waiting writer.
|
|
let l2 = l.clone();
|
|
let h = thread::spawn(move || {
|
|
let _w = l2.write().unwrap();
|
|
});
|
|
thread::yield_now();
|
|
|
|
drop(r);
|
|
h.join().unwrap();
|
|
}
|
|
|
|
fn park_timeout() {
|
|
let start = Instant::now();
|
|
|
|
thread::park_timeout(Duration::from_millis(200));
|
|
// Normally, waiting in park/park_timeout may spuriously wake up early, but we
|
|
// know Miri's timed synchronization primitives do not do that.
|
|
|
|
assert!((200..1000).contains(&start.elapsed().as_millis()));
|
|
}
|
|
|
|
fn park_unpark() {
|
|
let t1 = thread::current();
|
|
let t2 = thread::spawn(move || {
|
|
thread::park();
|
|
thread::sleep(Duration::from_millis(200));
|
|
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.
|
|
|
|
assert!((200..1000).contains(&start.elapsed().as_millis()));
|
|
}
|
|
|
|
fn check_condvar() {
|
|
let _ = std::sync::Condvar::new();
|
|
}
|
|
|
|
fn main() {
|
|
check_barriers();
|
|
check_conditional_variables_notify_one();
|
|
check_conditional_variables_notify_all();
|
|
check_conditional_variables_timed_wait_timeout();
|
|
check_conditional_variables_timed_wait_notimeout();
|
|
check_mutex();
|
|
check_rwlock_write();
|
|
check_rwlock_read_no_deadlock();
|
|
check_once();
|
|
check_rwlock_unlock_bug1();
|
|
check_rwlock_unlock_bug2();
|
|
park_timeout();
|
|
park_unpark();
|
|
check_condvar();
|
|
}
|