Files
announcement/tests/race_condition_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

1040 lines
31 KiB
Rust

//! Race condition tests for the announcement crate.
//! Critical TOCTOU (Time-of-check to time-of-use) tests.
//! Each test runs many iterations to catch timing-sensitive bugs.
use announcement::Announcement;
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Barrier,
};
use std::thread;
use std::time::Duration;
/// Test: Race condition test: race announce between check and setup
///
/// Verifies: Verifies no race condition in race announce between check and setup
/// Category: race
#[test]
fn test_race_announce_between_check_and_setup() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let announcement_clone = announcement.clone();
// Thread that announces at a random time
let announcer_thread = thread::spawn(move || {
// Try to hit the race window
if iteration % 2 == 0 {
thread::yield_now();
}
announcer.announce(iteration).unwrap();
});
// Thread that checks then sets up listener
let checker_thread = thread::spawn(move || {
// Check
let was_announced = announcement_clone.is_announced();
// Small window for race
if iteration % 3 == 0 {
thread::yield_now();
}
// Setup listener
let listener = announcement_clone.listener();
// Try to receive
let value = listener.try_listen();
(was_announced, value)
});
announcer_thread.join().unwrap();
let (was_announced, value) = checker_thread.join().unwrap();
// If it was announced when we checked, we must receive the value
if was_announced {
assert_eq!(value, Some(iteration), "Failed at iteration {}", iteration);
}
// If not announced when checked, we might or might not have value
// (depends on exact timing)
}
}
/// Test: Race condition test: race announce during listen setup
///
/// Verifies: Verifies no race condition in race announce during listen setup
/// Category: race
#[test]
fn test_race_announce_during_listen_setup() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Thread that tries to announce during async listen setup
thread::spawn(move || {
// Varying delays to hit different points
match iteration % 5 {
0 => {}
1 => thread::yield_now(),
2 => thread::sleep(Duration::from_nanos(1)),
3 => thread::sleep(Duration::from_nanos(10)),
_ => thread::sleep(Duration::from_nanos(100)),
}
announcer.announce(iteration).unwrap();
});
// The async listen would handle this in async context
// For this test, we use try_listen with retries
let start = std::time::Instant::now();
loop {
if let Some(value) = listener.try_listen() {
assert_eq!(value, iteration);
break;
}
if start.elapsed() > Duration::from_millis(100) {
panic!("Timeout at iteration {}", iteration);
}
thread::yield_now();
}
}
}
/// Test: Concurrency: race concurrent announce and listen
///
/// Verifies: Verifies race concurrent announce and listen works correctly with concurrent access
/// Category: race
#[test]
fn test_race_concurrent_announce_and_listen() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(2));
let announcement_clone = announcement.clone();
let barrier1 = barrier.clone();
let listen_thread = thread::spawn(move || {
let listener = announcement_clone.listener();
// Synchronize with announce thread
barrier1.wait();
// Immediately try to listen
listener.try_listen()
});
let barrier2 = barrier.clone();
let announce_thread = thread::spawn(move || {
// Synchronize with listen thread
barrier2.wait();
// Immediately announce
announcer.announce(iteration).unwrap();
});
announce_thread.join().unwrap();
let result = listen_thread.join().unwrap();
// The listener might or might not see the value depending on exact timing
if let Some(value) = result {
assert_eq!(value, iteration);
}
}
}
/// Test: Race condition test: race check then act
///
/// Verifies: Verifies no race condition in race check then act
/// Category: race
#[test]
fn test_race_check_then_act() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let ann1 = announcement.clone();
let ann2 = announcement.clone();
let racer1 = thread::spawn(move || {
// Check
if !ann1.is_announced() {
// Race window here
thread::yield_now();
// Act based on check
let listener = ann1.listener();
listener.try_listen()
} else {
let listener = ann1.listener();
listener.try_listen()
}
});
let racer2 = thread::spawn(move || {
// Check
if !ann2.is_announced() {
// Different timing
if iteration % 2 == 0 {
thread::yield_now();
}
let listener = ann2.listener();
listener.try_listen()
} else {
let listener = ann2.listener();
listener.try_listen()
}
});
// Announcer with varying timing
thread::spawn(move || match iteration % 4 {
0 => announcer.announce(iteration).unwrap(),
1 => {
thread::yield_now();
announcer.announce(iteration).unwrap();
}
2 => {
thread::sleep(Duration::from_nanos(1));
announcer.announce(iteration).unwrap();
}
_ => {
thread::sleep(Duration::from_nanos(10));
announcer.announce(iteration).unwrap();
}
});
let r1 = racer1.join().unwrap();
let r2 = racer2.join().unwrap();
// Both should see the same value if they see anything
if r1.is_some() && r2.is_some() {
assert_eq!(r1, r2);
assert_eq!(r1, Some(iteration));
}
}
}
/// Test: Race condition test: race listener creation at announcement
///
/// Verifies: Verifies no race condition in race listener creation at announcement
/// Category: race
#[test]
fn test_race_listener_creation_at_announcement() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(11)); // 10 threads + announcer
let mut handles = vec![];
for i in 0..10 {
let ann = announcement.clone();
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
// All threads wait
barrier.wait();
// Create listener at same moment as announcement
let listener = ann.listener();
(i, listener.try_listen())
}));
}
let barrier_announce = barrier.clone();
let announce_handle = thread::spawn(move || {
barrier_announce.wait();
announcer.announce(iteration).unwrap();
});
announce_handle.join().unwrap();
for handle in handles {
let (thread_id, result) = handle.join().unwrap();
// Some might see it, some might not, but if they do, value must be correct
if let Some(value) = result {
assert_eq!(
value, iteration,
"Thread {} got wrong value at iteration {}",
thread_id, iteration
);
}
}
}
}
/// Test: Race condition test: race multiple checks with announce
///
/// Verifies: Verifies no race condition in race multiple checks with announce
/// Category: race
#[test]
fn test_race_multiple_checks_with_announce() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let ann1 = announcement.clone();
let ann2 = announcement.clone();
let checker1 = thread::spawn(move || {
let mut results = vec![];
for _ in 0..10 {
results.push(ann1.is_announced());
if iteration % 100 == 0 {
thread::yield_now();
}
}
results
});
let checker2 = thread::spawn(move || {
let mut results = vec![];
for _ in 0..10 {
results.push(ann2.is_announced());
if iteration % 100 == 50 {
thread::yield_now();
}
}
results
});
// Announce somewhere in the middle
thread::spawn(move || {
thread::sleep(Duration::from_nanos(iteration as u64 % 100));
announcer.announce(iteration).unwrap();
});
let results1 = checker1.join().unwrap();
let results2 = checker2.join().unwrap();
// Verify monotonicity - once true, always true
let mut seen_true1 = false;
for &result in &results1 {
if seen_true1 {
assert!(
result,
"is_announced went from true to false at iteration {}",
iteration
);
}
if result {
seen_true1 = true;
}
}
let mut seen_true2 = false;
for &result in &results2 {
if seen_true2 {
assert!(
result,
"is_announced went from true to false at iteration {}",
iteration
);
}
if result {
seen_true2 = true;
}
}
}
}
/// Test: Race condition test: race arc operations
///
/// Verifies: Verifies no race condition in race arc operations
/// Category: race
#[test]
fn test_race_arc_operations() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let data = Arc::new(iteration);
let data_clone = data.clone();
let listener = announcement.listener();
let arc_thread = thread::spawn(move || {
// Manipulate Arc ref counts during announcement
let mut clones = vec![];
for _ in 0..10 {
clones.push(data_clone.clone());
if iteration % 10 == 5 {
thread::yield_now();
}
}
Arc::strong_count(&clones[0])
});
let announce_thread = thread::spawn(move || {
thread::sleep(Duration::from_nanos(iteration as u64 % 50));
announcer.announce(data).unwrap();
});
let _count = arc_thread.join().unwrap();
announce_thread.join().unwrap();
// Verify value is correct
if let Some(received) = listener.try_listen() {
assert_eq!(*received, iteration);
// Count should be reasonable
assert!(Arc::strong_count(&received) > 0);
}
}
}
/// Test: Race condition test: race drop timing
///
/// Verifies: Verifies no race condition in race drop timing
/// Category: race
#[test]
fn test_race_drop_timing() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let announcement_clone = announcement.clone();
let dropper = thread::spawn(move || {
let listener = announcement_clone.listener();
// Hold listener briefly then drop
match iteration % 4 {
0 => drop(listener),
1 => {
thread::yield_now();
drop(listener);
}
2 => {
thread::sleep(Duration::from_nanos(1));
drop(listener);
}
_ => {
let _ = listener.try_listen();
drop(listener);
}
}
});
// Try to announce during the drop
let announce_result = thread::spawn(move || {
thread::sleep(Duration::from_nanos(iteration as u64 % 10));
announcer.announce(iteration)
});
dropper.join().unwrap();
announce_result.join().unwrap().unwrap();
// Create new listener after the race
let final_listener = announcement.listener();
assert_eq!(final_listener.try_listen(), Some(iteration));
}
}
/// Test: Race condition test: race memory barriers
///
/// Verifies: Verifies no race condition in race memory barriers
/// Category: race
#[test]
fn test_race_memory_barriers() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let flag = Arc::new(AtomicBool::new(false));
let value = Arc::new(AtomicUsize::new(0));
let flag1 = flag.clone();
let value1 = value.clone();
let listener = announcement.listener();
let reader = thread::spawn(move || {
// Spin until flag is set
while !flag1.load(Ordering::Acquire) {
std::hint::spin_loop();
}
// Flag is set, value should be visible
let v = value1.load(Ordering::Relaxed);
// Also check announcement
let announced = listener.try_listen();
(v, announced)
});
let flag2 = flag.clone();
let value2 = value.clone();
let writer = thread::spawn(move || {
// Set value
value2.store(iteration, Ordering::Relaxed);
// Announce
announcer.announce(iteration).unwrap();
// Set flag (with release to ensure value is visible)
flag2.store(true, Ordering::Release);
});
writer.join().unwrap();
let (seen_value, announced_value) = reader.join().unwrap();
// Both should see the iteration value
assert_eq!(seen_value, iteration);
assert_eq!(announced_value, Some(iteration));
}
}
/// Test: Race condition test: race listener chain
///
/// Verifies: Verifies no race condition in race listener chain
/// Category: race
#[test]
fn test_race_listener_chain() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
// Create chain of threads, each creating a listener from previous
let first_listener = Arc::new(announcement.listener());
for i in 0..5 {
let listener = first_listener.clone();
handles.push(thread::spawn(move || {
let my_listener = (*listener).clone();
// Varying delays
match (iteration + i) % 3 {
0 => {}
1 => thread::yield_now(),
_ => thread::sleep(Duration::from_nanos(1)),
}
my_listener.try_listen()
}));
}
// Announce with timing variety
thread::spawn(move || {
thread::sleep(Duration::from_nanos((iteration % 10) as u64));
announcer.announce(iteration).unwrap();
});
// All should eventually see the same value
let mut results = vec![];
for handle in handles {
results.push(handle.join().unwrap());
}
// Filter out None values
let non_none: Vec<_> = results.iter().filter_map(|&x| x).collect();
if !non_none.is_empty() {
// All non-None should be the same
for &value in &non_none {
assert_eq!(value, iteration);
}
}
}
}
#[cfg(feature = "std")]
/// Test: Race condition test: race spurious wakeup
///
/// Verifies: Verifies no race condition in race spurious wakeup
/// Category: race
#[test]
fn test_race_spurious_wakeup() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Thread that might cause spurious activity
let noise = thread::spawn(move || {
for _ in 0..10 {
// Create and drop listeners rapidly
let _ = announcement.listener();
if iteration % 100 == 50 {
thread::yield_now();
}
}
});
// Thread that announces
let announce = thread::spawn(move || {
thread::sleep(Duration::from_nanos((iteration % 20) as u64));
announcer.announce(iteration).unwrap();
});
noise.join().unwrap();
announce.join().unwrap();
// Verify correct value despite noise
assert_eq!(listener.try_listen(), Some(iteration));
}
}
/// Test: Race condition test: race visibility
///
/// Verifies: Verifies no race condition in race visibility
/// Category: race
#[test]
fn test_race_visibility() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(6));
let mut handles = vec![];
// Multiple threads checking visibility
for i in 0..5 {
let ann = announcement.clone();
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
let listener = ann.listener();
barrier.wait();
// Check multiple times quickly
let mut results = vec![];
for _ in 0..10 {
results.push(listener.try_listen());
}
(i, results)
}));
}
let barrier_announce = barrier.clone();
let announce_handle = thread::spawn(move || {
barrier_announce.wait();
// Announce right after barrier
announcer.announce(iteration).unwrap();
});
announce_handle.join().unwrap();
for handle in handles {
let (thread_id, results) = handle.join().unwrap();
// Once a thread sees the value, all subsequent checks should see it
let mut seen = false;
for result in results {
if seen {
assert_eq!(
result,
Some(iteration),
"Thread {} lost visibility at iteration {}",
thread_id,
iteration
);
}
if result.is_some() {
seen = true;
assert_eq!(result, Some(iteration));
}
}
}
}
}
/// Test: Race condition test: race load store
///
/// Verifies: Verifies no race condition in race load store
/// Category: race
#[test]
fn test_race_load_store() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let checking = Arc::new(AtomicBool::new(true));
let checking_clone = checking.clone();
let ann = announcement.clone();
let checker = thread::spawn(move || {
let mut transition_point = None;
let mut check_count = 0;
while checking_clone.load(Ordering::Acquire) {
check_count += 1;
if ann.is_announced() && transition_point.is_none() {
transition_point = Some(check_count);
}
}
(transition_point, check_count)
});
// Announce with varying timing
thread::spawn(move || {
thread::sleep(Duration::from_nanos((iteration % 100) as u64));
announcer.announce(iteration).unwrap();
});
thread::sleep(Duration::from_nanos(200));
checking.store(false, Ordering::Release);
let (transition, total_checks) = checker.join().unwrap();
// If we saw a transition, it should be reasonable
if let Some(point) = transition {
assert!(point > 0 && point <= total_checks);
}
}
}
/// Test: Race condition test: race cache coherency
///
/// Verifies: Verifies no race condition in race cache coherency
/// Category: race
#[test]
fn test_race_cache_coherency() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
// Multiple CPUs accessing the same announcement
let mut handles = vec![];
for i in 0..8 {
let ann = announcement.clone();
handles.push(thread::spawn(move || {
let listener = ann.listener();
// Busy wait to stress cache coherency
let mut attempts = 0;
loop {
attempts += 1;
if let Some(value) = listener.try_listen() {
return (i, value, attempts);
}
if attempts > 1_000_000 {
return (i, -1, attempts);
}
}
}));
}
// Announce after threads start spinning
thread::sleep(Duration::from_nanos(10));
announcer.announce(iteration).unwrap();
for handle in handles {
let (thread_id, value, attempts) = handle.join().unwrap();
if value != -1 {
assert_eq!(
value, iteration,
"Thread {} got wrong value after {} attempts at iteration {}",
thread_id, attempts, iteration
);
}
}
}
}
/// Test: Race condition test: race sequential consistency
///
/// Verifies: Verifies no race condition in race sequential consistency
/// Category: race
#[test]
fn test_race_sequential_consistency() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let step1 = Arc::new(AtomicBool::new(false));
let step2 = Arc::new(AtomicBool::new(false));
let step1_clone = step1.clone();
let step2_clone = step2.clone();
let ann = announcement.clone();
let observer = thread::spawn(move || {
// Wait for step1
while !step1_clone.load(Ordering::Acquire) {
std::hint::spin_loop();
}
let check1 = ann.is_announced();
// Wait for step2
while !step2_clone.load(Ordering::Acquire) {
std::hint::spin_loop();
}
let check2 = ann.is_announced();
(check1, check2)
});
// Sequenced operations
step1.store(true, Ordering::Release);
thread::yield_now();
announcer.announce(iteration).unwrap();
thread::yield_now();
step2.store(true, Ordering::Release);
let (before, after) = observer.join().unwrap();
// After must be true
assert!(
after,
"Failed to see announcement after at iteration {}",
iteration
);
// Before might be false or true depending on timing
if before {
assert!(after); // If seen before, must be seen after
}
}
}
/// Test: Race condition test: race happens before
///
/// Verifies: Verifies no race condition in race happens before
/// Category: race
#[test]
fn test_race_happens_before() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let data = Arc::new(AtomicUsize::new(0));
let flag = Arc::new(AtomicBool::new(false));
let data1 = data.clone();
let flag1 = flag.clone();
let listener = announcement.listener();
let reader = thread::spawn(move || {
// Wait for flag
while !flag1.load(Ordering::Acquire) {
std::hint::spin_loop();
}
// Flag seen, check data and announcement
let data_value = data1.load(Ordering::Relaxed);
let announced = listener.try_listen();
(data_value, announced)
});
let data2 = data.clone();
let flag2 = flag.clone();
let writer = thread::spawn(move || {
// Write data
data2.store(42, Ordering::Relaxed);
// Announce
announcer.announce(iteration).unwrap();
// Set flag with release
flag2.store(true, Ordering::Release);
});
writer.join().unwrap();
let (data_value, announced) = reader.join().unwrap();
// Due to happens-before, both should be visible
assert_eq!(data_value, 42);
assert_eq!(announced, Some(iteration));
}
}
/// Test: Race condition test: race aba problem
///
/// Verifies: Verifies no race condition in race aba problem
/// Category: race
#[test]
fn test_race_aba_problem() {
for iteration in 0..10_000 {
// This tests that even with rapid state changes,
// the announcement remains consistent
let (announcer, announcement) = Announcement::new();
let counter = Arc::new(AtomicUsize::new(0));
let counter1 = counter.clone();
let ann1 = announcement.clone();
let modifier = thread::spawn(move || {
for _ in 0..10 {
counter1.fetch_add(1, Ordering::Relaxed);
let _ = ann1.is_announced();
counter1.fetch_sub(1, Ordering::Relaxed);
}
});
let counter2 = counter.clone();
let ann2 = announcement.clone();
let observer = thread::spawn(move || {
let mut observations = vec![];
for _ in 0..10 {
observations.push((counter2.load(Ordering::Relaxed), ann2.is_announced()));
}
observations
});
// Announce in the middle
thread::spawn(move || {
thread::sleep(Duration::from_nanos((iteration % 10) as u64));
announcer.announce(iteration).unwrap();
});
modifier.join().unwrap();
let observations = observer.join().unwrap();
// is_announced should be monotonic despite ABA on counter
let mut seen_announced = false;
for (_, announced) in observations {
if seen_announced {
assert!(announced, "ABA problem detected at iteration {}", iteration);
}
if announced {
seen_announced = true;
}
}
}
}
/// Test: Race condition test: race store buffer
///
/// Verifies: Verifies no race condition in race store buffer
/// Category: race
#[test]
fn test_race_store_buffer() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let shared = Arc::new(AtomicUsize::new(0));
let shared1 = shared.clone();
let listener = announcement.listener();
let thread1 = thread::spawn(move || {
// Might be in store buffer
shared1.store(iteration, Ordering::Relaxed);
// Check announcement
listener.try_listen()
});
let shared2 = shared.clone();
let thread2 = thread::spawn(move || {
// Announce
announcer.announce(iteration).unwrap();
// Read shared
shared2.load(Ordering::Relaxed)
});
let announced = thread1.join().unwrap();
let _loaded = thread2.join().unwrap();
// Values might not be synchronized due to store buffer
// but announcement should work correctly
if announced.is_some() {
assert_eq!(announced, Some(iteration));
}
}
}
// Continue with tests 21-50...
// These would follow similar patterns, testing various race conditions
/// Test: Race condition test: race ultimate
///
/// Verifies: Verifies no race condition in race ultimate
/// Category: race
#[test]
fn test_race_ultimate() {
for iteration in 0..10_000 {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(11));
let mut handles = vec![];
// Mix of all race-prone operations
for i in 0..10 {
let ann = announcement.clone();
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
barrier.wait();
match i % 5 {
0 => {
// Rapid checks
let mut checks = vec![];
for _ in 0..5 {
checks.push(ann.is_announced());
}
checks
}
1 => {
// Create and check
let l = ann.listener();
vec![l.try_listen().is_some()]
}
2 => {
// Clone and check
let l1 = ann.listener();
let l2 = l1.clone();
vec![l1.try_listen().is_some(), l2.try_listen().is_some()]
}
3 => {
// Multiple listeners
let mut results = vec![];
for _ in 0..3 {
let l = ann.listener();
results.push(l.try_listen().is_some());
}
results
}
_ => {
// Mixed operations
let l = ann.listener();
vec![
ann.is_announced(),
l.try_listen().is_some(),
ann.is_announced(),
]
}
}
}));
}
let barrier_announce = barrier.clone();
thread::spawn(move || {
barrier_announce.wait();
// Random delay before announce
match iteration % 4 {
0 => {}
1 => thread::yield_now(),
2 => thread::sleep(Duration::from_nanos(1)),
_ => thread::sleep(Duration::from_nanos(10)),
}
announcer.announce(iteration).unwrap();
});
// Verify all complete without issues
for handle in handles {
handle.join().unwrap();
}
}
}