use std::collections::VecDeque;
use std::num::NonZeroUsize;
use std::task;
use std::thread::ThreadId;
use std::time::Instant;
use nm::{Event, Magnitude, MetricsPusher, Push};
use observed::Sink;
use tick::SimpleClock;
use crate::{CycleOutcome, ExecutorBuilder, ExecutorShared, Shutdown, ShutdownCause, TaskHandle, TaskList, TaskSet, executor_context};
#[derive(Debug)]
pub struct Executor {
shared: ExecutorShared,
active: VecDeque<TaskHandle>,
pending: TaskList,
clock: SimpleClock,
last_cycle_ended: Option<Instant>,
}
impl Executor {
#[cfg_attr(test, mutants::skip)] pub fn builder() -> ExecutorBuilder {
ExecutorBuilder::new()
}
#[must_use]
pub(crate) fn new(owner_waker: task::Waker, sink: Sink, thread_id: ThreadId, panic_policy: crate::PanicPolicy) -> Self {
let shared = ExecutorShared::new(owner_waker, sink, thread_id, panic_policy);
let clock = SimpleClock::new_system().with_fast_instant(true);
executor_context::register(thread_id);
Self {
shared,
active: VecDeque::new(),
pending: TaskList::default(),
clock,
last_cycle_ended: None,
}
}
#[must_use]
pub fn tasks(&self) -> TaskSet {
TaskSet::new(&self.shared)
}
#[must_use]
pub fn shutdown(&self) -> Shutdown {
self.shared.shutdown().clone()
}
#[must_use]
pub fn has_tasks(&self) -> bool {
self.shared.has_tasks()
}
#[must_use]
pub fn execute_cycle(&mut self) -> CycleOutcome {
let _cycle = executor_context::enter_cycle(self.shared.wake().thread_id());
let cycle_started = self.start_cycle();
self.accept_new_tasks();
self.activate_awakened_tasks();
self.poll_active_tasks();
self.cancel_if_shutting_down();
let outcome = self.cycle_outcome();
self.finish_cycle(cycle_started);
outcome
}
fn start_cycle(&self) -> Instant {
let started = self.clock.instant();
if let Some(last_ended) = self.last_cycle_ended {
CYCLE_GAP_MILLIS.with(|event| {
event.observe_millis(started.saturating_duration_since(last_ended));
});
}
started
}
fn finish_cycle(&mut self, started: Instant) {
let ended = self.clock.instant();
self.last_cycle_ended = Some(ended);
CYCLE_DURATION_MILLIS.with(|event| {
event.observe_millis(ended.saturating_duration_since(started));
});
EXECUTOR_PUSHER.with(MetricsPusher::push);
}
fn accept_new_tasks(&mut self) {
let accepted = self.shared.accept_new_tasks(&mut self.active);
if let Some(accepted) = NonZeroUsize::new(accepted) {
TASKS_ACCEPTED.with(|event| event.observe(accepted.get()));
}
}
fn cancel_if_shutting_down(&mut self) {
if self.shared.shutdown().is_triggered() {
let cause = self.shared.shutdown().cause();
let aborted_count = self.prepare_task_abort();
self.abort_prepared_tasks(aborted_count, true, &cause);
}
}
fn cycle_outcome(&self) -> CycleOutcome {
if self.evaluate_shutdown_completion() {
CycleOutcome::Shutdown(self.shared.shutdown().cause())
} else if self.has_work_to_do() {
CYCLE_OUTCOME_CONTINUE.with(Event::observe_once);
CycleOutcome::Continue
} else {
CYCLE_OUTCOME_SUSPEND.with(Event::observe_once);
CycleOutcome::Suspend
}
}
fn activate_awakened_tasks(&mut self) {
self.shared.drain_remote_wakes();
let first_awakened = self.active.len();
executor_context::append_wakes(self.shared.wake().thread_id(), &mut self.active);
for task_handle in self.active.range(first_awakened..) {
unsafe { task_handle.as_task() }.clear_queued_notification();
TASKS_ACTIVATED_VIA_AWAKENED_SET.with(Event::observe_once);
}
}
fn poll_active_tasks(&mut self) {
let mut completed_count: usize = 0;
let mut pending_count: usize = 0;
while let Some(task_handle) = self.active.front() {
let task = unsafe { task_handle.as_task() };
let pointer = task_handle.as_ptr();
let linked = task.links().linked.get();
if self.shared.shutdown().is_triggered() {
task.abort(self.shared.shutdown().cause());
self.shared.decrement_task_count(1);
drop(unsafe { self.pending.remove(pointer) });
completed_count = completed_count.saturating_add(1);
self.active.pop_front();
continue;
}
let outcome = task.poll(&self.shared);
let task_handle = self.active.pop_front().expect("the task remains active until its poll returns");
match outcome {
task::Poll::Ready(()) => {
if linked {
drop(unsafe { self.pending.remove(pointer) });
}
self.shared.decrement_task_count(1);
completed_count = completed_count.saturating_add(1);
}
task::Poll::Pending => {
pending_count = pending_count.saturating_add(1);
if linked {
drop(task_handle);
} else {
self.pending.insert(task_handle);
}
}
}
}
record_poll_metrics(pending_count, completed_count);
}
#[must_use]
#[cfg_attr(test, mutants::skip)] fn has_work_to_do(&self) -> bool {
debug_assert!(self.active.is_empty(), "active tasks are drained before deciding the next cycle");
self.shared.has_new_tasks() || executor_context::has_wakes(self.shared.wake().thread_id()) || self.shared.wake().has_remote_wakes()
}
fn prepare_task_abort(&mut self) -> usize {
self.shared.close_remote_wakes();
executor_context::clear_wakes(self.shared.wake().thread_id());
for task_handle in &self.active {
drop(unsafe { self.pending.remove(task_handle.as_ptr()) });
}
self.shared.drain_new_tasks(&mut self.active);
while let Some(task_handle) = self.pending.pop_front() {
self.active.push_back(task_handle);
}
let aborted_count = self.active.len();
self.shared.decrement_task_count(aborted_count);
match EXECUTOR_PUSHER.try_with(|_pusher| TASKS_ABORTED_ON_SHUTDOWN.try_with(|event| event.batch(aborted_count).observe_once())) {
Ok(Ok(())) => {}
Ok(Err(_access_error)) | Err(_access_error) => {
}
}
aborted_count
}
fn abort_prepared_tasks(&mut self, aborted_count: usize, record_drop_metric: bool, cause: &ShutdownCause) {
while let Some(task_handle) = self.active.pop_front() {
unsafe { task_handle.as_task() }.abort(cause.clone());
}
if record_drop_metric && let Some(aborted_count) = NonZeroUsize::new(aborted_count) {
TASKS_DROPPED.with(|event| event.observe(aborted_count.get()));
}
}
#[cfg_attr(test, mutants::skip)] #[must_use]
fn evaluate_shutdown_completion(&self) -> bool {
if !self.shared.shutdown().is_triggered() {
return false;
}
debug_assert!(self.active.is_empty());
debug_assert!(self.pending.is_empty());
self.shared.shutdown_complete()
}
}
impl Drop for Executor {
fn drop(&mut self) {
struct FinishDrop<'a>(&'a mut Executor);
impl Drop for FinishDrop<'_> {
#[cfg_attr(coverage_nightly, coverage(off))] fn drop(&mut self) {
if std::thread::panicking() {
let cause = self.0.shared.shutdown().cause();
let aborted_count = self.0.prepare_task_abort();
self.0.abort_prepared_tasks(aborted_count, false, &cause);
}
executor_context::unregister(self.0.shared.wake().thread_id());
}
}
_ = self.shared.shutdown().publish(ShutdownCause::default());
let cause = self.shared.shutdown().cause();
let guard = FinishDrop(self);
let aborted_count = guard.0.prepare_task_abort();
guard.0.abort_prepared_tasks(aborted_count, false, &cause);
if EXECUTOR_PUSHER.try_with(MetricsPusher::push).is_err() {
}
debug_assert!(guard.0.shared.shutdown_complete());
}
}
#[cfg_attr(test, mutants::skip)] fn record_poll_metrics(pending_count: usize, completed_count: usize) {
if pending_count != 0 {
TASKS_INACTIVATED.with(|event| event.observe(pending_count));
}
if completed_count != 0 {
TASKS_COMPLETED.with(|event| event.observe(completed_count));
TASKS_DROPPED.with(|event| event.observe(completed_count));
}
}
const MILLIS_BUCKETS: &[Magnitude] = &[0, 1, 20, 50, 100, 1_000, 10_000];
const TASKS_TOUCHED_PER_CYCLE_BUCKETS: &[Magnitude] = &[0, 1, 10, 100, 1_000];
const TASKS_MANAGED_PER_CYCLE_BUCKETS: &[Magnitude] = &[0, 1, 10, 100, 1_000];
thread_local! {
static EXECUTOR_PUSHER: MetricsPusher = MetricsPusher::new();
static TASKS_ABORTED_ON_SHUTDOWN: Event<Push> = Event::builder()
.name("ae_tasks_aborted_on_shutdown")
.pusher_local(&EXECUTOR_PUSHER)
.build();
static TASKS_ACCEPTED: Event<Push> = Event::builder()
.name("ae_tasks_accepted_per_cycle")
.histogram(TASKS_MANAGED_PER_CYCLE_BUCKETS)
.pusher_local(&EXECUTOR_PUSHER)
.build();
static TASKS_COMPLETED: Event<Push> = Event::builder()
.name("ae_tasks_completed_per_cycle")
.histogram(TASKS_TOUCHED_PER_CYCLE_BUCKETS)
.pusher_local(&EXECUTOR_PUSHER)
.build();
static TASKS_INACTIVATED: Event<Push> = Event::builder()
.name("ae_tasks_inactivated_per_cycle")
.histogram(TASKS_TOUCHED_PER_CYCLE_BUCKETS)
.pusher_local(&EXECUTOR_PUSHER)
.build();
static TASKS_DROPPED: Event<Push> = Event::builder()
.name("ae_tasks_dropped_per_cycle")
.histogram(TASKS_MANAGED_PER_CYCLE_BUCKETS)
.pusher_local(&EXECUTOR_PUSHER)
.build();
static TASKS_ACTIVATED_VIA_AWAKENED_SET: Event<Push> = Event::builder()
.name("ae_tasks_activated_via_awakened_set")
.pusher_local(&EXECUTOR_PUSHER)
.build();
static CYCLE_DURATION_MILLIS: Event<Push> = Event::builder()
.name("ae_cycle_duration_millis")
.histogram(MILLIS_BUCKETS)
.pusher_local(&EXECUTOR_PUSHER)
.build();
static CYCLE_GAP_MILLIS: Event<Push> = Event::builder()
.name("ae_cycle_gap_millis")
.histogram(MILLIS_BUCKETS)
.pusher_local(&EXECUTOR_PUSHER)
.build();
static CYCLE_OUTCOME_CONTINUE: Event<Push> = Event::builder()
.name("ae_cycle_outcome_continue")
.pusher_local(&EXECUTOR_PUSHER)
.build();
static CYCLE_OUTCOME_SUSPEND: Event<Push> = Event::builder()
.name("ae_cycle_outcome_suspend")
.pusher_local(&EXECUTOR_PUSHER)
.build();
}