use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; use std::thread; use std::time::Duration; use super::nonpoison_and_poison_unwrap_test; nonpoison_and_poison_unwrap_test!( name: smoke, test_body: { use locks::Condvar; let c = Condvar::new(); c.notify_one(); c.notify_all(); } ); #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_notify_one() { use std::sync::poison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let m2 = m.clone(); let c = Arc::new(Condvar::new()); let c2 = c.clone(); let g = m.lock().unwrap(); let _t = thread::spawn(move || { let _g = m2.lock().unwrap(); c2.notify_one(); }); let g = c.wait(g).unwrap(); drop(g); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_notify_one() { use std::sync::nonpoison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let m2 = m.clone(); let c = Arc::new(Condvar::new()); let c2 = c.clone(); let mut g = m.lock(); let _t = thread::spawn(move || { let _g = m2.lock(); c2.notify_one(); }); c.wait(&mut g); drop(g); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_notify_all() { use std::sync::poison::{Condvar, Mutex}; const N: usize = 10; let data = Arc::new((Mutex::new(0), Condvar::new())); let (tx, rx) = channel(); for _ in 0..N { let data = data.clone(); let tx = tx.clone(); thread::spawn(move || { let &(ref lock, ref cond) = &*data; let mut cnt = lock.lock().unwrap(); *cnt += 1; if *cnt == N { tx.send(()).unwrap(); } while *cnt != 0 { cnt = cond.wait(cnt).unwrap(); } tx.send(()).unwrap(); }); } drop(tx); let &(ref lock, ref cond) = &*data; rx.recv().unwrap(); let mut cnt = lock.lock().unwrap(); *cnt = 0; cond.notify_all(); drop(cnt); for _ in 0..N { rx.recv().unwrap(); } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_notify_all() { use std::sync::nonpoison::{Condvar, Mutex}; const N: usize = 10; let data = Arc::new((Mutex::new(0), Condvar::new())); let (tx, rx) = channel(); for _ in 0..N { let data = data.clone(); let tx = tx.clone(); thread::spawn(move || { let &(ref lock, ref cond) = &*data; let mut cnt = lock.lock(); *cnt += 1; if *cnt == N { tx.send(()).unwrap(); } while *cnt != 0 { cond.wait(&mut cnt); } tx.send(()).unwrap(); }); } drop(tx); let &(ref lock, ref cond) = &*data; rx.recv().unwrap(); let mut cnt = lock.lock(); *cnt = 0; cond.notify_all(); drop(cnt); for _ in 0..N { rx.recv().unwrap(); } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_test_mutex_arc_condvar() { use std::sync::poison::{Condvar, Mutex}; struct Packet(Arc<(Mutex, Condvar)>); let packet = Packet(Arc::new((Mutex::new(false), Condvar::new()))); let packet2 = Packet(packet.0.clone()); let (tx, rx) = channel(); let _t = thread::spawn(move || { // Wait until our parent has taken the lock. rx.recv().unwrap(); let &(ref lock, ref cvar) = &*packet2.0; // Set the data to `true` and wake up our parent. let mut guard = lock.lock().unwrap(); *guard = true; cvar.notify_one(); }); let &(ref lock, ref cvar) = &*packet.0; let mut guard = lock.lock().unwrap(); // Wake up our child. tx.send(()).unwrap(); // Wait until our child has set the data to `true`. assert!(!*guard); while !*guard { guard = cvar.wait(guard).unwrap(); } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_test_mutex_arc_condvar() { use std::sync::nonpoison::{Condvar, Mutex}; struct Packet(Arc<(Mutex, Condvar)>); let packet = Packet(Arc::new((Mutex::new(false), Condvar::new()))); let packet2 = Packet(packet.0.clone()); let (tx, rx) = channel(); let _t = thread::spawn(move || { // Wait until our parent has taken the lock. rx.recv().unwrap(); let &(ref lock, ref cvar) = &*packet2.0; // Set the data to `true` and wake up our parent. let mut guard = lock.lock(); *guard = true; cvar.notify_one(); }); let &(ref lock, ref cvar) = &*packet.0; let mut guard = lock.lock(); // Wake up our child. tx.send(()).unwrap(); // Wait until our child has set the data to `true`. assert!(!*guard); while !*guard { cvar.wait(&mut guard); } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_wait_while() { use std::sync::poison::{Condvar, Mutex}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = pair.clone(); // Inside of our lock, spawn a new thread, and then wait for it to start. thread::spawn(move || { let &(ref lock, ref 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 start up. let &(ref lock, ref cvar) = &*pair; let guard = cvar.wait_while(lock.lock().unwrap(), |started| !*started).unwrap(); assert!(*guard); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_wait_while() { use std::sync::nonpoison::{Condvar, Mutex}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = pair.clone(); // Inside of our lock, spawn a new thread, and then wait for it to start. thread::spawn(move || { let &(ref lock, ref cvar) = &*pair2; let mut started = lock.lock(); *started = true; // We notify the condvar that the value has changed. cvar.notify_one(); }); // Wait for the thread to start up. let &(ref lock, ref cvar) = &*pair; let mut guard = lock.lock(); cvar.wait_while(&mut guard, |started| !*started); assert!(*guard); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_wait_timeout_wait() { use std::sync::poison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); loop { let g = m.lock().unwrap(); let (_g, no_timeout) = c.wait_timeout(g, Duration::from_millis(1)).unwrap(); // spurious wakeups mean this isn't necessarily true // so execute test again, if not timeout if !no_timeout.timed_out() { continue; } break; } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_wait_timeout_wait() { use std::sync::nonpoison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); loop { let mut g = m.lock(); let no_timeout = c.wait_timeout(&mut g, Duration::from_millis(1)); // spurious wakeups mean this isn't necessarily true // so execute test again, if not timeout if !no_timeout.timed_out() { continue; } break; } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_wait_timeout_while_wait() { use std::sync::poison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); let g = m.lock().unwrap(); let (_g, wait) = c.wait_timeout_while(g, Duration::from_millis(1), |_| true).unwrap(); // no spurious wakeups. ensure it timed-out assert!(wait.timed_out()); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_wait_timeout_while_wait() { use std::sync::nonpoison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); let mut g = m.lock(); let wait = c.wait_timeout_while(&mut g, Duration::from_millis(1), |_| true); // no spurious wakeups. ensure it timed-out assert!(wait.timed_out()); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_wait_timeout_while_instant_satisfy() { use std::sync::poison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); let g = m.lock().unwrap(); let (_g, wait) = c.wait_timeout_while(g, Duration::from_millis(0), |_| false).unwrap(); // ensure it didn't time-out even if we were not given any time. assert!(!wait.timed_out()); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_wait_timeout_while_instant_satisfy() { use std::sync::nonpoison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); let mut g = m.lock(); let wait = c.wait_timeout_while(&mut g, Duration::from_millis(0), |_| false); // ensure it didn't time-out even if we were not given any time. assert!(!wait.timed_out()); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_wait_timeout_while_wake() { use std::sync::poison::{Condvar, Mutex}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair_copy = pair.clone(); let &(ref m, ref c) = &*pair; let g = m.lock().unwrap(); let _t = thread::spawn(move || { let &(ref lock, ref cvar) = &*pair_copy; let mut started = lock.lock().unwrap(); thread::sleep(Duration::from_millis(1)); *started = true; cvar.notify_one(); }); let (g2, wait) = c .wait_timeout_while(g, Duration::from_millis(u64::MAX), |&mut notified| !notified) .unwrap(); // ensure it didn't time-out even if we were not given any time. assert!(!wait.timed_out()); assert!(*g2); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_wait_timeout_while_wake() { use std::sync::nonpoison::{Condvar, Mutex}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair_copy = pair.clone(); let &(ref m, ref c) = &*pair; let mut g = m.lock(); let _t = thread::spawn(move || { let &(ref lock, ref cvar) = &*pair_copy; let mut started = lock.lock(); thread::sleep(Duration::from_millis(1)); *started = true; cvar.notify_one(); }); let wait = c.wait_timeout_while(&mut g, Duration::from_millis(u64::MAX), |&mut notified| !notified); // ensure it didn't time-out even if we were not given any time. assert!(!wait.timed_out()); assert!(*g); } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn poison_wait_timeout_wake() { use std::sync::poison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); loop { let g = m.lock().unwrap(); let c2 = c.clone(); let m2 = m.clone(); let notified = Arc::new(AtomicBool::new(false)); let notified_copy = notified.clone(); let t = thread::spawn(move || { let _g = m2.lock().unwrap(); thread::sleep(Duration::from_millis(1)); notified_copy.store(true, Ordering::Relaxed); c2.notify_one(); }); let (g, timeout_res) = c.wait_timeout(g, Duration::from_millis(u64::MAX)).unwrap(); assert!(!timeout_res.timed_out()); // spurious wakeups mean this isn't necessarily true // so execute test again, if not notified if !notified.load(Ordering::Relaxed) { t.join().unwrap(); continue; } drop(g); t.join().unwrap(); break; } } #[test] #[cfg(not(any(target_os = "emscripten", target_os = "wasi")))] // No threads. fn nonpoison_wait_timeout_wake() { use std::sync::nonpoison::{Condvar, Mutex}; let m = Arc::new(Mutex::new(())); let c = Arc::new(Condvar::new()); loop { let mut g = m.lock(); let c2 = c.clone(); let m2 = m.clone(); let notified = Arc::new(AtomicBool::new(false)); let notified_copy = notified.clone(); let t = thread::spawn(move || { let _g = m2.lock(); thread::sleep(Duration::from_millis(1)); notified_copy.store(true, Ordering::Relaxed); c2.notify_one(); }); let timeout_res = c.wait_timeout(&mut g, Duration::from_millis(u64::MAX)); assert!(!timeout_res.timed_out()); // spurious wakeups mean this isn't necessarily true // so execute test again, if not notified if !notified.load(Ordering::Relaxed) { t.join().unwrap(); continue; } drop(g); t.join().unwrap(); break; } } // Some platforms internally cast the timeout duration into nanoseconds. // If they fail to consider overflow during the conversion (I'm looking // at you, macOS), `wait_timeout` will return immediately and indicate a // timeout for durations that are slightly longer than u64::MAX nanoseconds. // `std` should guard against this by clamping the timeout. // See #37440 for context. #[test] fn poison_timeout_nanoseconds() { use std::sync::poison::{Condvar, Mutex}; let sent = Mutex::new(false); let cond = Condvar::new(); thread::scope(|s| { s.spawn(|| { // Sleep so that the other thread has a chance to encounter the // timeout. thread::sleep(Duration::from_secs(2)); *sent.lock().unwrap() = true; cond.notify_all(); }); let mut guard = sent.lock().unwrap(); // Loop until `sent` is set by the thread to guard against spurious // wakeups. If the `wait_timeout` happens just before the signal by // the other thread, such a spurious wakeup might prevent the // miscalculated timeout from occurring, but this is basically just // a smoke test anyway. loop { if *guard { break; } // If there is internal overflow, this call will return almost // immediately, before the other thread has reached the `notify_all`, // and indicate a timeout. let (g, res) = cond .wait_timeout(guard, Duration::from_secs(u64::MAX.div_ceil(1_000_000_000))) .unwrap(); assert!(!res.timed_out()); guard = g; } }) } #[test] fn nonpoison_timeout_nanoseconds() { use std::sync::nonpoison::{Condvar, Mutex}; let sent = Mutex::new(false); let cond = Condvar::new(); thread::scope(|s| { s.spawn(|| { // Sleep so that the other thread has a chance to encounter the // timeout. thread::sleep(Duration::from_secs(2)); sent.set(true); cond.notify_all(); }); let mut guard = sent.lock(); // Loop until `sent` is set by the thread to guard against spurious // wakeups. If the `wait_timeout` happens just before the signal by // the other thread, such a spurious wakeup might prevent the // miscalculated timeout from occurring, but this is basically just // a smoke test anyway. loop { if *guard { break; } // If there is internal overflow, this call will return almost // immediately, before the other thread has reached the `notify_all`, // and indicate a timeout. let res = cond .wait_timeout(&mut guard, Duration::from_secs(u64::MAX.div_ceil(1_000_000_000))); assert!(!res.timed_out()); } }) } #[test] #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_arc_condvar_poison() { use std::sync::poison::{Condvar, Mutex}; struct Packet(Arc<(Mutex, Condvar)>); let packet = Packet(Arc::new((Mutex::new(1), Condvar::new()))); let packet2 = Packet(packet.0.clone()); let (tx, rx) = channel(); let _t = thread::spawn(move || -> () { rx.recv().unwrap(); let &(ref lock, ref cvar) = &*packet2.0; let _g = lock.lock().unwrap(); cvar.notify_one(); // Parent should fail when it wakes up. panic!(); }); let &(ref lock, ref cvar) = &*packet.0; let mut lock = lock.lock().unwrap(); tx.send(()).unwrap(); while *lock == 1 { match cvar.wait(lock) { Ok(l) => { lock = l; assert_eq!(*lock, 1); } Err(..) => break, } } }