#![allow(clippy::type_complexity)]
#![allow(clippy::return_self_not_must_use)]
use std::{
any::Any,
cell,
collections::{HashMap, VecDeque},
rc,
sync::{
self,
atomic::{self, AtomicBool, AtomicUsize, Ordering},
Arc,
},
time,
};
use crate::{
synca::{mpp, DurationWaker, Entry, EntryList, IdleMan, OnSignal, Sleepers, Waiter},
valtron::{
AnyResult, Ctx, EventReadinessPtr, ExecutionEngine, ExecutionIterator, State,
TaskIterator, TaskStatus,
},
};
use crate::{
synca::{Timeable, Timing},
valtron::{DualSequeunceChildAndParentLinkedTask, FinishChildBeforeParentTask},
};
use foundation_compact::rng::ChaCha8Rng;
use foundation_compact::rng::SeedableRng;
use concurrent_queue::{ConcurrentQueue, PopError, PushError};
#[allow(unused)]
use crate::compati::Mutex;
use crate::valtron::{WakeCallback, WakeRegistry, WakeResult, WakeableStream};
use crate::valtron::{
BoxedExecutionEngine, BoxedExecutionIterator, ExecutorError, GlobalTask,
ProcessController, SharedTaskQueue, SpawnInfo, SpawnType, TaskReadyResolver, TaskSpawnConfig,
};
use crate::valtron::executors::constants::{
DEFAULT_KILL_SIGNAL_CHECK_INTERVAL, DEFAULT_NOTIFY_QUEUE_MAX_SPINS, DEFAULT_READINESS_WAIT,
};
pub struct WrapTask<I, T>
where
I: Iterator<Item = T>,
{
iter: I,
}
impl<I, T> WrapTask<I, T>
where
I: Iterator<Item = T>,
{
pub fn new(iter: I) -> Self {
Self { iter }
}
}
impl<I, T> TaskIterator for WrapTask<I, T>
where
I: Iterator<Item = T>,
{
type Pending = ();
type Ready = T;
fn next_status(&mut self, _ctx: Ctx) -> Option<TaskStatus<Self::Ready, Self::Pending>> {
self.iter.next().map(TaskStatus::Ready)
}
}
const MAX_YIELD_DURATION: time::Duration = time::Duration::from_millis(100);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ThreadId(Entry, String);
impl ThreadId {
#[must_use]
pub fn new(entry: Entry, name: String) -> Self {
Self(entry, name)
}
pub fn get_mut(&mut self) -> &mut Entry {
&mut self.0
}
#[must_use]
pub fn get_ref(&self) -> &Entry {
&self.0
}
#[must_use]
pub fn get_cloned(&self) -> Entry {
self.0
}
#[must_use]
pub fn get_name(&self) -> &String {
&self.1
}
}
pub enum ThreadActivity {
Started(ThreadId),
Stopped(ThreadId),
Blocked(ThreadId),
Unblocked(ThreadId),
Parked(ThreadId),
Unparked(ThreadId),
Panicked(ThreadId, Box<dyn Any + Send>),
BroadcastedTask,
}
impl core::fmt::Display for ThreadActivity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThreadActivity::Panicked(id, _) => write!(f, "ThreadActivity::Panicked({id:?})"),
ThreadActivity::Started(id) => write!(f, "ThreadActivity::Started({id:?})"),
ThreadActivity::Stopped(id) => write!(f, "ThreadActivity::Stopped({id:?})"),
ThreadActivity::Blocked(id) => write!(f, "ThreadActivity::Blocked({id:?})"),
ThreadActivity::Unblocked(id) => write!(f, "ThreadActivity::Unblocked({id:?})"),
ThreadActivity::Parked(id) => write!(f, "ThreadActivity::Parked({id:?})"),
ThreadActivity::Unparked(id) => write!(f, "ThreadActivity::Unparked({id:?})"),
ThreadActivity::BroadcastedTask => write!(f, "ThreadActivity::BroadcastedTask"),
}
}
}
impl core::fmt::Debug for ThreadActivity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThreadActivity::Panicked(id, _) => write!(f, "ThreadActivity::Panicked({id:?})"),
ThreadActivity::Started(id) => write!(f, "ThreadActivity::Started({id:?})"),
ThreadActivity::Stopped(id) => write!(f, "ThreadActivity::Stopped({id:?})"),
ThreadActivity::Blocked(id) => write!(f, "ThreadActivity::Blocked({id:?})"),
ThreadActivity::Unblocked(id) => write!(f, "ThreadActivity::Unblocked({id:?})"),
ThreadActivity::Parked(id) => write!(f, "ThreadActivity::Parked({id:?})"),
ThreadActivity::Unparked(id) => write!(f, "ThreadActivity::Unparked({id:?})"),
ThreadActivity::BroadcastedTask => write!(f, "ThreadActivity::BroadcastedTask"),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PriorityOrder {
Top,
Bottom,
}
pub enum Sleepable {
Timable(DurationWaker<Entry>),
Atomic(sync::Arc<AtomicBool>, Entry),
Readiness(EventReadinessPtr, Entry),
}
impl core::fmt::Debug for Sleepable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timable(inner) => f.debug_tuple("Timable").field(inner).finish(),
Self::Atomic(_, entry) => f.debug_tuple("Atomic").field(entry).finish(),
Self::Readiness(_, entry) => f.debug_tuple("Readiness").field(entry).finish(),
}
}
}
impl Sleepable {
pub fn deadline(&self) -> Option<time::Instant> {
match self {
Sleepable::Timable(inner) => Some(inner.deadline()),
Sleepable::Atomic(_, _) | Sleepable::Readiness(_, _) => None,
}
}
}
impl Timeable for Sleepable {
fn remaining_duration(&self) -> Option<time::Duration> {
match self {
Sleepable::Timable(inner) => inner.remaining(),
Sleepable::Atomic(_, _) | Sleepable::Readiness(_, _) => None,
}
}
}
impl Waiter for Sleepable {
fn is_ready(&self, dur: Option<time::Duration>) -> bool {
match self {
Sleepable::Timable(inner) => inner.is_ready(dur),
Sleepable::Atomic(inner, _) => inner.load(atomic::Ordering::SeqCst),
Sleepable::Readiness(signal, _) => signal.is_ready(dur),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NotificationItem<T> {
Ready(T),
None,
}
#[derive(Debug)]
pub struct NotifyQueue<T> {
queue: Arc<ConcurrentQueue<T>>,
condvar: Arc<foundation_nostd::comp::condvar_comp::CondVar>,
mutex: Arc<foundation_nostd::comp::condvar_comp::Mutex<bool>>,
max_spins: AtomicUsize,
wake: Arc<WakeRegistry>,
}
impl<T> Clone for NotifyQueue<T> {
fn clone(&self) -> Self {
Self {
queue: self.queue.clone(),
condvar: self.condvar.clone(),
mutex: self.mutex.clone(),
max_spins: AtomicUsize::new(self.max_spins.load(Ordering::Relaxed)),
wake: self.wake.clone(),
}
}
}
impl<T> NotifyQueue<T> {
pub fn unbounded() -> Self {
Self {
queue: Arc::new(ConcurrentQueue::unbounded()),
condvar: Arc::new(foundation_nostd::comp::condvar_comp::CondVar::new()),
mutex: Arc::new(foundation_nostd::comp::condvar_comp::Mutex::new(false)),
max_spins: AtomicUsize::new(DEFAULT_NOTIFY_QUEUE_MAX_SPINS),
wake: Arc::new(WakeRegistry::new()),
}
}
pub fn bounded(capacity: usize) -> Self {
Self {
queue: Arc::new(ConcurrentQueue::bounded(capacity)),
condvar: Arc::new(foundation_nostd::comp::condvar_comp::CondVar::new()),
mutex: Arc::new(foundation_nostd::comp::condvar_comp::Mutex::new(false)),
max_spins: AtomicUsize::new(DEFAULT_NOTIFY_QUEUE_MAX_SPINS),
wake: Arc::new(WakeRegistry::new()),
}
}
pub fn set_max_spins(&self, max_spins: usize) {
self.max_spins.store(max_spins, Ordering::Relaxed);
}
pub fn push(&self, item: T) -> Result<(), PushError<T>> {
self.queue.push(item)?;
self.wake.fire();
let mut guard = self.mutex.lock().unwrap();
*guard = true;
self.condvar.notify_one();
Ok(())
}
pub fn pop(&self) -> Result<T, PopError> {
self.queue.pop()
}
pub fn wait_for_item(&self, timeout: time::Duration) -> Option<NotificationItem<T>> {
match self.queue.pop() {
Ok(item) => return Some(NotificationItem::Ready(item)),
Err(PopError::Closed) => return None,
Err(PopError::Empty) => {}
}
let max_spins = self.max_spins.load(Ordering::Relaxed);
let mut spins = 0;
let mut guard = self.mutex.lock().unwrap();
loop {
if spins >= max_spins {
*guard = false;
return Some(NotificationItem::None);
}
match self.queue.pop() {
Ok(item) => {
*guard = false;
return Some(NotificationItem::Ready(item));
}
Err(PopError::Closed) => {
*guard = false;
return None;
}
Err(PopError::Empty) => {}
}
let result = self.condvar.wait_timeout(guard, timeout).unwrap();
guard = result.0;
spins += 1;
match self.queue.pop() {
Ok(item) => {
*guard = false;
return Some(NotificationItem::Ready(item));
}
Err(PopError::Closed) => {
*guard = false;
return None;
}
Err(PopError::Empty) => {}
}
*guard = false;
}
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn is_closed(&self) -> bool {
self.queue.is_closed()
}
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn close(&self) -> bool {
let closed = self.queue.close();
if closed {
self.wake.fire();
}
closed
}
pub fn register_wake(&self, cb: WakeCallback) {
self.wake.register(cb);
if !self.queue.is_empty() || self.queue.is_closed() {
self.wake.fire();
}
}
pub fn queue(&self) -> &ConcurrentQueue<T> {
&self.queue
}
pub fn queue_arc(&self) -> Arc<ConcurrentQueue<T>> {
self.queue.clone()
}
}
#[derive(Debug)]
pub struct NotifyRecvIter<T> {
queue: Arc<NotifyQueue<T>>,
}
impl<T> Clone for NotifyRecvIter<T> {
fn clone(&self) -> Self {
Self {
queue: self.queue.clone(),
}
}
}
impl<T> NotifyRecvIter<T> {
pub fn new(queue: Arc<NotifyQueue<T>>) -> Self {
Self { queue }
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.queue.is_closed()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn block_recv(&self, timeout: time::Duration) -> Option<NotificationItem<T>> {
self.queue.wait_for_item(timeout)
}
}
#[derive(Debug)]
pub struct NotifyRecvIterator<T>(NotifyRecvIter<T>, time::Duration);
impl<T> NotifyRecvIterator<T> {
pub fn from_notify_queue(item: Arc<NotifyQueue<T>>, dur: time::Duration) -> Self {
Self::new(NotifyRecvIter::new(item), dur)
}
#[must_use]
pub fn new(item: NotifyRecvIter<T>, dur: time::Duration) -> Self {
Self(item, dur)
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.0.is_closed()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
}
impl<T> Iterator for NotifyRecvIterator<T> {
type Item = NotificationItem<T>;
fn next(&mut self) -> Option<Self::Item> {
match self.0.block_recv(self.1) {
Some(NotificationItem::Ready(value)) => Some(NotificationItem::Ready(value)),
Some(NotificationItem::None) => Some(NotificationItem::None),
None => None, }
}
}
use crate::valtron::iterators::Stream;
#[derive(Debug)]
pub struct NotifyQueueStreamIterator<D, P> {
chan: Arc<NotifyQueue<Stream<D, P>>>,
max_turns: usize,
park_duration: time::Duration,
}
impl<D, P> NotifyQueueStreamIterator<D, P> {
pub fn new(
chan: Arc<NotifyQueue<Stream<D, P>>>,
max_turns: usize,
park_duration: time::Duration,
) -> Self {
assert!(max_turns > 0, "max_turns must be greater than 0");
Self {
chan,
max_turns,
park_duration,
}
}
#[must_use]
pub fn chan(&self) -> &Arc<NotifyQueue<Stream<D, P>>> {
&self.chan
}
#[must_use]
pub fn max_turns(&self) -> usize {
self.max_turns
}
#[must_use]
pub fn park_duration(&self) -> time::Duration {
self.park_duration
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.chan.is_closed()
}
#[must_use]
pub fn len(&self) -> usize {
self.chan.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.chan.is_empty()
}
}
impl<D, P> Iterator for NotifyQueueStreamIterator<D, P> {
type Item = Stream<D, P>;
fn next(&mut self) -> Option<Self::Item> {
match self.chan.pop() {
Ok(value) => {
return Some(value);
}
Err(PopError::Closed) => {
return None;
}
Err(PopError::Empty) => {}
}
match self.chan.wait_for_item(self.park_duration) {
Some(NotificationItem::Ready(value)) => Some(value),
Some(NotificationItem::None) => {
Some(Stream::Wait)
}
None => {
None
}
}
}
}
#[cfg(feature = "multi")]
impl<D, P> WakeableStream for NotifyQueueStreamIterator<D, P>
where
D: Send + 'static,
P: Send + 'static,
{
fn wake(&mut self, cb: WakeCallback) -> WakeResult {
self.chan.register_wake(cb);
WakeResult::Successful
}
}
#[cfg(not(feature = "multi"))]
impl<D, P> WakeableStream for NotifyQueueStreamIterator<D, P>
where
D: 'static,
P: 'static,
{
fn wake(&mut self, cb: WakeCallback) -> WakeResult {
self.chan.register_wake(cb);
WakeResult::Successful
}
}
pub struct ExecutorState {
pub state_owner: String,
pub wakeup_priority: PriorityOrder,
pub global_tasks: SharedTaskQueue,
pub current_task: rc::Rc<cell::RefCell<Option<Entry>>>,
pub local_tasks: rc::Rc<cell::RefCell<EntryList<BoxedExecutionIterator>>>,
pub task_graph: rc::Rc<cell::RefCell<HashMap<Entry, Entry>>>,
pub packed_tasks: rc::Rc<cell::RefCell<HashMap<Entry, bool>>>,
pub processing: rc::Rc<cell::RefCell<VecDeque<Entry>>>,
pub rng: rc::Rc<cell::RefCell<ChaCha8Rng>>,
pub sleepers: Sleepers<Sleepable>,
pub idler: rc::Rc<cell::RefCell<IdleMan>>,
pub depends_true_violations: rc::Rc<cell::RefCell<HashMap<Entry, u32>>>,
pub root: Ctx,
pub task_ctx: rc::Rc<cell::RefCell<HashMap<Entry, Ctx>>>,
}
static DEQUEUE_CAPACITY: usize = 10;
const READINESS_POLL_QUANTUM: time::Duration = time::Duration::from_millis(1);
const DEPENDS_TRUE_PANIC_THRESHOLD: u32 = 4096;
impl ExecutorState {
pub fn new(
state_owner: String,
global_tasks: SharedTaskQueue,
wakeup_priority: PriorityOrder,
rng: ChaCha8Rng,
idler: IdleMan,
) -> Self {
Self {
state_owner,
wakeup_priority,
global_tasks,
rng: rc::Rc::new(cell::RefCell::new(rng)),
idler: rc::Rc::new(cell::RefCell::new(idler)),
current_task: rc::Rc::new(cell::RefCell::new(None)),
task_graph: rc::Rc::new(cell::RefCell::new(HashMap::new())),
packed_tasks: rc::Rc::new(cell::RefCell::new(HashMap::new())),
local_tasks: rc::Rc::new(cell::RefCell::new(EntryList::new())),
sleepers: Sleepers::with_max_readiness_wait(DEFAULT_READINESS_WAIT),
processing: rc::Rc::new(cell::RefCell::new(VecDeque::with_capacity(
DEQUEUE_CAPACITY,
))),
depends_true_violations: rc::Rc::new(cell::RefCell::new(HashMap::new())),
root: Ctx::new(),
task_ctx: rc::Rc::new(cell::RefCell::new(HashMap::new())),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ScheduleOutcome {
GlobalTaskAcquired,
NoTaskRunningOrAcquired,
LocalTaskRunning,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProgressIndicator {
NoWork,
CanProgress(Option<State>),
SpinWait(time::Duration),
Wait,
}
impl Clone for ExecutorState {
fn clone(&self) -> Self {
Self {
state_owner: self.state_owner.clone(),
rng: self.rng.clone(),
idler: self.idler.clone(),
sleepers: self.sleepers.clone(),
wakeup_priority: self.wakeup_priority.clone(),
current_task: self.current_task.clone(),
global_tasks: self.global_tasks.clone(),
local_tasks: self.local_tasks.clone(),
task_graph: self.task_graph.clone(),
packed_tasks: self.packed_tasks.clone(),
processing: self.processing.clone(),
depends_true_violations: rc::Rc::new(cell::RefCell::new(HashMap::new())),
root: self.root.clone(),
task_ctx: rc::Rc::new(cell::RefCell::new(HashMap::new())),
}
}
}
impl core::fmt::Debug for ExecutorState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExecutorState")
.field("priority", &self.wakeup_priority)
.field("global_tasks", &self.global_tasks)
.field("current_task", &self.current_task)
.field("local_tasks", &self.local_tasks.borrow().allocated_slots())
.field("task_graph", &self.task_graph)
.field("packed_tasks", &self.packed_tasks)
.field("processing", &self.processing)
.field("rng", &self.rng)
.field("sleepers", &self.sleepers)
.field("idler", &self.idler.borrow().count())
.finish()
}
}
impl ExecutorState {
#[must_use]
pub fn get_rng(&self) -> rc::Rc<cell::RefCell<ChaCha8Rng>> {
self.rng.clone()
}
fn number_of_local_tasks(&self) -> usize {
self.local_tasks.borrow().active_slots()
}
fn number_of_inprocess(&self) -> usize {
self.processing.borrow().len()
}
#[inline]
#[must_use]
pub fn get_task_dependents(&self, target: Entry) -> Vec<Entry> {
let mut deps = Vec::new();
let mut previous = Some(target);
while previous.is_some() {
let key = previous.unwrap();
match self.task_graph.borrow().get(&key) {
Some(dep) => {
deps.push(*dep);
previous = Some(*dep);
}
None => break,
}
}
deps
}
#[inline]
#[must_use]
pub fn is_packed(&self, target: &Entry) -> bool {
*self.packed_tasks.borrow().get(target).unwrap_or(&false)
}
#[inline]
pub fn wake_up(&self, target: Entry) {
if !self.local_tasks.borrow_mut().has(&target) {
tracing::debug!(
"[SLEEPER] task {:?} no longer in local_tasks, skipping wake_up",
target
);
return;
}
self.packed_tasks.borrow_mut().remove(&target);
match self.wakeup_priority {
PriorityOrder::Top => {
self.processing.borrow_mut().push_front(target);
}
PriorityOrder::Bottom => {
self.processing.borrow_mut().push_back(target);
}
}
}
#[inline]
pub fn wakeup_ready_sleepers(&self) {
for matured in self.sleepers.get_matured() {
match matured {
Sleepable::Timable(wakeable) => self.wake_up(wakeable.handle),
Sleepable::Atomic(_, entry) => self.wake_up(entry),
Sleepable::Readiness(_, entry) => self.wake_up(entry),
}
}
}
#[inline]
#[must_use]
pub fn number_of_sleepers(&self) -> usize {
self.sleepers.count()
}
#[inline]
#[must_use]
pub fn number_of_depends_true_violations(&self) -> usize {
self.depends_true_violations.borrow().len()
}
#[inline]
#[must_use]
pub fn has_sleeping_tasks(&self) -> bool {
self.sleepers.has_pending_tasks()
}
#[inline]
#[must_use]
pub fn time_until_next_wakeup(&self) -> Option<time::Duration> {
self.sleepers.time_until_next()
}
#[inline]
#[must_use]
pub fn has_local_tasks(&self) -> bool {
self.local_tasks.borrow().active_slots() > 0
}
#[must_use]
pub fn has_active_tasks(&self) -> bool {
self.total_active_tasks() > 0
}
#[must_use]
pub fn has_inflight_task(&self) -> bool {
!self.processing.borrow().is_empty()
}
#[must_use]
pub fn has_incoming_global_tasks(&self) -> bool {
!self.global_tasks.is_empty()
}
#[inline]
#[must_use]
pub fn has_runnable_work(&self) -> bool {
self.has_active_tasks() || self.has_inflight_task() || self.has_incoming_global_tasks()
}
#[must_use]
pub fn total_inprocess_tasks(&self) -> usize {
self.number_of_inprocess()
}
#[must_use]
pub fn total_sleeping_tasks(&self) -> usize {
self.sleepers.count()
}
#[must_use]
pub fn total_tasks(&self) -> usize {
self.local_tasks.borrow().active_slots()
}
pub fn total_active_tasks(&self) -> usize {
let local_task_count = self.local_tasks.borrow().active_slots();
let sleeping_task_count = self.sleepers.count();
local_task_count.saturating_sub(sleeping_task_count)
}
#[inline]
pub fn remove_sleepers_for_entry(&self, entry: &Entry) {
let _ = self.sleepers.remove(entry);
}
#[inline]
fn insert_local_task(&self, task: BoxedExecutionIterator) -> Entry {
let entry = self.local_tasks.borrow_mut().insert(task);
self.task_ctx.borrow_mut().insert(entry, self.root.fork());
entry
}
#[inline]
fn remove_task_ctx(&self, entry: &Entry) {
self.task_ctx.borrow_mut().remove(entry);
}
#[inline]
pub fn schedule_next(&self) -> ScheduleOutcome {
if self.local_tasks.borrow().active_slots() > 0 && !self.processing.borrow().is_empty() {
return ScheduleOutcome::LocalTaskRunning;
}
match self.global_tasks.pop() {
Ok(task) => {
let task_entry = self.insert_local_task(task);
self.processing.borrow_mut().push_front(task_entry);
ScheduleOutcome::GlobalTaskAcquired
}
Err(_) => ScheduleOutcome::NoTaskRunningOrAcquired,
}
}
#[inline]
pub fn request_global_task(&self) -> ProgressIndicator {
if self.has_active_tasks() {
return ProgressIndicator::CanProgress(None);
}
match self.schedule_next() {
ScheduleOutcome::GlobalTaskAcquired => ProgressIndicator::CanProgress(None),
ScheduleOutcome::NoTaskRunningOrAcquired => {
if self.has_sleeping_tasks() {
return ProgressIndicator::CanProgress(None);
}
ProgressIndicator::NoWork
}
ScheduleOutcome::LocalTaskRunning => {
unreachable!("No local task should be running at this point")
}
}
}
#[inline]
pub fn schedule_and_do_work(&self, engine: BoxedExecutionEngine) -> ProgressIndicator {
match self.request_global_task() {
ProgressIndicator::CanProgress(_) => {}
ProgressIndicator::NoWork => {
return ProgressIndicator::NoWork;
}
ProgressIndicator::SpinWait(_) => {
unreachable!("Requesting global task should never spin wait")
}
ProgressIndicator::Wait => {
return ProgressIndicator::Wait;
}
}
self.wakeup_ready_sleepers();
match self.do_work(engine) {
ProgressIndicator::CanProgress(state) => ProgressIndicator::CanProgress(state),
ProgressIndicator::NoWork => {
if self.has_sleeping_tasks() {
if let Some(min_sleep_dur) = self.sleepers.min_duration() {
return ProgressIndicator::SpinWait(min_sleep_dur);
}
return ProgressIndicator::CanProgress(None);
}
self.current_task.borrow_mut().take();
match self.idler.borrow_mut().increment() {
Some(next_dur) => ProgressIndicator::SpinWait(next_dur),
None => ProgressIndicator::NoWork,
}
}
ProgressIndicator::SpinWait(duration) => {
if self.has_inflight_task() {
return ProgressIndicator::CanProgress(None);
}
if self.has_incoming_global_tasks() {
return ProgressIndicator::CanProgress(None);
}
self.current_task.borrow_mut().take();
match self.schedule_next() {
ScheduleOutcome::GlobalTaskAcquired => ProgressIndicator::CanProgress(None),
ScheduleOutcome::NoTaskRunningOrAcquired => {
ProgressIndicator::SpinWait(duration)
}
ScheduleOutcome::LocalTaskRunning => {
unreachable!("global task never spinWaits")
}
}
}
ProgressIndicator::Wait => ProgressIndicator::Wait,
}
}
pub fn check_processing_queue(&self) -> Option<ProgressIndicator> {
let has_sleeping_tasks = self.has_sleeping_tasks();
let handle = self.processing.borrow_mut();
if handle.is_empty() {
if has_sleeping_tasks {
return Some(ProgressIndicator::CanProgress(None));
}
return Some(ProgressIndicator::NoWork);
}
None
}
#[inline]
pub fn do_work(&self, engine: BoxedExecutionEngine) -> ProgressIndicator {
if let Some(inner) = self.check_processing_queue() {
match inner {
ProgressIndicator::NoWork => {
return ProgressIndicator::NoWork;
}
ProgressIndicator::CanProgress(inner) => {
return ProgressIndicator::CanProgress(inner);
}
ProgressIndicator::SpinWait(_) => {
unreachable!("check_processing_queue should never reach here")
}
ProgressIndicator::Wait => {
unreachable!("check_processing_queue should never return Wait")
}
}
}
let top_entry = self.processing.borrow_mut().pop_front().unwrap();
let remaining_tasks = self.processing.borrow().len();
if self.is_packed(&top_entry) {
return ProgressIndicator::CanProgress(None);
}
self.current_task.borrow_mut().replace(top_entry);
let iter_container = self.local_tasks.borrow_mut().park(&top_entry);
assert!(
iter_container.is_some(),
"An entry must always have a task attached"
);
let mut iter = iter_container.unwrap();
let ctx = self
.task_ctx
.borrow()
.get(&top_entry)
.cloned()
.unwrap_or_else(|| self.root.fork());
let next_result = iter.next(top_entry, engine, ctx);
if next_result.is_none() {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.remove_sleepers_for_entry(&top_entry);
self.local_tasks.borrow_mut().take(&top_entry);
self.remove_task_ctx(&top_entry);
if remaining_tasks == 0 {
return ProgressIndicator::NoWork;
}
return ProgressIndicator::CanProgress(None);
}
if !matches!(next_result.as_ref().unwrap(), State::Depends(_)) {
self.depends_true_violations.borrow_mut().remove(&top_entry);
}
match next_result.unwrap() {
State::SpawnFailed(parent_key) => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.remove_sleepers_for_entry(&top_entry);
self.local_tasks.borrow_mut().take(&top_entry);
self.remove_task_ctx(&top_entry);
if remaining_tasks == 0 {
ProgressIndicator::NoWork
} else {
ProgressIndicator::CanProgress(Some(State::SpawnFailed(parent_key)))
}
}
State::Panicked => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.remove_sleepers_for_entry(&top_entry);
self.local_tasks.borrow_mut().take(&top_entry);
self.remove_task_ctx(&top_entry);
if remaining_tasks == 0 {
ProgressIndicator::NoWork
} else {
ProgressIndicator::CanProgress(Some(State::Panicked))
}
}
State::SpawnFinished(info) => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
if !info.has_parent_and_child() {
return match info.spawn_type() {
SpawnType::Lifted => {
let child_entry = info.parent().unwrap();
let parent_task = self
.local_tasks
.borrow_mut()
.take(&top_entry)
.expect("get parent task");
let child_task = self
.local_tasks
.borrow_mut()
.take(&child_entry)
.expect("get child task");
self.remove_task_ctx(&top_entry);
self.remove_task_ctx(&child_entry);
let task =
FinishChildBeforeParentTask::new(info, parent_task, child_task);
let new_task_id = self.insert_local_task(Box::new(task));
self.processing.borrow_mut().push_front(new_task_id);
ProgressIndicator::CanProgress(Some(State::SpawnFinished(
SpawnInfo::new(SpawnType::Lifted, Some(new_task_id), None),
)))
}
SpawnType::Broadcasted | SpawnType::None | SpawnType::Scheduled => {
tracing::trace!("spawned process without parent, requeueing: {:?} - id: {:?}", info, top_entry);
self.processing.borrow_mut().push_front(top_entry);
ProgressIndicator::CanProgress(Some(State::SpawnFinished(info)))
}
SpawnType::LiftedWithParent | SpawnType::Sequenced => {
unreachable!("This state must always come with a parent");
}
};
}
match info.spawn_type() {
SpawnType::LiftedWithParent => {
assert!(
top_entry == info.parent().unwrap(),
"Parent entry id must be the same as top entry"
);
let parent_entry = info.parent().unwrap();
let child_entry = info.child().unwrap();
let parent_task = self
.local_tasks
.borrow_mut()
.take(&parent_entry)
.expect("get parent task");
let child_task = self
.local_tasks
.borrow_mut()
.take(&child_entry)
.expect("get child task");
self.remove_task_ctx(&parent_entry);
self.remove_task_ctx(&child_entry);
let task = FinishChildBeforeParentTask::new(info, parent_task, child_task);
let new_task_id = self.insert_local_task(Box::new(task));
self.processing.borrow_mut().push_front(new_task_id);
ProgressIndicator::CanProgress(Some(State::SpawnFinished(SpawnInfo::new(
SpawnType::Lifted,
Some(new_task_id),
None,
))))
}
SpawnType::Sequenced => {
assert!(
top_entry == info.parent().unwrap(),
"Parent entry id must be the same as top entry"
);
let parent_entry = info.parent().unwrap();
let child_entry = info.child().unwrap();
let parent_task = self
.local_tasks
.borrow_mut()
.take(&parent_entry)
.expect("get parent task");
let child_task = self
.local_tasks
.borrow_mut()
.take(&child_entry)
.expect("get child task");
self.remove_task_ctx(&parent_entry);
self.remove_task_ctx(&child_entry);
let task = DualSequeunceChildAndParentLinkedTask::new(
info,
parent_task,
child_task,
);
let new_task_id = self.insert_local_task(Box::new(task));
self.processing.borrow_mut().push_front(new_task_id);
ProgressIndicator::CanProgress(Some(State::SpawnFinished(SpawnInfo::new(
SpawnType::Lifted,
Some(new_task_id),
None,
))))
}
_ => {
tracing::trace!(
"spawned process without parent, requeueing: {:?} - id: {:?}",
info,
top_entry
);
self.processing.borrow_mut().push_front(top_entry);
ProgressIndicator::CanProgress(Some(State::SpawnFinished(info)))
}
}
}
State::Done => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.remove_sleepers_for_entry(&top_entry);
self.local_tasks.borrow_mut().take(&top_entry);
self.remove_task_ctx(&top_entry);
if remaining_tasks == 0 {
ProgressIndicator::NoWork
} else {
ProgressIndicator::CanProgress(Some(State::Done))
}
}
State::ReadyValue(entry_id) => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.processing.borrow_mut().push_front(top_entry);
ProgressIndicator::CanProgress(Some(State::ReadyValue(entry_id)))
}
State::Progressed => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.processing.borrow_mut().push_front(top_entry);
ProgressIndicator::CanProgress(Some(State::Progressed))
}
State::Pending(duration) => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
let final_state = if let Some(inner) = duration {
self.sleepers.insert(
top_entry,
Sleepable::Timable(DurationWaker::from_now(top_entry, inner)),
);
if !self.processing.borrow().is_empty() {
return ProgressIndicator::CanProgress(Some(State::Pending(duration)));
}
ProgressIndicator::SpinWait(inner)
} else {
self.processing.borrow_mut().push_front(top_entry);
ProgressIndicator::CanProgress(None)
};
final_state
}
State::Reschedule => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.processing.borrow_mut().push_back(top_entry);
ProgressIndicator::CanProgress(Some(State::Reschedule))
}
State::Wait => {
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.processing.borrow_mut().push_front(top_entry);
ProgressIndicator::Wait
}
State::Depends(signal) => {
if self.sleepers.contains(&top_entry) {
panic!(
"Task {:?} returned State::Depends while already registered as a sleeper. \
A sleeping task must be woken before it can send Depends again.",
top_entry
);
}
if signal.is_ready(None) {
self.depends_true_violations
.borrow_mut()
.entry(top_entry)
.and_modify(|c| *c += 1)
.or_insert(1);
let violations = *self
.depends_true_violations
.borrow()
.get(&top_entry)
.unwrap();
if violations >= DEPENDS_TRUE_PANIC_THRESHOLD {
panic!(
"Task {:?} returned State::Depends with an already-ready signal {} \
consecutive times. This is a task bug, not an executor issue.",
top_entry, violations
);
}
if violations <= 3 || violations.is_multiple_of(256) {
tracing::warn!(
"[DEPENDS] Task {:?} returned already-ready signal (violation {}/{})",
top_entry,
violations,
DEPENDS_TRUE_PANIC_THRESHOLD
);
}
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.processing.borrow_mut().push_back(top_entry);
return ProgressIndicator::CanProgress(Some(State::Pending(None)));
}
self.depends_true_violations.borrow_mut().remove(&top_entry);
self.local_tasks.borrow_mut().unpark(&top_entry, iter);
self.sleepers
.insert(top_entry, Sleepable::Readiness(signal, top_entry));
if self.has_incoming_global_tasks() || self.has_inflight_task() {
return ProgressIndicator::CanProgress(None);
}
match self.idler.borrow_mut().increment() {
Some(next_dur) => ProgressIndicator::SpinWait(next_dur),
None => ProgressIndicator::NoWork,
}
}
}
}
}
impl ExecutorState {
#[inline]
pub fn sequenced(
&self,
task: BoxedExecutionIterator,
parent: Entry,
) -> AnyResult<SpawnInfo, ExecutorError> {
match *self.current_task.borrow() {
Some(current_task) => {
if !current_task.eq(&parent) {
return Err(ExecutorError::ParentMustBeExecutingToLift);
}
}
None => unreachable!("Parent task should never truly be empty at this point"),
}
let task_entry = self.insert_local_task(task);
Ok(SpawnInfo::new(
SpawnType::Sequenced,
Some(task_entry),
Some(parent),
))
}
#[inline]
pub fn lift(
&self,
task: BoxedExecutionIterator,
parent: Option<Entry>,
) -> AnyResult<SpawnInfo, ExecutorError> {
if let Some(parent_handle) = &parent {
match *self.current_task.borrow() {
Some(current_task) => {
if !current_task.eq(parent_handle) {
return Err(ExecutorError::ParentMustBeExecutingToLift);
}
}
None => unreachable!("Current task should never truly be empty at this point"),
}
}
let task_entry = self.insert_local_task(task);
Ok(if parent.is_some() {
SpawnInfo::new(SpawnType::LiftedWithParent, Some(task_entry), parent)
} else {
self.processing.borrow_mut().push_front(task_entry);
SpawnInfo::new(SpawnType::Lifted, Some(task_entry), parent)
})
}
#[inline]
pub fn schedule(&self, task: BoxedExecutionIterator) -> AnyResult<SpawnInfo, ExecutorError> {
let task_entry = self.insert_local_task(task);
self.processing.borrow_mut().push_back(task_entry);
Ok(SpawnInfo::new(SpawnType::Scheduled, Some(task_entry), None))
}
}
impl ExecutorState {
pub fn broadcast(&self, task: GlobalTask) -> AnyResult<SpawnInfo, ExecutorError> {
match self.global_tasks.push(task) {
Ok(()) => Ok(SpawnInfo::new(SpawnType::Broadcasted, None, None)),
Err(err) => match err {
PushError::Full(_) => Err(ExecutorError::QueueFull),
PushError::Closed(_) => Err(ExecutorError::QueueClosed),
},
}
}
}
pub struct ReferencedExecutorState {
inner: rc::Rc<ExecutorState>,
activities: Option<mpp::Sender<ThreadActivity>>,
}
impl From<rc::Rc<ExecutorState>> for ReferencedExecutorState {
fn from(value: rc::Rc<ExecutorState>) -> Self {
ReferencedExecutorState {
inner: value,
activities: None,
}
}
}
impl Clone for ReferencedExecutorState {
fn clone(&self) -> Self {
ReferencedExecutorState {
inner: rc::Rc::clone(&self.inner),
activities: self.activities.clone(),
}
}
}
#[allow(unused)]
impl ReferencedExecutorState {
#[must_use]
pub fn new(
inner: rc::Rc<ExecutorState>,
activities: Option<mpp::Sender<ThreadActivity>>,
) -> Self {
Self { inner, activities }
}
#[must_use]
pub fn clone_queue(&self) -> SharedTaskQueue {
self.inner.global_tasks.clone()
}
#[must_use]
pub fn clone_state(&self) -> rc::Rc<ExecutorState> {
self.inner.clone()
}
fn use_activities(&mut self, activities: mpp::Sender<ThreadActivity>) {
self.activities = Some(activities);
}
fn get_ref(&self) -> &rc::Rc<ExecutorState> {
&self.inner
}
fn get_ref_mut(&mut self) -> &mut rc::Rc<ExecutorState> {
&mut self.inner
}
fn number_of_local_tasks(&self) -> usize {
self.inner.number_of_local_tasks()
}
fn number_of_inprocess(&self) -> usize {
self.inner.number_of_inprocess()
}
}
impl ReferencedExecutorState {
#[inline]
#[must_use]
pub fn get_rng(&self) -> rc::Rc<cell::RefCell<ChaCha8Rng>> {
self.inner.get_rng()
}
#[inline]
#[must_use]
pub fn fork_ctx(&self) -> Ctx {
self.inner.root.fork()
}
#[inline]
#[must_use]
pub fn number_of_sleepers(&self) -> usize {
self.inner.number_of_sleepers()
}
#[inline]
#[must_use]
pub fn number_of_depends_true_violations(&self) -> usize {
self.inner.number_of_depends_true_violations()
}
#[inline]
#[must_use]
pub fn time_until_next_wakeup(&self) -> Option<time::Duration> {
self.inner.time_until_next_wakeup()
}
#[inline]
#[must_use]
pub fn total_tasks(&self) -> usize {
self.inner.total_tasks()
}
#[must_use]
pub fn has_inflight_task(&self) -> bool {
self.inner.has_inflight_task()
}
#[inline]
#[must_use]
pub fn has_runnable_work(&self) -> bool {
self.inner.has_runnable_work()
}
#[must_use]
pub fn schedule_and_do_work(&self, engine: BoxedExecutionEngine) -> ProgressIndicator {
self.inner.schedule_and_do_work(engine)
}
}
pub struct LocalExecutionEngine {
inner: rc::Rc<ExecutorState>,
activities: Option<mpp::Sender<ThreadActivity>>,
}
impl Clone for LocalExecutionEngine {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
activities: self.activities.clone(),
}
}
}
impl LocalExecutionEngine {
#[must_use]
pub fn new(
inner: rc::Rc<ExecutorState>,
activities: Option<mpp::Sender<ThreadActivity>>,
) -> Self {
Self { inner, activities }
}
#[allow(unused)]
fn use_activities(&mut self, activities: mpp::Sender<ThreadActivity>) {
self.activities = Some(activities);
}
}
#[allow(clippy::needless_lifetimes)]
impl<'a> ExecutionEngine for Box<&'a LocalExecutionEngine> {
fn lift(
&self,
task: BoxedExecutionIterator,
parent: Option<Entry>,
) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).lift(task, parent)
}
fn sequenced(
&self,
task: BoxedExecutionIterator,
parent: Entry,
) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).sequenced(task, parent)
}
fn schedule(&self, task: BoxedExecutionIterator) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).schedule(task)
}
fn broadcast(&self, task: GlobalTask) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).broadcast(task)
}
fn shared_queue(&self) -> SharedTaskQueue {
(**self).shared_queue()
}
fn boxed_engine(&self) -> BoxedExecutionEngine {
(**self).boxed_engine()
}
fn rng(&self) -> rc::Rc<cell::RefCell<ChaCha8Rng>> {
(**self).rng()
}
}
impl ExecutionEngine for Box<LocalExecutionEngine> {
fn sequenced(
&self,
task: BoxedExecutionIterator,
parent: Entry,
) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).sequenced(task, parent)
}
fn lift(
&self,
task: BoxedExecutionIterator,
parent: Option<Entry>,
) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).lift(task, parent)
}
fn schedule(&self, task: BoxedExecutionIterator) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).schedule(task)
}
fn broadcast(&self, task: GlobalTask) -> AnyResult<SpawnInfo, ExecutorError> {
(**self).broadcast(task)
}
fn shared_queue(&self) -> SharedTaskQueue {
(**self).shared_queue()
}
fn boxed_engine(&self) -> BoxedExecutionEngine {
(**self).boxed_engine()
}
fn rng(&self) -> rc::Rc<cell::RefCell<ChaCha8Rng>> {
(**self).rng()
}
}
impl ExecutionEngine for LocalExecutionEngine {
fn sequenced(
&self,
task: BoxedExecutionIterator,
parent: Entry,
) -> AnyResult<SpawnInfo, ExecutorError> {
let entry = self.inner.sequenced(task, parent)?;
Ok(entry)
}
fn lift(
&self,
task: BoxedExecutionIterator,
parent: Option<Entry>,
) -> AnyResult<SpawnInfo, ExecutorError> {
let entry = self.inner.lift(task, parent)?;
Ok(entry)
}
fn schedule(&self, task: BoxedExecutionIterator) -> AnyResult<SpawnInfo, ExecutorError> {
let entry = self.inner.schedule(task)?;
Ok(entry)
}
fn broadcast(&self, task: GlobalTask) -> AnyResult<SpawnInfo, ExecutorError> {
let info = self.inner.broadcast(task)?;
if let Some(sender) = &self.activities {
if let Err(err) = sender.send(ThreadActivity::BroadcastedTask) {
tracing::error!("Failure in sending thread activity occurred: {:?}", err);
return Err(ExecutorError::FailedToSendThreadActivity);
}
}
Ok(info)
}
fn boxed_engine(&self) -> BoxedExecutionEngine {
Box::new(self.clone())
}
fn shared_queue(&self) -> SharedTaskQueue {
self.inner.global_tasks.clone()
}
fn rng(&self) -> rc::Rc<cell::RefCell<ChaCha8Rng>> {
self.inner.get_rng()
}
}
impl ReferencedExecutorState {
#[inline]
#[must_use]
pub fn local_engine(&self) -> LocalExecutionEngine {
LocalExecutionEngine::new(self.inner.clone(), self.activities.clone())
}
#[inline]
#[must_use]
pub fn boxed_engine(&self) -> BoxedExecutionEngine {
Box::new(self.local_engine())
}
}
pub struct LocalThreadExecutor<T: ProcessController + Clone> {
state_owner: String,
kill_signal: Option<Arc<OnSignal>>,
state: ReferencedExecutorState,
no_work_yield: time::Duration,
max_yield_duration: time::Duration,
yielder: T,
kill_signal_check_interval: usize,
}
impl<T: ProcessController + Clone> Clone for LocalThreadExecutor<T> {
fn clone(&self) -> Self {
LocalThreadExecutor {
state_owner: self.state_owner.clone(),
state: self.state.clone(),
yielder: self.yielder.clone(),
no_work_yield: self.no_work_yield,
max_yield_duration: self.max_yield_duration,
kill_signal: self.kill_signal.clone(),
kill_signal_check_interval: self.kill_signal_check_interval,
}
}
}
#[allow(unused)]
#[allow(clippy::too_many_arguments)]
impl<T: ProcessController + Clone> LocalThreadExecutor<T> {
pub fn new(
state_owner: String,
tasks: SharedTaskQueue,
rng: ChaCha8Rng,
idler: IdleMan,
priority: PriorityOrder,
yielder: T,
no_work_yield: time::Duration,
kill_signal: Option<Arc<OnSignal>>,
activities: Option<mpp::Sender<ThreadActivity>>,
kill_signal_check_interval: usize,
) -> Self {
Self {
state_owner: state_owner.clone(),
yielder,
kill_signal,
no_work_yield,
max_yield_duration: time::Duration::from_millis(100),
kill_signal_check_interval,
state: ReferencedExecutorState::new(
rc::Rc::new(ExecutorState::new(state_owner, tasks, priority, rng, idler)),
activities,
),
}
}
pub fn from_seed(
seed: u64,
state_owner: String,
tasks: SharedTaskQueue,
idler: IdleMan,
priority: PriorityOrder,
yielder: T,
no_work_yield: time::Duration,
kill_signal: Option<Arc<OnSignal>>,
activities: Option<mpp::Sender<ThreadActivity>>,
) -> Self {
Self::new(
state_owner,
tasks,
ChaCha8Rng::seed_from_u64(seed),
idler,
priority,
yielder,
no_work_yield,
kill_signal,
activities,
DEFAULT_KILL_SIGNAL_CHECK_INTERVAL,
)
}
pub fn from_rng<R: foundation_compact::rng::Rng>(
state_owner: String,
tasks: SharedTaskQueue,
rng: &mut R,
idler: IdleMan,
priority: PriorityOrder,
yielder: T,
no_work_yield: time::Duration,
kill_signal: Option<Arc<OnSignal>>,
activities: Option<mpp::Sender<ThreadActivity>>,
) -> Self {
Self::from_seed(
rng.next_u64(),
state_owner,
tasks,
idler,
priority,
yielder,
no_work_yield,
kill_signal,
activities,
)
}
pub fn with_kill_signal_check_interval(mut self, interval: usize) -> Self {
self.kill_signal_check_interval = interval;
self
}
}
#[allow(unused)]
impl<T: ProcessController + Clone> LocalThreadExecutor<T> {
#[inline]
pub fn local_engine(&self) -> LocalExecutionEngine {
self.state.local_engine()
}
#[inline]
pub fn boxed_engine(&self) -> BoxedExecutionEngine {
self.state.boxed_engine()
}
#[inline]
#[must_use]
pub fn fork_ctx(&self) -> Ctx {
self.state.fork_ctx()
}
#[inline]
pub fn number_of_sleepers(&self) -> usize {
self.state.number_of_sleepers()
}
#[inline]
#[must_use]
pub fn has_runnable_work(&self) -> bool {
self.state.has_runnable_work()
}
#[inline]
#[must_use]
pub fn number_of_depends_true_violations(&self) -> usize {
self.state.number_of_depends_true_violations()
}
#[inline]
pub fn number_of_local_tasks(&self) -> usize {
self.state.number_of_local_tasks()
}
#[inline]
pub fn number_of_inprocess(&self) -> usize {
self.state.number_of_inprocess()
}
}
#[allow(unused)]
impl<T: ProcessController + Clone> LocalThreadExecutor<T> {
#[inline]
pub fn get_rng(&self) -> rc::Rc<cell::RefCell<ChaCha8Rng>> {
self.state.get_rng()
}
#[inline]
pub fn run_once(&self) -> ProgressIndicator {
let local_executor = self.state.local_engine();
self.state.schedule_and_do_work(Box::new(local_executor))
}
#[inline]
pub fn run_until<S>(&self, checker: S)
where
S: Fn(ProgressIndicator) -> bool,
{
loop {
if let Some(ref kill_signal) = self.kill_signal {
if kill_signal.probe() {
return;
}
}
let local_executor = self.state.local_engine();
let response = self.state.schedule_and_do_work(Box::new(local_executor));
if checker(response.clone()) {
break;
}
if matches!(
response,
ProgressIndicator::NoWork | ProgressIndicator::SpinWait(_)
) && self.state.total_tasks() == 0
{
break;
}
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
if matches!(&response, ProgressIndicator::NoWork) {
break;
}
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
if matches!(
&response,
ProgressIndicator::CanProgress(Some(State::Reschedule))
) {
break;
}
if let ProgressIndicator::SpinWait(spin_duration) = response {
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
{
let signal = self.yielder.yield_for(spin_duration);
if self.yielder.should_stop(&signal) {
break;
}
}
#[cfg(not(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm")))]
self.yielder.yield_for(spin_duration);
continue;
}
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
if matches!(&response, ProgressIndicator::Wait) {
let signal = self
.yielder
.yield_for(crate::valtron::wasm::JS_WAIT_CHECK_INTERVAL);
if self.yielder.should_stop(&signal) {
break;
}
}
if matches!(response, ProgressIndicator::CanProgress(_))
&& self.state.has_runnable_work()
{
continue;
}
let yield_duration = self
.state
.time_until_next_wakeup()
.unwrap_or(self.no_work_yield)
.min(MAX_YIELD_DURATION);
self.yielder.yield_for(yield_duration);
}
}
#[inline]
pub fn block_until_finished(&self) {
'main_loop: loop {
for _ in 0..200 {
match self.run_once() {
ProgressIndicator::NoWork => {
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
{
break 'main_loop; }
#[cfg(not(any(
feature = "js-wasmbindgen",
feature = "js-foundation-wasm"
)))]
break 'main_loop;
}
ProgressIndicator::SpinWait(duration) => {
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
{
let signal = self.yielder.yield_for(duration);
if self.yielder.should_stop(&signal) {
break 'main_loop; }
}
#[cfg(not(any(
feature = "js-wasmbindgen",
feature = "js-foundation-wasm"
)))]
self.yielder.yield_for(duration);
}
ProgressIndicator::CanProgress(_) => {}
ProgressIndicator::Wait => {
#[cfg(any(feature = "js-wasmbindgen", feature = "js-foundation-wasm"))]
{
let signal = self
.yielder
.yield_for(crate::valtron::wasm::JS_WAIT_CHECK_INTERVAL);
if self.yielder.should_stop(&signal) {
break 'main_loop; }
}
#[cfg(not(any(
feature = "js-wasmbindgen",
feature = "js-foundation-wasm"
)))]
self.yielder.yield_for(self.no_work_yield);
}
}
}
let yield_duration = self
.state
.time_until_next_wakeup()
.unwrap_or(self.no_work_yield);
self.yielder.yield_for(yield_duration);
}
}
#[inline]
pub fn block_on(&self) {
let kill_signal = self.kill_signal
.clone()
.expect("Calling LocalThreadExecutor::block_on requires kill_signal to be provided for termination");
loop {
if kill_signal.probe() {
return;
}
for i in 0..200 {
if i % self.kill_signal_check_interval == 0 && kill_signal.probe() {
return;
}
match self.run_once() {
ProgressIndicator::CanProgress(_) => {}
ProgressIndicator::NoWork => {
if kill_signal.probe() {
return;
}
let yield_duration =
self.state.time_until_next_wakeup().unwrap_or_else(|| {
if self.state.number_of_sleepers() > 0 {
READINESS_POLL_QUANTUM
} else {
self.no_work_yield
}
});
self.yielder.yield_for(yield_duration);
}
ProgressIndicator::SpinWait(duration) => {
if kill_signal.probe() {
return;
}
self.yielder.yield_for(duration);
}
ProgressIndicator::Wait => {
if kill_signal.probe() {
return;
}
self.yielder.yield_for(self.no_work_yield);
}
}
}
}
}
}
#[must_use]
pub fn send_any_task<Task>(
engine: BoxedExecutionEngine,
) -> TaskSpawnConfig<
Task::Ready,
Task::Pending,
Box<dyn TaskReadyResolver<Task::Ready, Task::Pending> + Send + 'static>,
Task,
>
where
Task::Ready: Send,
Task::Pending: Send,
Task: TaskIterator + Send + 'static,
{
TaskSpawnConfig::new(engine)
}
#[must_use]
pub fn send_typed_task<Task, Resolver>(
engine: BoxedExecutionEngine,
) -> TaskSpawnConfig<Task::Ready, Task::Pending, Resolver, Task>
where
Task::Ready: Send + 'static,
Task::Pending: Send + 'static,
Task: TaskIterator + Send + 'static,
Resolver: TaskReadyResolver<Task::Ready, Task::Pending> + Send + 'static,
{
TaskSpawnConfig::new(engine)
}