#![cfg(all(feature = "trace", feature = "parallel"))]
use std::time::{Duration, Instant};
use threadteam::trace::{self, Kind};
use threadteam::Pool;
const DEADLINE: Duration = Duration::from_secs(10);
fn team(width: usize) -> threadteam::Team {
Pool::builder()
.threads(width)
.spin_polls(0)
.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_one_park_per_episode(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 before: Vec<_> = events
.iter()
.filter(|e| e.member == park.member && e.ns <= park.ns)
.filter(|e| {
matches!(
e.kind,
Kind::SectionEnd | Kind::ParkExit | Kind::Enter | Kind::ParkEnter
)
})
.collect();
let prev = before
.iter()
.rev()
.find(|e| !std::ptr::eq(**e, park))
.unwrap_or_else(|| panic!("member {} parked without an anchor event", park.member));
assert!(
prev.kind != Kind::ParkEnter,
"member {} parked twice in one idle episode",
park.member
);
}
}
#[test]
fn members_park_once_per_idle_episode_and_wake_on_the_next_section() {
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_one_park_per_episode(&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
});
wait_until("every member parked again", || {
parked_now(&team) == team.size().get() - 1
});
assert_one_park_per_episode(&team);
}