#![cfg(all(feature = "trace", feature = "parallel"))]
use std::time::{Duration, Instant};
use threadteam::trace::{self, Kind};
use threadteam::Pool;
const SPIN: Duration = Duration::from_millis(40);
const DEADLINE: Duration = Duration::from_secs(10);
fn team(width: usize) -> threadteam::Team {
Pool::builder()
.threads(width)
.idle_spin(SPIN)
.acquire_timeout(Duration::from_secs(10))
.build()
.unwrap()
.into_team(width)
.unwrap()
}
fn member_events(team: &threadteam::Team) -> Vec<trace::Event> {
trace::events(team.pool())
.into_iter()
.filter(|e| e.member != usize::MAX)
.collect()
}
fn count(team: &threadteam::Team, kind: Kind) -> usize {
member_events(team)
.iter()
.filter(|e| e.kind == kind)
.count()
}
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 parked_now(team: &threadteam::Team) -> usize {
let mut parked = std::collections::HashSet::new();
for e in member_events(team) {
match e.kind {
Kind::ParkEnter => {
parked.insert(e.member);
}
Kind::ParkExit => {
parked.remove(&e.member);
}
_ => {}
}
}
parked.len()
}
fn wait_until(what: &str, mut cond: impl FnMut() -> bool) {
let start = Instant::now();
while !cond() {
assert!(start.elapsed() < DEADLINE, "timeout waiting for {what}");
std::thread::sleep(Duration::from_millis(2));
}
}
fn every_member_parked_after_its_last_share(events: &[trace::Event]) -> bool {
let members = members_that_ran(events);
!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)
})
}
fn assert_no_park_before_the_delay(team: &threadteam::Team) {
let events = member_events(team);
let parks: Vec<_> = events
.iter()
.filter(|e| e.kind == Kind::ParkEnter)
.collect();
assert!(!parks.is_empty(), "no member parked at all");
for park in parks {
let anchor = events
.iter()
.filter(|e| e.member == park.member && e.ns <= park.ns)
.filter(|e| matches!(e.kind, Kind::SectionEnd | Kind::ParkExit | Kind::Enter))
.map(|e| e.ns)
.max();
let Some(anchor) = anchor else {
panic!("member {} parked without an anchor event", park.member);
};
assert!(
park.ns - anchor >= SPIN.as_nanos() as u64,
"member {} parked {} us after its own last share, wake-up or start",
park.member,
(park.ns - anchor) / 1000
);
}
}
#[test]
fn members_park_after_the_park_delay_since_their_own_last_share_or_wake() {
let team = team(3);
let mut exec = team.exec(3);
if team.size().get() < 3 {
return;
}
for _ in 0..30 {
exec.reborrow().broadcast(|_| {}).unwrap();
std::thread::sleep(Duration::from_millis(5));
}
wait_until("every member parked after its own last share", || {
every_member_parked_after_its_last_share(&member_events(&team))
});
let members = members_that_ran(&member_events(&team));
assert_eq!(
members.len(),
team.size().get() - 1,
"every member ran a share: {members:?}"
);
assert_no_park_before_the_delay(&team);
let parked = parked_now(&team);
assert_eq!(
parked,
team.size().get() - 1,
"every member is parked after the last section"
);
let exits = count(&team, Kind::ParkExit);
exec.reborrow().broadcast(|_| {}).unwrap();
wait_until("every parked member woke", || {
count(&team, Kind::ParkExit) >= exits + parked
});
assert_no_park_before_the_delay(&team);
}