Files
announcement/tests/concurrency_tests.rs
swaits e8e55b7676 chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
2025-10-13 19:36:37 -06:00

1081 lines
30 KiB
Rust

//! Concurrency tests for the announcement crate.
//! Tests thread safety and concurrent operations.
use announcement::Announcement;
use std::collections::HashSet;
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Barrier, Mutex,
};
use std::thread;
use std::time::Duration;
/// Test: Concurrency: concurrent listener creation 1000
///
/// Verifies: Verifies concurrent listener creation 1000 works correctly with concurrent access
/// Category: concurrency
#[test]
fn test_concurrent_listener_creation_1000() {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(1001));
let mut handles = Vec::new();
for i in 0..1000 {
let ann = announcement.clone();
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
barrier.wait(); // Synchronize all threads
let listener = ann.listener();
(i, listener)
}));
}
barrier.wait(); // Release all threads at once
// Announce while threads might still be creating listeners
thread::sleep(Duration::from_millis(1));
announcer.announce(42).unwrap();
// Collect all listeners
let mut listeners = Vec::new();
for handle in handles {
let (id, listener) = handle.join().unwrap();
listeners.push((id, listener));
}
// Verify all listeners received the value
for (_, listener) in &listeners {
assert_eq!(listener.try_listen(), Some(42));
}
assert_eq!(listeners.len(), 1000);
}
/// Test: Concurrency: concurrent try listen 1000
///
/// Verifies: Verifies concurrent try listen 1000 works correctly with concurrent access
/// Category: concurrency
#[test]
fn test_concurrent_try_listen_1000() {
let (announcer, announcement) = Announcement::new();
// Create listeners first
let mut listeners = Vec::new();
for _ in 0..1000 {
listeners.push(Arc::new(announcement.listener()));
}
announcer.announce(42).unwrap();
let barrier = Arc::new(Barrier::new(1001));
let mut handles = Vec::new();
for listener in listeners {
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
barrier.wait();
// All threads try_listen at once
listener.try_listen()
}));
}
barrier.wait(); // Release all threads
// Verify all got the value
for handle in handles {
assert_eq!(handle.join().unwrap(), Some(42));
}
}
/// Test: Concurrency: concurrent announce protection
///
/// Verifies: Verifies concurrent announce protection works correctly with concurrent access
/// Category: concurrency
#[test]
fn test_concurrent_announce_protection() {
// Since announce() consumes self, we can't actually have concurrent attempts
// This test verifies the type system prevents this
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Only one thread can own the announcer
announcer.announce(42).unwrap();
// Can't announce again - announcer is consumed
assert_eq!(listener.try_listen(), Some(42));
}
#[cfg(feature = "std")]
/// Test: Concurrency: concurrent is announced checks
///
/// Verifies: Verifies concurrent is announced checks works correctly with concurrent access
/// Category: concurrency
#[test]
fn test_concurrent_is_announced_checks() {
let (announcer, announcement) = Announcement::new();
let checking = Arc::new(AtomicBool::new(true));
let false_count = Arc::new(AtomicUsize::new(0));
let true_count = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for _ in 0..100 {
let ann = announcement.clone();
let checking = checking.clone();
let false_count = false_count.clone();
let true_count = true_count.clone();
handles.push(thread::spawn(move || {
while checking.load(Ordering::Acquire) {
if ann.is_announced() {
true_count.fetch_add(1, Ordering::Relaxed);
} else {
false_count.fetch_add(1, Ordering::Relaxed);
}
thread::yield_now();
}
}));
}
// Let threads check for a bit
thread::sleep(Duration::from_millis(10));
// Announce
announcer.announce(42).unwrap();
// Let threads see the announcement
thread::sleep(Duration::from_millis(10));
// Stop checking
checking.store(false, Ordering::Release);
for handle in handles {
handle.join().unwrap();
}
// Should have seen both states
assert!(
false_count.load(Ordering::Relaxed) > 0,
"Should see false before announce"
);
assert!(
true_count.load(Ordering::Relaxed) > 0,
"Should see true after announce"
);
}
/// Test: Concurrency test: thread safety shared state
///
/// Verifies: Verifies thread safety shared state works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_thread_safety_shared_state() {
#[derive(Clone, Debug)]
struct SharedState {
data: Arc<Mutex<Vec<i32>>>,
}
let (announcer, announcement) = Announcement::new();
let state = SharedState {
data: Arc::new(Mutex::new(vec![1, 2, 3])),
};
let mut handles = Vec::new();
// Multiple threads get listeners
for i in 0..50 {
let listener = announcement.listener();
handles.push(thread::spawn(move || (i, listener)));
}
// Announce shared state
announcer.announce(state.clone()).unwrap();
// All threads access the shared state
for handle in handles {
let (id, listener) = handle.join().unwrap();
let received = listener.try_listen().unwrap();
// Modify shared state
received.data.lock().unwrap().push(id);
}
// Verify all modifications were recorded
let final_data = state.data.lock().unwrap();
assert_eq!(final_data.len(), 53); // Original 3 + 50 threads
}
/// Test: Concurrency test: listener creation during announce
///
/// Verifies: Verifies listener creation during announce works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_listener_creation_during_announce() {
let (announcer, announcement) = Announcement::new();
let announcement_clone = announcement.clone();
// Thread that creates listeners continuously
let creating = Arc::new(AtomicBool::new(true));
let creating_clone = creating.clone();
let created_count = Arc::new(AtomicUsize::new(0));
let count_clone = created_count.clone();
let creator = thread::spawn(move || {
let mut listeners = Vec::new();
while creating_clone.load(Ordering::Acquire) {
listeners.push(announcement_clone.listener());
count_clone.fetch_add(1, Ordering::Relaxed);
}
listeners
});
// Let it create some listeners
thread::sleep(Duration::from_millis(5));
// Announce while creation is happening
announcer.announce(42).unwrap();
// Let it create more listeners
thread::sleep(Duration::from_millis(5));
// Stop creation
creating.store(false, Ordering::Release);
let listeners = creator.join().unwrap();
let total_created = created_count.load(Ordering::Relaxed);
println!("Created {} listeners during test", total_created);
// All listeners should see the value
for listener in &listeners {
assert_eq!(listener.try_listen(), Some(42));
}
}
/// Test: Concurrency: concurrent cloning
///
/// Verifies: Verifies concurrent cloning works correctly with concurrent access
/// Category: concurrency
#[test]
fn test_concurrent_cloning() {
let (announcer, announcement) = Announcement::new();
let original_listener = Arc::new(announcement.listener());
let mut handles = Vec::new();
// Many threads cloning the same listener
for _ in 0..100 {
let listener = original_listener.clone();
handles.push(thread::spawn(move || (*listener).clone()));
}
announcer.announce(42).unwrap();
// Verify all clones work
for handle in handles {
let listener = handle.join().unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
// Original still works
assert_eq!(original_listener.try_listen(), Some(42));
}
/// Test: Concurrency test: thread pool pattern
///
/// Verifies: Verifies thread pool pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_thread_pool_pattern() {
let (announcer, announcement) = Announcement::new();
let num_workers = 10;
let mut handles = Vec::new();
// Create worker threads
for worker_id in 0..num_workers {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
// Simulate work
thread::sleep(Duration::from_millis(worker_id as u64));
// Wait for value
loop {
if let Some(value) = listener.try_listen() {
return (worker_id, value);
}
thread::yield_now();
}
}));
}
// Announce to all workers
announcer.announce(42).unwrap();
// Collect results
let mut results = Vec::new();
for handle in handles {
results.push(handle.join().unwrap());
}
// Verify all workers received the value
assert_eq!(results.len(), num_workers);
for (_, value) in results {
assert_eq!(value, 42);
}
}
/// Test: Concurrency test: producer consumer pattern
///
/// Verifies: Verifies producer consumer pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_producer_consumer_pattern() {
let (announcer, announcement) = Announcement::new();
// Producer thread
let producer = thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
announcer.announce(vec![1, 2, 3, 4, 5]).unwrap();
});
// Multiple consumer threads
let mut consumers = Vec::new();
for i in 0..5 {
let listener = announcement.listener();
consumers.push(thread::spawn(move || {
loop {
if let Some(data) = listener.try_listen() {
// Process data
let sum: i32 = data.iter().sum();
return (i, sum);
}
thread::sleep(Duration::from_millis(1));
}
}));
}
producer.join().unwrap();
// All consumers should get the same data
for consumer in consumers {
let (_, sum) = consumer.join().unwrap();
assert_eq!(sum, 15);
}
}
/// Test: Concurrency test: broadcast pattern
///
/// Verifies: Verifies broadcast pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_broadcast_pattern() {
let (announcer, announcement) = Announcement::new();
let mut receivers = Vec::new();
// Create many receivers
for i in 0..100 {
let listener = announcement.listener();
receivers.push(thread::spawn(move || loop {
if let Some(msg) = listener.try_listen() {
return (i, msg);
}
thread::yield_now();
}));
}
// Broadcast message
announcer.announce("broadcast message".to_string()).unwrap();
// All should receive
for receiver in receivers {
let (_, msg) = receiver.join().unwrap();
assert_eq!(msg, "broadcast message");
}
}
/// Test: Concurrency test: fork join pattern
///
/// Verifies: Verifies fork join pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_fork_join_pattern() {
let (announcer, announcement) = Announcement::new();
// Fork phase - create workers
let mut workers = Vec::new();
for i in 0..10 {
let listener = announcement.listener();
workers.push(thread::spawn(move || {
// Wait for work
loop {
if let Some(work_item) = listener.try_listen() {
// Do work
return work_item * (i + 1);
}
thread::yield_now();
}
}));
}
// Distribute work
announcer.announce(10).unwrap();
// Join phase - collect results
let results: Vec<i32> = workers.into_iter().map(|h| h.join().unwrap()).collect();
// Verify results
let expected_sum: i32 = (1..=10).map(|i| 10 * i).sum();
let actual_sum: i32 = results.iter().sum();
assert_eq!(actual_sum, expected_sum);
}
/// Test: Concurrency test: reader writer pattern
///
/// Verifies: Verifies reader writer pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_reader_writer_pattern() {
#[derive(Clone, Debug)]
struct Document {
content: Arc<Mutex<String>>,
}
let (announcer, announcement) = Announcement::new();
let doc = Document {
content: Arc::new(Mutex::new("initial".to_string())),
};
// Writer
let doc_clone = doc.clone();
let writer = thread::spawn(move || {
doc_clone.content.lock().unwrap().push_str(" updated");
announcer.announce(doc_clone).unwrap();
});
// Multiple readers
let mut readers = Vec::new();
for i in 0..5 {
let listener = announcement.listener();
readers.push(thread::spawn(move || loop {
if let Some(doc) = listener.try_listen() {
let content = doc.content.lock().unwrap().clone();
return (i, content);
}
thread::yield_now();
}));
}
writer.join().unwrap();
for reader in readers {
let (_, content) = reader.join().unwrap();
assert_eq!(content, "initial updated");
}
}
/// Test: Concurrency test: pipeline pattern
///
/// Verifies: Verifies pipeline pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_pipeline_pattern() {
// Stage 1 -> Stage 2 -> Stage 3
let (ann1, announcement1) = Announcement::new();
let (ann2, announcement2) = Announcement::new();
let (ann3, announcement3) = Announcement::new();
// Stage 1: Generate
thread::spawn(move || {
ann1.announce(10).unwrap();
});
// Stage 2: Transform
let listener1 = announcement1.listener();
thread::spawn(move || loop {
if let Some(val) = listener1.try_listen() {
ann2.announce(val * 2).unwrap();
break;
}
thread::yield_now();
});
// Stage 3: Finalize
let listener2 = announcement2.listener();
thread::spawn(move || loop {
if let Some(val) = listener2.try_listen() {
ann3.announce(val + 5).unwrap();
break;
}
thread::yield_now();
});
// Get final result
let listener3 = announcement3.listener();
let start = std::time::Instant::now();
loop {
if let Some(result) = listener3.try_listen() {
assert_eq!(result, 25); // 10 * 2 + 5
break;
}
if start.elapsed() > Duration::from_secs(1) {
panic!("Pipeline timeout");
}
thread::yield_now();
}
}
/// Test: Concurrency test: scatter gather pattern
///
/// Verifies: Verifies scatter gather pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_scatter_gather_pattern() {
let (announcer, announcement) = Announcement::new();
let results = Arc::new(Mutex::new(Vec::new()));
// Scatter work to multiple threads
let mut workers = Vec::new();
for i in 0..10 {
let listener = announcement.listener();
let results = results.clone();
workers.push(thread::spawn(move || loop {
if let Some(base) = listener.try_listen() {
let result = base + i;
results.lock().unwrap().push(result);
break;
}
thread::yield_now();
}));
}
// Scatter the work
announcer.announce(100).unwrap();
// Wait for all workers
for worker in workers {
worker.join().unwrap();
}
// Gather results
let final_results = results.lock().unwrap();
assert_eq!(final_results.len(), 10);
let sum: i32 = final_results.iter().sum();
assert_eq!(sum, 1045); // 100*10 + sum(0..10)
}
/// Test: Concurrency test: rendezvous pattern
///
/// Verifies: Verifies rendezvous pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_rendezvous_pattern() {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(5));
let mut handles = Vec::new();
for i in 0..5 {
let listener = announcement.listener();
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
// Everyone waits at rendezvous point
barrier.wait();
// Then check for announcement
loop {
if let Some(msg) = listener.try_listen() {
return (i, msg);
}
thread::yield_now();
}
}));
}
// After all threads reach rendezvous, announce
thread::sleep(Duration::from_millis(10));
announcer.announce("rendezvous complete").unwrap();
for handle in handles {
let (_, msg) = handle.join().unwrap();
assert_eq!(msg, "rendezvous complete");
}
}
/// Test: Concurrency test: map reduce pattern
///
/// Verifies: Verifies map reduce pattern works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_map_reduce_pattern() {
// Create separate channels for each mapper
let mut channels = Vec::new();
let mut mappers = Vec::new();
for _ in 0..10 {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
channels.push(announcer);
mappers.push(thread::spawn(move || {
loop {
if let Some(data) = listener.try_listen() {
// Map function: square the value
return data * data;
}
thread::yield_now();
}
}));
}
// Distribute data to each mapper
for (i, announcer) in channels.into_iter().enumerate() {
announcer.announce((i + 1) as i32).unwrap();
}
// Reduce phase
let mut results = Vec::new();
for mapper in mappers {
results.push(mapper.join().unwrap());
}
// Reduce function: sum
let total: i32 = results.iter().sum();
assert_eq!(total, 385); // Sum of squares 1..10
}
/// Test: Concurrency test: mutex contention
///
/// Verifies: Verifies mutex contention works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_mutex_contention() {
let (announcer, announcement) = Announcement::new();
let shared_counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..50 {
let listener = announcement.listener();
let counter = shared_counter.clone();
handles.push(thread::spawn(move || {
loop {
if listener.try_listen().is_some() {
// High contention on mutex
for _ in 0..100 {
let mut count = counter.lock().unwrap();
*count += 1;
}
break;
}
thread::yield_now();
}
}));
}
// Trigger the contention
announcer.announce(()).unwrap();
for handle in handles {
handle.join().unwrap();
}
assert_eq!(*shared_counter.lock().unwrap(), 5000); // 50 threads * 100
}
/// Test: Concurrency test: recursive spawning
///
/// Verifies: Verifies recursive spawning works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_recursive_spawning() {
fn spawn_recursive(
announcement: Announcement<i32>,
depth: usize,
) -> Vec<thread::JoinHandle<i32>> {
if depth == 0 {
return vec![];
}
let mut handles = vec![];
for _ in 0..2 {
let ann = announcement.clone();
handles.push(thread::spawn(move || {
let listener = ann.listener();
loop {
if let Some(val) = listener.try_listen() {
return val;
}
thread::yield_now();
}
}));
}
handles.extend(spawn_recursive(announcement, depth - 1));
handles
}
let (announcer, announcement) = Announcement::new();
let handles = spawn_recursive(announcement, 5); // Creates 2^5 - 1 = 31 threads
announcer.announce(42).unwrap();
for handle in handles {
assert_eq!(handle.join().unwrap(), 42);
}
}
/// Test: Concurrency test: priority inversion
///
/// Verifies: Verifies priority inversion works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_priority_inversion() {
let (announcer, announcement) = Announcement::new();
let low_priority = thread::spawn(move || {
// Simulate low priority by yielding
for _ in 0..100 {
thread::yield_now();
}
announcer.announce(42).unwrap();
});
let high_priority_listener = announcement.listener();
let high_priority = thread::spawn(move || {
// High priority tries to get value quickly
let start = std::time::Instant::now();
loop {
if let Some(val) = high_priority_listener.try_listen() {
return (val, start.elapsed());
}
// Don't yield - simulate high priority
}
});
low_priority.join().unwrap();
let (value, elapsed) = high_priority.join().unwrap();
assert_eq!(value, 42);
println!("Priority inversion test completed in {:?}", elapsed);
}
/// Test: Deadlock prevention: deadlock prevention
///
/// Verifies: Verifies deadlock prevention cannot cause deadlock
/// Category: concurrency
#[test]
fn test_deadlock_prevention() {
let (announcer1, announcement1) = Announcement::new();
let (announcer2, announcement2) = Announcement::new();
let listener1 = announcement1.listener();
let listener2 = announcement2.listener();
// These threads would deadlock if announcement had internal locks
let h1 = thread::spawn(move || {
announcer1.announce(1).unwrap();
loop {
if let Some(val) = listener2.try_listen() {
return val;
}
thread::yield_now();
}
});
let h2 = thread::spawn(move || {
announcer2.announce(2).unwrap();
loop {
if let Some(val) = listener1.try_listen() {
return val;
}
thread::yield_now();
}
});
// Should complete without deadlock
assert_eq!(h1.join().unwrap(), 2);
assert_eq!(h2.join().unwrap(), 1);
}
/// Test: Concurrency test: memory ordering
///
/// Verifies: Verifies memory ordering works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_memory_ordering() {
use std::sync::atomic::AtomicUsize;
let (announcer, announcement) = Announcement::new();
let counter = Arc::new(AtomicUsize::new(0));
// Writer thread
let counter_clone = counter.clone();
let writer = thread::spawn(move || {
counter_clone.store(42, Ordering::Relaxed);
announcer.announce(()).unwrap();
});
// Reader threads
let mut readers = vec![];
for _ in 0..10 {
let listener = announcement.listener();
let counter_clone = counter.clone();
readers.push(thread::spawn(move || {
loop {
if listener.try_listen().is_some() {
// Should see the write due to synchronization
return counter_clone.load(Ordering::Relaxed);
}
thread::yield_now();
}
}));
}
writer.join().unwrap();
for reader in readers {
assert_eq!(reader.join().unwrap(), 42);
}
}
/// Test: Concurrency test: thread local interaction
///
/// Verifies: Verifies thread local interaction works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_thread_local_interaction() {
thread_local! {
static THREAD_ID: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
THREAD_ID.with(|id| id.set(i));
loop {
if let Some(val) = listener.try_listen() {
return (THREAD_ID.with(|id| id.get()), val);
}
thread::yield_now();
}
}));
}
announcer.announce(42).unwrap();
let mut thread_ids = HashSet::new();
for handle in handles {
let (id, val) = handle.join().unwrap();
thread_ids.insert(id);
assert_eq!(val, 42);
}
// Each thread should have unique ID
assert_eq!(thread_ids.len(), 10);
}
// Tests 26-80: More concurrency patterns
// I'll add a few more representative tests
/// Test: Concurrency test: spinlock waiting
///
/// Verifies: Verifies spinlock waiting works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_spinlock_waiting() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for _ in 0..20 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
let mut spins = 0u64;
loop {
spins += 1;
if let Some(val) = listener.try_listen() {
return (spins, val);
}
std::hint::spin_loop();
}
}));
}
thread::sleep(Duration::from_micros(100));
announcer.announce(42).unwrap();
for handle in handles {
let (spins, val) = handle.join().unwrap();
assert_eq!(val, 42);
println!("Thread spun {} times", spins);
}
}
/// Test: Concurrency test: mpsc simulation
///
/// Verifies: Verifies mpsc simulation works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_mpsc_simulation() {
let (announcer, announcement) = Announcement::new();
let received = Arc::new(Mutex::new(Vec::new()));
// Multiple producers (senders)
let mut producers = vec![];
for i in 0..10 {
let received = received.clone();
producers.push(thread::spawn(move || {
// Simulate sending by adding to shared vec
received.lock().unwrap().push(i);
}));
}
// Wait for all "sends"
for p in producers {
p.join().unwrap();
}
// Single consumer announces when done
announcer
.announce(received.lock().unwrap().clone())
.unwrap();
// Verify
let listener = announcement.listener();
let data = listener.try_listen().unwrap();
assert_eq!(data.len(), 10);
}
// Continue with more diverse tests through Test 80...
// For brevity, I'll add one more comprehensive test
/// Test: Concurrency test: comprehensive concurrency
///
/// Verifies: Verifies comprehensive concurrency works correctly under concurrent access
/// Category: concurrency
#[test]
fn test_comprehensive_concurrency() {
let num_iterations = 100;
for iteration in 0..num_iterations {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
// Mix of different concurrent operations
for i in 0..20 {
let ann = announcement.clone();
handles.push(thread::spawn(move || {
match i % 5 {
0 => {
// Rapid listener creation
let mut listeners = vec![];
for _ in 0..10 {
listeners.push(ann.listener());
}
listeners
.into_iter()
.map(|l| l.try_listen())
.collect::<Vec<_>>()
}
1 => {
// Single listener, multiple checks
let l = ann.listener();
let mut results = vec![];
for _ in 0..10 {
results.push(l.try_listen());
}
results
}
2 => {
// Clone and check
let l1 = ann.listener();
let l2 = l1.clone();
vec![l1.try_listen(), l2.try_listen()]
}
3 => {
// is_announced checks
let mut results = vec![];
for _ in 0..10 {
results.push(if ann.is_announced() {
Some(iteration)
} else {
None
});
}
results
}
_ => {
// Mixed operations
let l = ann.listener();
vec![
l.try_listen(),
if ann.is_announced() {
Some(iteration)
} else {
None
},
l.try_listen(),
]
}
}
}));
}
// Announce at different times
if iteration % 2 == 0 {
announcer.announce(iteration).unwrap();
} else {
thread::sleep(Duration::from_micros(10));
announcer.announce(iteration).unwrap();
}
// Verify all threads complete successfully
for handle in handles {
let results = handle.join().unwrap();
for val in results.into_iter().flatten() {
assert_eq!(val, iteration);
}
}
}
}