//! Tests for `block_on_io` — running a blocking closure on a worker OS //! thread while the calling actor is parked. use smarm::{block_on_io, run, spawn, yield_now}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; #[test] fn block_on_io_returns_the_closures_value() { let captured: Arc>> = Arc::new(Mutex::new(None)); let c = captured.clone(); run(move || { let v: u64 = block_on_io(|| { // Burn a tiny bit of time so this actually crosses thread. std::thread::sleep(Duration::from_millis(5)); 42 }); *c.lock().unwrap() = Some(v); }); assert_eq!(*captured.lock().unwrap(), Some(42)); } #[test] fn other_actors_run_while_block_on_io_is_in_flight() { // While actor A is parked in block_on_io, actor B should be able to // make progress. let order: Arc>> = Arc::new(Mutex::new(Vec::new())); let oa = order.clone(); let ob = order.clone(); run(move || { let a = spawn(move || { oa.lock().unwrap().push(1); // A starts first. block_on_io(|| { std::thread::sleep(Duration::from_millis(50)); }); oa.lock().unwrap().push(4); // A resumes last. }); let b = spawn(move || { // Make sure A enters block_on_io first. yield_now(); ob.lock().unwrap().push(2); yield_now(); ob.lock().unwrap().push(3); }); a.join().unwrap(); b.join().unwrap(); }); // Required interleaving: 1 (A starts) before 2,3 (B runs while A // is parked), and 4 (A resumes) after 2,3. let v = order.lock().unwrap(); assert_eq!(v[0], 1, "log: {:?}", *v); assert_eq!(v[v.len() - 1], 4, "log: {:?}", *v); let pos_2 = v.iter().position(|&x| x == 2).unwrap(); let pos_3 = v.iter().position(|&x| x == 3).unwrap(); let pos_4 = v.iter().position(|&x| x == 4).unwrap(); assert!(pos_2 < pos_4, "B's first step ran after A resumed: {:?}", *v); assert!(pos_3 < pos_4, "B's second step ran after A resumed: {:?}", *v); } #[test] fn many_concurrent_block_on_io_calls_all_complete() { let counter = Arc::new(AtomicU32::new(0)); let c = counter.clone(); run(move || { let mut handles = Vec::new(); for _ in 0..10 { let cc = c.clone(); handles.push(spawn(move || { let n: u32 = block_on_io(|| { std::thread::sleep(Duration::from_millis(10)); 1 }); cc.fetch_add(n, Ordering::SeqCst); })); } for h in handles { h.join().unwrap(); } }); assert_eq!(counter.load(Ordering::SeqCst), 10); } #[test] fn block_on_io_panic_propagates_to_caller() { let saw_err = Arc::new(std::sync::atomic::AtomicBool::new(false)); let s = saw_err.clone(); run(move || { let h = spawn(move || { // The closure panics on the worker thread; that should // resurface as a panic in this actor. let _: () = block_on_io(|| panic!("boom on io thread")); }); if h.join().is_err() { s.store(true, Ordering::SeqCst); } }); assert!(saw_err.load(Ordering::SeqCst)); }