use crate::awake::{Awake, Geometry, JoinGuard, Section};
use crate::cost::{pays_at, width_for_by, DispatchCosts, DispatchKind};
use crate::error::Error;
use crate::policy::{WaitOverride, WaitPolicy};
use crate::split;
use crate::track::Track;
use core::fmt;
use core::num::NonZeroUsize;
use core::time::Duration;
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("a participant of the team section panicked; leave the section")]
pub struct Cancelled;
pub struct Participant<'a> {
index: usize,
section: &'a Section,
}
impl Participant<'_> {
pub fn index(&self) -> usize {
self.index
}
pub fn size(&self) -> usize {
self.section.size()
}
pub fn barrier(&self) -> Result<(), Cancelled> {
if self.section.barrier() {
Ok(())
} else {
Err(Cancelled)
}
}
pub fn is_cancelled(&self) -> bool {
self.section.is_cancelled()
}
}
#[must_use = "a TeamExec is exclusive authority over reserved members; run a section"]
pub struct TeamExec<'team> {
pub(crate) awake: Option<&'team Awake>,
pub(crate) ids: &'team [usize],
pub(crate) policy: WaitPolicy,
pub(crate) track: Track,
pub(crate) over: bool,
}
pub(crate) enum Plan<'a> {
Sizes(&'a [usize]),
Even { size: usize, children: usize },
}
impl Plan<'_> {
pub(crate) fn children(&self) -> usize {
match self {
Plan::Sizes(s) => s.len(),
Plan::Even { children, .. } => *children,
}
}
fn child(&self, c: usize) -> (usize, usize) {
match self {
Plan::Sizes(s) => (s[..c].iter().sum(), s[c]),
Plan::Even { size, children } => {
let q = size / children;
let r = size % children;
(c * q + c.min(r), split::share(*size, *children, c))
}
}
}
}
impl<'team> TeamExec<'team> {
pub fn size(&self) -> NonZeroUsize {
NonZeroUsize::new(self.ids.len() + 1).unwrap_or(NonZeroUsize::MIN)
}
pub fn dispatch_cost(&self, width: NonZeroUsize) -> Duration {
let width = width.min(self.size());
match self.awake {
Some(awake) if width.get() >= 2 => awake.costs.cost(DispatchKind::Team, width),
_ => Duration::MAX,
}
}
pub fn dispatch_costs(&self) -> DispatchCosts {
self.awake
.map_or_else(DispatchCosts::host_default, |a| a.costs)
}
pub fn pays(&self, estimate: Duration, sections: u32, width: NonZeroUsize) -> bool {
pays_at(estimate, sections, self.dispatch_cost(width))
}
pub fn width_for(&self, estimate: Duration, sections: u32) -> NonZeroUsize {
width_for_by(estimate, sections, self.size(), |k| self.dispatch_cost(k))
}
pub fn policy(&self) -> WaitPolicy {
self.policy
}
pub fn with_wait(mut self, over: WaitOverride) -> Self {
self.policy = self.policy.with(over);
self
}
pub fn reborrow(&mut self) -> TeamExec<'_> {
TeamExec {
awake: self.awake,
ids: self.ids,
policy: self.policy,
track: self.track.alias(),
over: self.over,
}
}
pub fn run<F>(self, f: F)
where
F: Fn(usize, usize) + Sync,
{
self.run_with(move |p| f(p.index(), p.size()));
}
pub fn run_with<F>(self, f: F)
where
F: Fn(&Participant<'_>) + Sync,
{
self.run_with_pin(f, false);
}
pub(crate) fn run_with_pin<F>(self, f: F, pin: bool)
where
F: Fn(&Participant<'_>) + Sync,
{
let track = &self.track;
let _released = track.release();
let body = |p: &Participant<'_>| {
let _live = track.task();
f(p);
};
let ids = self.ids;
let geometry = Geometry {
dispatched: ids.len(),
size: ids.len() + 1,
over: self.over,
pin,
};
section(self.awake, &|pos| ids[pos], &body, geometry);
}
pub(crate) fn pin_section(&mut self, set: &crate::pin::CpuMask) {
self.reborrow().run_with_pin(
|p| {
let _ = p.barrier();
if p.index() > 0 {
crate::pin::pin_to(set, p.index());
}
},
true,
);
}
pub fn split<F>(self, sizes: &[usize], f: F) -> Result<(), Error>
where
F: for<'c> Fn(usize, TeamExec<'c>) + Sync,
{
let total = self.ids.len() + 1;
let sum = sizes.iter().try_fold(0usize, |a, &s| a.checked_add(s));
if sizes.is_empty() || sizes.contains(&0) || sum.is_none_or(|s| s > total) {
return Err(Error::InvalidSplit);
}
self.split_plan(&Plan::Sizes(sizes), f);
Ok(())
}
pub fn fork<F>(self, children: usize, f: F)
where
F: for<'c> Fn(usize, TeamExec<'c>) + Sync,
{
let size = self.ids.len() + 1;
let children = children.min(size);
if children == 0 {
return;
}
self.split_plan(&Plan::Even { size, children }, f);
}
fn split_plan<F>(self, plan: &Plan<'_>, f: F)
where
F: for<'c> Fn(usize, TeamExec<'c>) + Sync,
{
let track = &self.track;
let _released = track.release();
let (awake, ids, policy, over) = (self.awake, self.ids, self.policy, self.over);
split_raw(awake, ids, plan, over, |c, child_ids| {
let _live = track.task();
let child = TeamExec {
awake,
ids: child_ids,
policy,
track: track.child(),
over,
};
f(c, child);
});
}
pub(crate) fn acknowledge(&mut self) {
self.reborrow().run_with(|p| {
let _ = p.barrier();
});
}
}
pub(crate) fn section<B, S>(awake: Option<&Awake>, slot_of: &S, body: &B, geometry: Geometry)
where
B: Fn(&Participant<'_>) + Sync,
S: Fn(usize) -> usize + Sync,
{
unsafe fn slot_call<S: Fn(usize) -> usize>(data: *const (), pos: usize) -> usize {
(*data.cast::<S>())(pos)
}
unsafe fn call<B: Fn(&Participant<'_>)>(data: *const (), sec: &Section, index: usize) {
let body = &*data.cast::<B>();
body(&Participant {
index,
section: sec,
});
}
let sec = Section::new(
call::<B>,
(body as *const B).cast(),
slot_call::<S>,
(slot_of as *const S).cast(),
geometry,
);
let dispatched = geometry.dispatched;
let guard = JoinGuard(&sec);
if dispatched > 0 {
if let Some(awake) = awake {
awake.publish(&sec);
}
}
let own = catch_unwind(AssertUnwindSafe(|| unsafe { sec.call_here(0) }));
if let Err(payload) = own {
sec.fail(payload);
}
drop(guard); if let Some(payload) = sec.take_panic() {
resume_unwind(payload);
}
}
pub(crate) fn split_raw<F>(awake: Option<&Awake>, ids: &[usize], plan: &Plan<'_>, over: bool, f: F)
where
F: Fn(usize, &[usize]) + Sync,
{
let body = |p: &Participant<'_>| {
let c = p.index();
let (start, size) = plan.child(c);
f(c, &ids[start..start + size - 1]);
};
let n = plan.children();
let geometry = Geometry {
dispatched: n - 1,
size: ids.len() + 1,
over,
pin: false,
};
section(
awake,
&|pos| ids[plan.child(pos + 1).0 - 1],
&body,
geometry,
);
}
pub(crate) fn spread_raw<F>(
awake: Option<&Awake>,
ids: &[usize],
participants: usize,
over: bool,
f: F,
) where
F: Fn(usize) + Sync,
{
let body = |p: &Participant<'_>| f(p.index());
let geometry = Geometry {
dispatched: participants - 1,
size: participants,
over,
pin: false,
};
section(awake, &|pos| ids[pos], &body, geometry);
}
impl fmt::Debug for TeamExec<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TeamExec")
.field("size", &self.size())
.field("policy", &self.policy)
.finish()
}
}