use crate::error::{Error, Refused};
use crate::pool::Pool;
use crate::sched;
use crate::split;
use crate::track::Track;
use core::fmt;
use core::num::NonZeroUsize;
use core::ops::Range;
use core::sync::atomic::{AtomicUsize, Ordering};
#[must_use = "an Exec is a budget of execution slots; pass it to a kernel"]
pub struct Exec<'p> {
pool: Option<&'p Pool>,
budget: NonZeroUsize,
child: bool,
track: Track,
}
impl Exec<'static> {
pub const fn serial() -> Exec<'static> {
Exec {
pool: None,
budget: NonZeroUsize::MIN,
child: false,
track: Track::NONE,
}
}
}
impl<'p> Exec<'p> {
pub fn new(pool: &'p Pool, budget: usize) -> Result<Exec<'p>, Error> {
let budget = NonZeroUsize::new(budget).ok_or(Error::ZeroBudget)?;
Ok(Exec {
pool: Some(pool),
budget: budget.min(pool.width()),
child: false,
track: Track::root(),
})
}
pub fn budget(&self) -> NonZeroUsize {
self.budget
}
pub fn is_serial(&self) -> bool {
self.budget.get() == 1
}
pub fn limit(self, max: NonZeroUsize) -> Exec<'p> {
Exec {
budget: self.budget.min(max),
..self
}
}
pub fn reborrow(&mut self) -> Exec<'_> {
Exec {
pool: self.pool,
budget: self.budget,
child: self.child,
track: self.track.alias(),
}
}
pub fn fork<F>(self, jobs: usize, f: F)
where
F: Fn(usize, Exec<'_>) + Sync,
{
if jobs == 0 {
return;
}
let budget = self.budget.get();
let tasks = jobs.min(budget);
let _released = self.track.release();
let next = AtomicUsize::new(0);
let this = &self;
let body = |k: usize| {
let _live = this.track.task();
let share = split::share(budget, tasks, k);
let child = || Exec {
pool: this.pool,
budget: NonZeroUsize::new(share).unwrap_or(NonZeroUsize::MIN),
child: true,
track: this.track.child(),
};
if jobs <= tasks {
f(k, child());
} else {
loop {
let job = next.fetch_add(1, Ordering::Relaxed);
if job >= jobs {
break;
}
f(job, child());
}
}
};
match self.pool {
Some(pool) if tasks > 1 => sched::run_tasks(pool, tasks, &self.track, body),
_ => {
for k in 0..tasks {
body(k);
}
}
}
}
pub fn for_ranges<F>(self, len: usize, grain: NonZeroUsize, f: F)
where
F: Fn(Range<usize>) + Sync,
{
if len == 0 {
return;
}
let (chunk, tasks) = split::range_plan(len, grain.get(), self.budget.get());
let _released = self.track.release();
let track = &self.track;
let body = |k: usize| {
let _live = track.task();
f(split::chunk_range(len, chunk, k));
};
match self.pool {
Some(pool) if tasks > 1 => sched::run_tasks(pool, tasks, track, body),
_ => {
for k in 0..tasks {
body(k);
}
}
}
}
pub fn broadcast<F>(self, f: F) -> Result<(), Refused>
where
F: Fn(usize) + Sync,
{
if let Some(refused) = self.refusal() {
return Err(refused);
}
let pool = match self.pool {
Some(pool) if self.budget.get() > 1 => pool,
_ => {
let _live = self.track.task();
f(0);
return Ok(());
}
};
let _released = self.track.release();
#[cfg(feature = "parallel")]
sched::broadcast(pool, &self.track, self.budget.get(), f);
#[cfg(not(feature = "parallel"))]
{
let _ = pool;
f(0);
}
Ok(())
}
pub fn can_broadcast(&self) -> bool {
self.refusal().is_none()
}
fn refusal(&self) -> Option<Refused> {
let pool = match self.pool {
Some(pool) if self.budget.get() > 1 => pool,
_ => return None,
};
if self.child {
return Some(Refused::ForkChild);
}
if sched::in_pool(pool) {
return Some(Refused::CallerInPool);
}
None
}
pub fn external<R: Send>(self, f: impl FnOnce(NonZeroUsize) -> R + Send) -> R {
#[cfg(feature = "parallel")]
if let Some(pool) = self.pool {
if self.budget.get() > 1 {
let width = self.budget;
let _released = self.track.release();
return sched::enter(pool, &self.track, move || f(width));
}
}
f(NonZeroUsize::MIN)
}
#[cfg(feature = "ledger")]
pub fn ledger_root(&self) -> Option<u64> {
self.track.root_id()
}
}
impl fmt::Debug for Exec<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Exec")
.field("budget", &self.budget)
.field("pooled", &self.pool.is_some())
.field("child", &self.child)
.finish()
}
}