#![cfg(all(feature = "trace", feature = "parallel", target_os = "linux"))]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use threadteam::trace::{self, Kind};
use threadteam::Pool;
const PARK_DELAY: Duration = Duration::from_secs(60);
const DEADLINE: Duration = Duration::from_secs(10);
fn watchdog() -> impl Drop {
struct Stop(std::sync::mpsc::Sender<()>);
impl Drop for Stop {
fn drop(&mut self) {
let _ = self.0.send(());
}
}
let (tx, rx) = std::sync::mpsc::channel::<()>();
std::thread::spawn(move || {
if rx.recv_timeout(Duration::from_secs(120)).is_err() {
eprintln!("watchdog: test stalled");
std::process::exit(101);
}
});
Stop(tx)
}
fn own_cpus() -> Vec<usize> {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
let r = libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut set);
assert_eq!(r, 0, "sched_getaffinity failed");
(0..libc::CPU_SETSIZE as usize)
.filter(|&cpu| libc::CPU_ISSET(cpu, &set))
.collect()
}
}
fn set_own_cpus(cpus: &[usize]) -> bool {
unsafe {
let mut set: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut set);
for &cpu in cpus {
libc::CPU_SET(cpu, &mut set);
}
libc::sched_setaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &set) == 0
}
}
struct Restrict(Vec<usize>);
impl Drop for Restrict {
fn drop(&mut self) {
set_own_cpus(&self.0);
}
}
fn members_that_ran(events: &[trace::Event]) -> Vec<usize> {
let mut out: Vec<usize> = Vec::new();
for e in events {
if e.kind == Kind::SectionEnd && !out.contains(&e.member) {
out.push(e.member);
}
}
out
}
fn wait_until_members_parked(team: &threadteam::Team) {
let start = Instant::now();
loop {
let events = trace::events(team.pool());
let members = members_that_ran(&events);
let parked = !members.is_empty()
&& members.iter().all(|&member| {
let last_end = events
.iter()
.filter(|e| e.member == member && e.kind == Kind::SectionEnd)
.map(|e| e.ns)
.max();
let park = events
.iter()
.filter(|e| e.member == member && e.kind == Kind::ParkEnter)
.map(|e| e.ns)
.max();
matches!((last_end, park), (Some(end), Some(park)) if park > end)
});
if parked {
assert_eq!(
members.len(),
team.size().get() - 1,
"every member ran a share"
);
return;
}
assert!(
start.elapsed() < DEADLINE,
"no member parked within {DEADLINE:?}: the scope kept the policy's {PARK_DELAY:?} delay"
);
std::thread::sleep(Duration::from_millis(2));
}
}
#[test]
fn an_oversubscribed_root_scope_parks_its_members_quickly() {
let _w = watchdog();
let all = own_cpus();
if all.len() < 3 {
eprintln!("skipped: need at least 3 allowed CPUs to restrict to 2");
return;
}
let cpus = vec![all[0], all[1]];
if !set_own_cpus(&cpus) {
eprintln!("skipped: cannot restrict the calling thread to {cpus:?}");
return;
}
let _restore = Restrict(all);
let pool = Pool::builder()
.threads(4)
.idle_spin(PARK_DELAY)
.acquire_timeout(Duration::from_secs(10))
.build()
.unwrap();
let team = pool.into_team(4).expect("an idle pool");
let width = team.size().get();
assert!(width > cpus.len(), "4 participants on 2 CPUs");
let sum = AtomicUsize::new(0);
let exec = team.exec(width);
exec.broadcast(|i| {
sum.fetch_add(i + 1, Ordering::Relaxed);
})
.expect("a team token broadcasts");
assert_eq!(sum.load(Ordering::Relaxed), (1..=width).sum::<usize>());
wait_until_members_parked(&team);
let mut exec = team.exec(width);
exec.team(|team| {
team.split(&[2, 2], |_, child| child.run(|_, _| {}))
.unwrap();
})
.expect("a team token opens its scope");
wait_until_members_parked(&team);
}
#[test]
fn the_scope_that_enters_decides_the_park_delay_not_the_mint() {
let _w = watchdog();
let all = own_cpus();
if all.len() < 2 {
eprintln!("skipped: need at least 2 allowed CPUs to restrict to 1");
return;
}
let pool = Pool::builder()
.threads(3)
.idle_spin(PARK_DELAY)
.acquire_timeout(Duration::from_secs(10))
.build()
.unwrap();
let team = pool.into_team(3).expect("an idle pool");
let width = team.size().get();
let mut exec = team.exec(width);
let cpus = vec![all[0]];
if !set_own_cpus(&cpus) {
eprintln!("skipped: cannot restrict the calling thread to {cpus:?}");
return;
}
let _restore = Restrict(all);
let sum = AtomicUsize::new(0);
exec.team(|mut team| {
team.reborrow().run(|i, _| {
sum.fetch_add(i + 1, Ordering::Relaxed);
});
})
.expect("a team token opens its scope");
assert_eq!(sum.load(Ordering::Relaxed), (1..=width).sum::<usize>());
wait_until_members_parked(&team);
}