// ignore-windows: Concurrency on Windows is not supported yet. use std::thread; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::mpsc; use std::cell::Cell; /// When a thread yields, Miri's scheduler used to pick the thread with the lowest ID /// that can run. IDs are assigned in thread creation order. /// This means we could make 2 threads infinitely ping-pong with each other while /// really there is a 3rd thread that we should schedule to make progress. fn two_player_ping_pong() { static FLAG: AtomicUsize = AtomicUsize::new(0); let waiter1 = thread::spawn(|| { while FLAG.load(Ordering::Acquire) == 0 { // spin and wait thread::yield_now(); } }); let waiter2 = thread::spawn(|| { while FLAG.load(Ordering::Acquire) == 0 { // spin and wait thread::yield_now(); } }); let progress = thread::spawn(|| { FLAG.store(1, Ordering::Release); }); // The first `join` blocks the main thread and thus takes it out of the equation. waiter1.join().unwrap(); waiter2.join().unwrap(); progress.join().unwrap(); } /// Based on a test by @jethrogb. fn launcher() { static THREAD2_LAUNCHED: AtomicBool = AtomicBool::new(false); for _ in 0..10 { let (tx, rx) = mpsc::sync_channel(0); THREAD2_LAUNCHED.store(false, Ordering::SeqCst); let jh = thread::spawn(move || { struct RecvOnDrop(Cell>>); impl Drop for RecvOnDrop { fn drop(&mut self) { let rx = self.0.take().unwrap(); while !THREAD2_LAUNCHED.load(Ordering::SeqCst) { thread::yield_now(); } rx.recv().unwrap(); } } let tl_rx: RecvOnDrop = RecvOnDrop(Cell::new(None)); tl_rx.0.set(Some(rx)); }); let tx_clone = tx.clone(); let jh2 = thread::spawn(move || { THREAD2_LAUNCHED.store(true, Ordering::SeqCst); jh.join().unwrap(); tx_clone.send(()).expect_err( "Expecting channel to be closed because thread 1 TLS destructors must've run", ); }); while !THREAD2_LAUNCHED.load(Ordering::SeqCst) { thread::yield_now(); } thread::yield_now(); tx.send(()).expect("Expecting channel to be live because thread 2 must block on join"); jh2.join().unwrap(); } } fn main() { two_player_ping_pong(); launcher(); }