| use std::sync::Once; |
| use std::sync::atomic::AtomicBool; |
| use std::sync::atomic::Ordering::Relaxed; |
| use std::sync::mpsc::channel; |
| use std::time::Duration; |
| use std::{panic, thread}; |
| |
| #[test] |
| fn smoke_once() { |
| static O: Once = Once::new(); |
| let mut a = 0; |
| O.call_once(|| a += 1); |
| assert_eq!(a, 1); |
| O.call_once(|| a += 1); |
| assert_eq!(a, 1); |
| } |
| |
| #[test] |
| fn stampede_once() { |
| static O: Once = Once::new(); |
| static mut RUN: bool = false; |
| |
| let (tx, rx) = channel(); |
| for _ in 0..10 { |
| let tx = tx.clone(); |
| thread::spawn(move || { |
| for _ in 0..4 { |
| thread::yield_now() |
| } |
| unsafe { |
| O.call_once(|| { |
| assert!(!RUN); |
| RUN = true; |
| }); |
| assert!(RUN); |
| } |
| tx.send(()).unwrap(); |
| }); |
| } |
| |
| unsafe { |
| O.call_once(|| { |
| assert!(!RUN); |
| RUN = true; |
| }); |
| assert!(RUN); |
| } |
| |
| for _ in 0..10 { |
| rx.recv().unwrap(); |
| } |
| } |
| |
| #[test] |
| #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] |
| fn poison_bad() { |
| static O: Once = Once::new(); |
| |
| // poison the once |
| let t = panic::catch_unwind(|| { |
| O.call_once(|| panic!()); |
| }); |
| assert!(t.is_err()); |
| |
| // poisoning propagates |
| let t = panic::catch_unwind(|| { |
| O.call_once(|| {}); |
| }); |
| assert!(t.is_err()); |
| |
| // we can subvert poisoning, however |
| let mut called = false; |
| O.call_once_force(|p| { |
| called = true; |
| assert!(p.is_poisoned()) |
| }); |
| assert!(called); |
| |
| // once any success happens, we stop propagating the poison |
| O.call_once(|| {}); |
| } |
| |
| #[test] |
| #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] |
| fn wait_for_force_to_finish() { |
| static O: Once = Once::new(); |
| |
| // poison the once |
| let t = panic::catch_unwind(|| { |
| O.call_once(|| panic!()); |
| }); |
| assert!(t.is_err()); |
| |
| // make sure someone's waiting inside the once via a force |
| let (tx1, rx1) = channel(); |
| let (tx2, rx2) = channel(); |
| let t1 = thread::spawn(move || { |
| O.call_once_force(|p| { |
| assert!(p.is_poisoned()); |
| tx1.send(()).unwrap(); |
| rx2.recv().unwrap(); |
| }); |
| }); |
| |
| rx1.recv().unwrap(); |
| |
| // put another waiter on the once |
| let t2 = thread::spawn(|| { |
| let mut called = false; |
| O.call_once(|| { |
| called = true; |
| }); |
| assert!(!called); |
| }); |
| |
| tx2.send(()).unwrap(); |
| |
| assert!(t1.join().is_ok()); |
| assert!(t2.join().is_ok()); |
| } |
| |
| #[test] |
| fn wait() { |
| for _ in 0..50 { |
| let val = AtomicBool::new(false); |
| let once = Once::new(); |
| |
| thread::scope(|s| { |
| for _ in 0..4 { |
| s.spawn(|| { |
| once.wait(); |
| assert!(val.load(Relaxed)); |
| }); |
| } |
| |
| once.call_once(|| val.store(true, Relaxed)); |
| }); |
| } |
| } |
| |
| #[test] |
| #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] |
| fn wait_on_poisoned() { |
| let once = Once::new(); |
| |
| panic::catch_unwind(|| once.call_once(|| panic!())).unwrap_err(); |
| panic::catch_unwind(|| once.wait()).unwrap_err(); |
| } |
| |
| #[test] |
| #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] |
| fn wait_force_on_poisoned() { |
| let once = Once::new(); |
| |
| thread::scope(|s| { |
| panic::catch_unwind(|| once.call_once(|| panic!())).unwrap_err(); |
| |
| s.spawn(|| { |
| thread::sleep(Duration::from_millis(100)); |
| |
| once.call_once_force(|_| {}); |
| }); |
| |
| once.wait_force(); |
| }) |
| } |