1040 lines
31 KiB
Rust
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();
|
|
}
|
|
}
|
|
}
|