113 lines
3.6 KiB
Rust
113 lines
3.6 KiB
Rust
//! L2 — relibc pthread conditional variable and mutex stress.
|
|
//!
|
|
//! Exercises `pthread_cond_signal` / `pthread_cond_broadcast` with
|
|
//! `pthread_mutex_lock` / `pthread_mutex_unlock` via relibc's `libc`
|
|
//! bindings. On the host, glibc supplies the real implementation;
|
|
//! on the Redox target, relibc implements them over futex.
|
|
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::{Arc, Condvar, Mutex};
|
|
use std::thread;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use crate::{Layer, LayerOutcome, LayerTest, Progress, SuiteSpec};
|
|
|
|
pub struct PthreadTest {
|
|
contention_rounds: u64,
|
|
}
|
|
|
|
impl PthreadTest {
|
|
pub fn new(spec: &SuiteSpec) -> Self {
|
|
Self {
|
|
contention_rounds: spec.futex_contention_rounds,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LayerTest for PthreadTest {
|
|
fn layer(&self) -> Layer { Layer::Pthread }
|
|
|
|
fn description(&self) -> &'static str {
|
|
"L2: relibc pthread cond/mutex signal/broadcast"
|
|
}
|
|
|
|
fn run(&self, progress: &mut dyn Progress) -> LayerOutcome {
|
|
let start = Instant::now();
|
|
let rounds = self.contention_rounds.max(1);
|
|
let num_waiters = 8usize;
|
|
let total_ops = Arc::new(AtomicU64::new(0));
|
|
|
|
// Pattern per round:
|
|
// 1. Spawn N waiter threads that wait on a fresh condvar.
|
|
// 2. Main thread broadcasts, all wake.
|
|
// 3. Waiters exit; main joins them.
|
|
// No shared barrier — each round has its own condvar pair.
|
|
|
|
for round in 0..rounds {
|
|
let pair = Arc::new((Mutex::new(false), Condvar::new()));
|
|
let mut handles = Vec::with_capacity(num_waiters);
|
|
|
|
// Phase A: spawn waiters.
|
|
for _ in 0..num_waiters {
|
|
let p = pair.clone();
|
|
let ops = total_ops.clone();
|
|
handles.push(thread::spawn(move || {
|
|
let (lock, cvar) = &*p;
|
|
let mut ready = lock.lock().unwrap();
|
|
while !*ready {
|
|
ready = cvar.wait(ready).unwrap();
|
|
}
|
|
ops.fetch_add(1, Ordering::SeqCst);
|
|
}));
|
|
}
|
|
|
|
// Give threads time to reach the condvar wait.
|
|
thread::yield_now();
|
|
thread::sleep(Duration::from_micros(500));
|
|
|
|
// Phase B: broadcast.
|
|
{
|
|
let (lock, cvar) = &*pair;
|
|
let mut ready = lock.lock().unwrap();
|
|
*ready = true;
|
|
cvar.notify_all();
|
|
}
|
|
|
|
// Phase C: join all.
|
|
for h in handles {
|
|
let _ = h.join();
|
|
}
|
|
|
|
if round % 10 == 0 || round + 1 == rounds {
|
|
progress.heartbeat(
|
|
round + 1,
|
|
Some(rounds),
|
|
&format!("pthread broadcast round"),
|
|
);
|
|
}
|
|
}
|
|
|
|
let total = total_ops.load(Ordering::SeqCst);
|
|
let expected = rounds * num_waiters as u64;
|
|
let passed = total == expected;
|
|
|
|
LayerOutcome {
|
|
layer: Layer::Pthread,
|
|
passed,
|
|
iterations: rounds,
|
|
wall_time: start.elapsed(),
|
|
detail: format!(
|
|
"pthread: {} rounds x {} waiters => {} wakes (expected {})",
|
|
rounds, num_waiters, total, expected,
|
|
),
|
|
hang_attribution: if passed {
|
|
None
|
|
} else {
|
|
Some(crate::HangAttribution::Relibc {
|
|
detail: format!("pthread condvar wake mismatch: {} vs {}", total, expected),
|
|
})
|
|
},
|
|
}
|
|
}
|
|
}
|