use crate::loom::sync::{Arc, Condvar, Mutex, MutexGuard};
use crate::runtime;
use crate::runtime::driver::Driver;
use crate::runtime::scheduler::multi_thread_alt::{
idle, queue, stats, Counters, Handle, Idle, Overflow, Stats, TraceStatus,
};
use crate::runtime::scheduler::{self, inject, Lock};
use crate::runtime::task::{OwnedTasks, TaskHarnessScheduleHooks};
use crate::runtime::{blocking, coop, driver, task, Config, SchedulerMetrics, WorkerMetrics};
use crate::runtime::{context, TaskHooks};
use crate::util::atomic_cell::AtomicCell;
use crate::util::rand::{FastRand, RngSeedGenerator};
use std::cell::{Cell, RefCell};
use std::task::Waker;
use std::time::Duration;
use std::{cmp, thread};
cfg_unstable_metrics! {
mod metrics;
}
mod taskdump_mock;
pub(super) struct Worker {
tick: u32,
pub(super) is_shutdown: bool,
is_traced: bool,
num_seq_local_queue_polls: u32,
global_queue_interval: u32,
workers_to_notify: Vec<usize>,
idle_snapshot: idle::Snapshot,
stats: stats::Ephemeral,
}
#[repr(align(128))]
pub(super) struct Core {
pub(super) index: usize,
lifo_slot: Option<Notified>,
run_queue: queue::Local<Arc<Handle>>,
pub(super) is_searching: bool,
stats: Stats,
rand: FastRand,
}
pub(crate) struct Shared {
remotes: Box<[Remote]>,
pub(super) inject: inject::Shared<Arc<Handle>>,
idle: Idle,
pub(super) owned: OwnedTasks<Arc<Handle>>,
pub(super) synced: Mutex<Synced>,
driver: AtomicCell<Driver>,
pub(super) condvars: Vec<Condvar>,
pub(super) trace_status: TraceStatus,
config: Config,
pub(super) scheduler_metrics: SchedulerMetrics,
pub(super) worker_metrics: Box<[WorkerMetrics]>,
_counters: Counters,
}
pub(crate) struct Synced {
pub(super) assigned_cores: Vec<Option<Box<Core>>>,
shutdown_cores: Vec<Box<Core>>,
shutdown_driver: Option<Box<Driver>>,
pub(super) idle: idle::Synced,
pub(crate) inject: inject::Synced,
}
struct Remote {
pub(super) steal: queue::Steal<Arc<Handle>>,
}
pub(crate) struct Context {
handle: Arc<Handle>,
index: usize,
lifo_enabled: Cell<bool>,
core: RefCell<Option<Box<Core>>>,
handoff_core: Arc<AtomicCell<Core>>,
pub(crate) defer: RefCell<Vec<Notified>>,
}
type RunResult = Result<Box<Core>, ()>;
type NextTaskResult = Result<(Option<Notified>, Box<Core>), ()>;
type Task = task::Task<Arc<Handle>>;
type Notified = task::Notified<Arc<Handle>>;
const MAX_LIFO_POLLS_PER_TICK: usize = 3;
pub(super) fn create(
num_cores: usize,
driver: Driver,
driver_handle: driver::Handle,
blocking_spawner: blocking::Spawner,
seed_generator: RngSeedGenerator,
config: Config,
) -> runtime::Handle {
let mut num_workers = num_cores;
if driver.is_enabled() {
num_workers += 1;
}
let mut cores = Vec::with_capacity(num_cores);
let mut remotes = Vec::with_capacity(num_cores);
let mut worker_metrics = Vec::with_capacity(num_cores);
for i in 0..num_cores {
let (steal, run_queue) = queue::local(config.local_queue_capacity);
let metrics = WorkerMetrics::from_config(&config);
let stats = Stats::new(&metrics);
cores.push(Box::new(Core {
index: i,
lifo_slot: None,
run_queue,
is_searching: false,
stats,
rand: FastRand::from_seed(config.seed_generator.next_seed()),
}));
remotes.push(Remote {
steal,
});
worker_metrics.push(metrics);
}
let (idle, idle_synced) = Idle::new(cores, num_workers);
let (inject, inject_synced) = inject::Shared::new();
let handle = Arc::new(Handle {
task_hooks: TaskHooks {
task_spawn_callback: config.before_spawn.clone(),
task_terminate_callback: config.after_termination.clone(),
},
shared: Shared {
remotes: remotes.into_boxed_slice(),
inject,
idle,
owned: OwnedTasks::new(num_cores),
synced: Mutex::new(Synced {
assigned_cores: (0..num_workers).map(|_| None).collect(),
shutdown_cores: Vec::with_capacity(num_cores),
shutdown_driver: None,
idle: idle_synced,
inject: inject_synced,
}),
driver: AtomicCell::new(Some(Box::new(driver))),
condvars: (0..num_workers).map(|_| Condvar::new()).collect(),
trace_status: TraceStatus::new(num_cores),
config,
scheduler_metrics: SchedulerMetrics::new(),
worker_metrics: worker_metrics.into_boxed_slice(),
_counters: Counters,
},
driver: driver_handle,
blocking_spawner,
seed_generator,
});
let rt_handle = runtime::Handle {
inner: scheduler::Handle::MultiThreadAlt(handle),
};
for index in 0..num_workers {
let handle = rt_handle.inner.expect_multi_thread_alt();
let h2 = handle.clone();
let handoff_core = Arc::new(AtomicCell::new(None));
handle
.blocking_spawner
.spawn_blocking(&rt_handle, move || run(index, h2, handoff_core, false));
}
rt_handle
}
#[track_caller]
pub(crate) fn block_in_place<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
struct Reset(coop::Budget);
impl Drop for Reset {
fn drop(&mut self) {
with_current(|maybe_cx| {
if let Some(cx) = maybe_cx {
let core = cx.handoff_core.take();
let mut cx_core = cx.core.borrow_mut();
assert!(cx_core.is_none());
*cx_core = core;
coop::set(self.0);
}
});
}
}
let mut had_entered = false;
let setup_result = with_current(|maybe_cx| {
match (
crate::runtime::context::current_enter_context(),
maybe_cx.is_some(),
) {
(context::EnterRuntime::Entered { .. }, true) => {
had_entered = true;
}
(
context::EnterRuntime::Entered {
allow_block_in_place,
},
false,
) => {
if allow_block_in_place {
had_entered = true;
return Ok(());
} else {
return Err(
"can call blocking only when running on the multi-threaded runtime",
);
}
}
(context::EnterRuntime::NotEntered, true) => {
return Ok(());
}
(context::EnterRuntime::NotEntered, false) => {
return Ok(());
}
}
let cx = maybe_cx.expect("no .is_some() == false cases above should lead here");
let core = match cx.core.borrow_mut().take() {
Some(core) => core,
None => return Ok(()),
};
cx.handoff_core.set(core);
let index = cx.index;
let handle = cx.handle.clone();
let handoff_core = cx.handoff_core.clone();
runtime::spawn_blocking(move || run(index, handle, handoff_core, true));
Ok(())
});
if let Err(panic_message) = setup_result {
panic!("{}", panic_message);
}
if had_entered {
let _reset = Reset(coop::stop());
crate::runtime::context::exit_runtime(f)
} else {
f()
}
}
fn run(
index: usize,
handle: Arc<Handle>,
handoff_core: Arc<AtomicCell<Core>>,
blocking_in_place: bool,
) {
struct AbortOnPanic;
impl Drop for AbortOnPanic {
fn drop(&mut self) {
if std::thread::panicking() {
eprintln!("worker thread panicking; aborting process");
std::process::abort();
}
}
}
#[cfg(debug_assertions)]
let _abort_on_panic = AbortOnPanic;
let num_workers = handle.shared.condvars.len();
let mut worker = Worker {
tick: 0,
num_seq_local_queue_polls: 0,
global_queue_interval: Stats::DEFAULT_GLOBAL_QUEUE_INTERVAL,
is_shutdown: false,
is_traced: false,
workers_to_notify: Vec::with_capacity(num_workers - 1),
idle_snapshot: idle::Snapshot::new(&handle.shared.idle),
stats: stats::Ephemeral::new(),
};
let sched_handle = scheduler::Handle::MultiThreadAlt(handle.clone());
crate::runtime::context::enter_runtime(&sched_handle, true, |_| {
let cx = scheduler::Context::MultiThreadAlt(Context {
index,
lifo_enabled: Cell::new(!handle.shared.config.disable_lifo_slot),
handle,
core: RefCell::new(None),
handoff_core,
defer: RefCell::new(Vec::with_capacity(64)),
});
context::set_scheduler(&cx, || {
let cx = cx.expect_multi_thread_alt();
let res = worker.run(&cx, blocking_in_place);
debug_assert!(res.is_err());
if !cx.defer.borrow().is_empty() {
worker.schedule_deferred_without_core(&cx, &mut cx.shared().synced.lock());
}
});
});
}
macro_rules! try_task {
($e:expr) => {{
let (task, core) = $e?;
if task.is_some() {
return Ok((task, core));
}
core
}};
}
macro_rules! try_task_new_batch {
($w:expr, $e:expr) => {{
let (task, mut core) = $e?;
if task.is_some() {
core.stats.start_processing_scheduled_tasks(&mut $w.stats);
return Ok((task, core));
}
core
}};
}
impl Worker {
fn run(&mut self, cx: &Context, blocking_in_place: bool) -> RunResult {
let (maybe_task, mut core) = {
if blocking_in_place {
if let Some(core) = cx.handoff_core.take() {
(None, core)
} else {
return Err(());
}
} else {
let mut synced = cx.shared().synced.lock();
if let Some(core) = self.try_acquire_available_core(cx, &mut synced) {
let maybe_task = cx.shared().next_remote_task_synced(&mut synced);
(maybe_task, core)
} else {
self.wait_for_core(cx, synced)?
}
}
};
cx.shared().worker_metrics[core.index].set_thread_id(thread::current().id());
core.stats.start_processing_scheduled_tasks(&mut self.stats);
if let Some(task) = maybe_task {
core = self.run_task(cx, core, task)?;
}
while !self.is_shutdown {
let (maybe_task, c) = self.next_task(cx, core)?;
core = c;
if let Some(task) = maybe_task {
core = self.run_task(cx, core, task)?;
} else {
assert!(self.is_shutdown);
break;
}
}
cx.shared().shutdown_core(&cx.handle, core);
self.shutdown_clear_defer(cx);
Err(())
}
fn try_acquire_available_core(
&mut self,
cx: &Context,
synced: &mut Synced,
) -> Option<Box<Core>> {
if let Some(mut core) = cx
.shared()
.idle
.try_acquire_available_core(&mut synced.idle)
{
self.reset_acquired_core(cx, synced, &mut core);
Some(core)
} else {
None
}
}
fn wait_for_core(
&mut self,
cx: &Context,
mut synced: MutexGuard<'_, Synced>,
) -> NextTaskResult {
if cx.shared().idle.needs_searching() {
if let Some(mut core) = self.try_acquire_available_core(cx, &mut synced) {
cx.shared().idle.transition_worker_to_searching(&mut core);
return Ok((None, core));
}
}
cx.shared()
.idle
.transition_worker_to_parked(&mut synced, cx.index);
let mut core = loop {
if let Some(core) = synced.assigned_cores[cx.index].take() {
break core;
}
if cx.shared().inject.is_closed(&synced.inject) {
self.shutdown_clear_defer(cx);
return Err(());
}
synced = cx.shared().condvars[cx.index].wait(synced).unwrap();
};
self.reset_acquired_core(cx, &mut synced, &mut core);
if self.is_shutdown {
return Ok((None, core));
}
let n = cmp::max(core.run_queue.remaining_slots() / 2, 1);
let maybe_task = self.next_remote_task_batch_synced(cx, &mut synced, &mut core, n);
core.stats.unparked();
self.flush_metrics(cx, &mut core);
Ok((maybe_task, core))
}
fn reset_acquired_core(&mut self, cx: &Context, synced: &mut Synced, core: &mut Core) {
self.global_queue_interval = core.stats.tuned_global_queue_interval(&cx.shared().config);
self.reset_lifo_enabled(cx);
#[cfg(not(loom))]
debug_assert!(core.run_queue.is_empty());
self.update_global_flags(cx, synced);
}
fn next_task(&mut self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
self.assert_lifo_enabled_is_correct(cx);
if self.is_traced {
core = cx.handle.trace_core(core);
}
self.tick = self.tick.wrapping_add(1);
core = try_task!(self.maybe_maintenance(&cx, core));
core = try_task!(self.next_notified_task(cx, core));
core.stats.end_processing_scheduled_tasks(&mut self.stats);
super::counters::inc_num_no_local_work();
if !cx.defer.borrow().is_empty() {
try_task_new_batch!(self, self.park_yield(cx, core));
panic!("what happened to the deferred tasks? 🤔");
}
while !self.is_shutdown {
core = try_task_new_batch!(self, self.search_for_work(cx, core));
debug_assert!(cx.defer.borrow().is_empty());
core = try_task_new_batch!(self, self.park(cx, core));
}
self.shutdown_clear_defer(cx);
Ok((None, core))
}
fn next_notified_task(&mut self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
self.num_seq_local_queue_polls += 1;
if self.num_seq_local_queue_polls % self.global_queue_interval == 0 {
super::counters::inc_global_queue_interval();
self.num_seq_local_queue_polls = 0;
self.tune_global_queue_interval(cx, &mut core);
if let Some(task) = self.next_remote_task(cx) {
return Ok((Some(task), core));
}
}
if let Some(task) = core.next_local_task() {
return Ok((Some(task), core));
}
self.next_remote_task_batch(cx, core)
}
fn next_remote_task(&self, cx: &Context) -> Option<Notified> {
if cx.shared().inject.is_empty() {
return None;
}
let mut synced = cx.shared().synced.lock();
cx.shared().next_remote_task_synced(&mut synced)
}
fn next_remote_task_batch(&self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
if cx.shared().inject.is_empty() {
return Ok((None, core));
}
let cap = usize::min(
core.run_queue.remaining_slots(),
usize::max(core.run_queue.max_capacity() / 2, 1),
);
let mut synced = cx.shared().synced.lock();
let maybe_task = self.next_remote_task_batch_synced(cx, &mut synced, &mut core, cap);
Ok((maybe_task, core))
}
fn next_remote_task_batch_synced(
&self,
cx: &Context,
synced: &mut Synced,
core: &mut Core,
max: usize,
) -> Option<Notified> {
super::counters::inc_num_remote_batch();
let n = if core.is_searching {
cx.shared().inject.len() / cx.shared().idle.num_searching() + 1
} else {
cx.shared().inject.len() / cx.shared().remotes.len() + 1
};
let n = usize::min(n, max) + 1;
let mut tasks = unsafe { cx.shared().inject.pop_n(&mut synced.inject, n) };
let ret = tasks.next();
core.run_queue.push_back(tasks);
ret
}
fn search_for_work(&mut self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
#[cfg(not(loom))]
const ROUNDS: usize = 4;
#[cfg(loom)]
const ROUNDS: usize = 1;
debug_assert!(core.lifo_slot.is_none());
#[cfg(not(loom))]
debug_assert!(core.run_queue.is_empty());
if !core.run_queue.can_steal() {
return Ok((None, core));
}
if !self.transition_to_searching(cx, &mut core) {
return Ok((None, core));
}
cx.shared().idle.snapshot(&mut self.idle_snapshot);
let num = cx.shared().remotes.len();
for i in 0..ROUNDS {
let start = core.rand.fastrand_n(num as u32) as usize;
if let Some(task) = self.steal_one_round(cx, &mut core, start) {
return Ok((Some(task), core));
}
core = try_task!(self.next_remote_task_batch(cx, core));
if i > 0 {
super::counters::inc_num_spin_stall();
std::thread::sleep(std::time::Duration::from_micros(i as u64));
}
}
Ok((None, core))
}
fn steal_one_round(&self, cx: &Context, core: &mut Core, start: usize) -> Option<Notified> {
let num = cx.shared().remotes.len();
for i in 0..num {
let i = (start + i) % num;
if i == core.index {
continue;
}
if self.idle_snapshot.is_idle(i) {
continue;
}
let target = &cx.shared().remotes[i];
if let Some(task) = target
.steal
.steal_into(&mut core.run_queue, &mut core.stats)
{
return Some(task);
}
}
None
}
fn run_task(&mut self, cx: &Context, mut core: Box<Core>, task: Notified) -> RunResult {
let task = cx.shared().owned.assert_owner(task);
if self.transition_from_searching(cx, &mut core) {
super::counters::inc_num_relay_search();
cx.shared().notify_parked_local();
}
self.assert_lifo_enabled_is_correct(cx);
core.stats.start_poll(&mut self.stats);
*cx.core.borrow_mut() = Some(core);
coop::budget(|| {
super::counters::inc_num_polls();
task.run();
let mut lifo_polls = 0;
loop {
let mut core = match cx.core.borrow_mut().take() {
Some(core) => core,
None => {
return Err(());
}
};
let task = match core.next_lifo_task() {
Some(task) => task,
None => {
self.reset_lifo_enabled(cx);
core.stats.end_poll();
return Ok(core);
}
};
if !coop::has_budget_remaining() {
core.stats.end_poll();
core.run_queue
.push_back_or_overflow(task, cx.shared(), &mut core.stats);
debug_assert!(cx.lifo_enabled.get());
return Ok(core);
}
lifo_polls += 1;
super::counters::inc_lifo_schedules();
if lifo_polls >= MAX_LIFO_POLLS_PER_TICK {
cx.lifo_enabled.set(false);
super::counters::inc_lifo_capped();
}
*cx.core.borrow_mut() = Some(core);
let task = cx.shared().owned.assert_owner(task);
super::counters::inc_num_lifo_polls();
task.run();
}
})
}
fn schedule_deferred_with_core<'a>(
&mut self,
cx: &'a Context,
mut core: Box<Core>,
synced: impl FnOnce() -> MutexGuard<'a, Synced>,
) -> NextTaskResult {
let mut defer = cx.defer.borrow_mut();
let task = defer.pop();
if task.is_none() {
return Ok((None, core));
}
if !defer.is_empty() {
let mut synced = synced();
let num_fanout = cmp::min(defer.len(), cx.shared().idle.num_idle(&synced.idle));
let num_fanout = cmp::min(2, num_fanout);
if num_fanout > 0 {
cx.shared()
.push_remote_task_batch_synced(&mut synced, defer.drain(..num_fanout));
cx.shared()
.idle
.notify_mult(&mut synced, &mut self.workers_to_notify, num_fanout);
}
drop(synced);
}
for worker in self.workers_to_notify.drain(..) {
cx.shared().condvars[worker].notify_one()
}
if !defer.is_empty() {
for task in defer.drain(..) {
core.run_queue
.push_back_or_overflow(task, cx.shared(), &mut core.stats);
}
cx.shared().notify_parked_local();
}
Ok((task, core))
}
fn schedule_deferred_without_core<'a>(&mut self, cx: &Context, synced: &mut Synced) {
let mut defer = cx.defer.borrow_mut();
let num = defer.len();
if num > 0 {
cx.shared()
.push_remote_task_batch_synced(synced, defer.drain(..));
debug_assert!(self.workers_to_notify.is_empty());
cx.shared()
.idle
.notify_mult(synced, &mut self.workers_to_notify, num);
for worker in self.workers_to_notify.drain(..) {
cx.shared().condvars[worker].notify_one()
}
}
}
fn maybe_maintenance(&mut self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
if self.tick % cx.shared().config.event_interval == 0 {
super::counters::inc_num_maintenance();
core.stats.end_processing_scheduled_tasks(&mut self.stats);
core = try_task_new_batch!(self, self.park_yield(cx, core));
core.stats.start_processing_scheduled_tasks(&mut self.stats);
}
Ok((None, core))
}
fn flush_metrics(&self, cx: &Context, core: &mut Core) {
core.stats.submit(&cx.shared().worker_metrics[core.index]);
}
fn update_global_flags(&mut self, cx: &Context, synced: &mut Synced) {
if !self.is_shutdown {
self.is_shutdown = cx.shared().inject.is_closed(&synced.inject);
}
if !self.is_traced {
self.is_traced = cx.shared().trace_status.trace_requested();
}
}
fn park_yield(&mut self, cx: &Context, core: Box<Core>) -> NextTaskResult {
if let Some(mut driver) = cx.shared().driver.take() {
driver.park_timeout(&cx.handle.driver, Duration::from_millis(0));
cx.shared().driver.set(driver);
}
let (maybe_task, mut core) =
self.schedule_deferred_with_core(cx, core, || cx.shared().synced.lock())?;
self.flush_metrics(cx, &mut core);
self.update_global_flags(cx, &mut cx.shared().synced.lock());
Ok((maybe_task, core))
}
fn park(&mut self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
if let Some(f) = &cx.shared().config.before_park {
f();
}
if self.can_transition_to_parked(&mut core) {
debug_assert!(!self.is_shutdown);
debug_assert!(!self.is_traced);
core = try_task!(self.do_park(cx, core));
}
if let Some(f) = &cx.shared().config.after_unpark {
f();
}
Ok((None, core))
}
fn do_park(&mut self, cx: &Context, mut core: Box<Core>) -> NextTaskResult {
let was_searching = core.is_searching;
let mut synced = cx.shared().synced.lock();
#[cfg(not(loom))]
debug_assert!(core.run_queue.is_empty());
let n = cmp::max(core.run_queue.remaining_slots() / 2, 1);
if let Some(task) = self.next_remote_task_batch_synced(cx, &mut synced, &mut core, n) {
return Ok((Some(task), core));
}
if !was_searching {
if cx
.shared()
.idle
.transition_worker_to_searching_if_needed(&mut synced.idle, &mut core)
{
return Ok((None, core));
}
}
super::counters::inc_num_parks();
core.stats.about_to_park();
self.flush_metrics(cx, &mut core);
self.update_global_flags(cx, &mut synced);
if self.is_shutdown {
return Ok((None, core));
}
core.is_searching = false;
cx.shared().idle.release_core(&mut synced, core);
drop(synced);
if was_searching {
if cx.shared().idle.transition_worker_from_searching() {
for i in 0..cx.shared().remotes.len() {
if !cx.shared().remotes[i].steal.is_empty() {
let mut synced = cx.shared().synced.lock();
if let Some(mut core) = self.try_acquire_available_core(cx, &mut synced) {
cx.shared().idle.transition_worker_to_searching(&mut core);
return Ok((None, core));
} else {
break;
}
}
}
}
}
if let Some(mut driver) = cx.shared().take_driver() {
driver.park(&cx.handle.driver);
synced = cx.shared().synced.lock();
if cx.shared().inject.is_closed(&mut synced.inject) {
synced.shutdown_driver = Some(driver);
self.shutdown_clear_defer(cx);
cx.shared().shutdown_finalize(&cx.handle, &mut synced);
return Err(());
}
cx.shared().driver.set(driver);
if let Some(core) = self.try_acquire_available_core(cx, &mut synced) {
self.schedule_deferred_with_core(cx, core, move || synced)
} else {
self.schedule_deferred_without_core(cx, &mut synced);
self.wait_for_core(cx, synced)
}
} else {
synced = cx.shared().synced.lock();
self.wait_for_core(cx, synced)
}
}
fn transition_to_searching(&self, cx: &Context, core: &mut Core) -> bool {
if !core.is_searching {
cx.shared().idle.try_transition_worker_to_searching(core);
}
core.is_searching
}
fn transition_from_searching(&self, cx: &Context, core: &mut Core) -> bool {
if !core.is_searching {
return false;
}
core.is_searching = false;
cx.shared().idle.transition_worker_from_searching()
}
fn can_transition_to_parked(&self, core: &mut Core) -> bool {
!self.has_tasks(core) && !self.is_shutdown && !self.is_traced
}
fn has_tasks(&self, core: &Core) -> bool {
core.lifo_slot.is_some() || !core.run_queue.is_empty()
}
fn reset_lifo_enabled(&self, cx: &Context) {
cx.lifo_enabled
.set(!cx.handle.shared.config.disable_lifo_slot);
}
fn assert_lifo_enabled_is_correct(&self, cx: &Context) {
debug_assert_eq!(
cx.lifo_enabled.get(),
!cx.handle.shared.config.disable_lifo_slot
);
}
fn tune_global_queue_interval(&mut self, cx: &Context, core: &mut Core) {
let next = core.stats.tuned_global_queue_interval(&cx.shared().config);
if u32::abs_diff(self.global_queue_interval, next) > 2 {
self.global_queue_interval = next;
}
}
fn shutdown_clear_defer(&self, cx: &Context) {
let mut defer = cx.defer.borrow_mut();
for task in defer.drain(..) {
drop(task);
}
}
}
impl Context {
pub(crate) fn defer(&self, waker: &Waker) {
waker.wake_by_ref();
}
fn shared(&self) -> &Shared {
&self.handle.shared
}
#[cfg_attr(not(feature = "time"), allow(dead_code))]
pub(crate) fn get_worker_index(&self) -> usize {
self.index
}
}
impl Core {
fn next_local_task(&mut self) -> Option<Notified> {
self.next_lifo_task().or_else(|| self.run_queue.pop())
}
fn next_lifo_task(&mut self) -> Option<Notified> {
self.lifo_slot.take()
}
}
impl Shared {
fn next_remote_task_synced(&self, synced: &mut Synced) -> Option<Notified> {
unsafe { self.inject.pop(&mut synced.inject) }
}
pub(super) fn schedule_task(&self, task: Notified, is_yield: bool) {
use std::ptr;
with_current(|maybe_cx| {
if let Some(cx) = maybe_cx {
if ptr::eq(self, &cx.handle.shared) {
if let Some(core) = cx.core.borrow_mut().as_mut() {
if is_yield {
cx.defer.borrow_mut().push(task);
} else {
self.schedule_local(cx, core, task);
}
} else {
cx.defer.borrow_mut().push(task);
}
return;
}
}
self.schedule_remote(task);
})
}
fn schedule_local(&self, cx: &Context, core: &mut Core, task: Notified) {
core.stats.inc_local_schedule_count();
if cx.lifo_enabled.get() {
let prev = std::mem::replace(&mut core.lifo_slot, Some(task));
if let Some(prev) = prev {
core.run_queue
.push_back_or_overflow(prev, self, &mut core.stats);
} else {
return;
}
} else {
core.run_queue
.push_back_or_overflow(task, self, &mut core.stats);
}
self.notify_parked_local();
}
fn notify_parked_local(&self) {
super::counters::inc_num_inc_notify_local();
self.idle.notify_local(self);
}
fn schedule_remote(&self, task: Notified) {
super::counters::inc_num_notify_remote();
self.scheduler_metrics.inc_remote_schedule_count();
let mut synced = self.synced.lock();
self.push_remote_task(&mut synced, task);
self.idle.notify_remote(synced, self);
}
pub(super) fn close(&self, handle: &Handle) {
{
let mut synced = self.synced.lock();
if let Some(driver) = self.driver.take() {
synced.shutdown_driver = Some(driver);
}
if !self.inject.close(&mut synced.inject) {
return;
}
self.idle.shutdown(&mut synced, self);
}
self.idle.shutdown_unassigned_cores(handle, self);
}
fn push_remote_task(&self, synced: &mut Synced, task: Notified) {
unsafe {
self.inject.push(&mut synced.inject, task);
}
}
fn push_remote_task_batch<I>(&self, iter: I)
where
I: Iterator<Item = task::Notified<Arc<Handle>>>,
{
unsafe {
self.inject.push_batch(self, iter);
}
}
fn push_remote_task_batch_synced<I>(&self, synced: &mut Synced, iter: I)
where
I: Iterator<Item = task::Notified<Arc<Handle>>>,
{
unsafe {
self.inject.push_batch(&mut synced.inject, iter);
}
}
fn take_driver(&self) -> Option<Box<Driver>> {
if !self.driver_enabled() {
return None;
}
self.driver.take()
}
fn driver_enabled(&self) -> bool {
self.condvars.len() > self.remotes.len()
}
pub(super) fn shutdown_core(&self, handle: &Handle, mut core: Box<Core>) {
let start = core.rand.fastrand_n(self.owned.get_shard_size() as u32);
self.owned.close_and_shutdown_all(start as usize);
core.stats.submit(&self.worker_metrics[core.index]);
let mut synced = self.synced.lock();
synced.shutdown_cores.push(core);
self.shutdown_finalize(handle, &mut synced);
}
pub(super) fn shutdown_finalize(&self, handle: &Handle, synced: &mut Synced) {
if synced.shutdown_cores.len() != self.remotes.len() {
return;
}
let driver = synced.shutdown_driver.take();
if self.driver_enabled() && driver.is_none() {
return;
}
debug_assert!(self.owned.is_empty());
for mut core in synced.shutdown_cores.drain(..) {
while core.next_local_task().is_some() {}
}
if let Some(mut driver) = driver {
driver.shutdown(&handle.driver);
}
while let Some(task) = self.next_remote_task_synced(synced) {
drop(task);
}
}
}
impl Overflow<Arc<Handle>> for Shared {
fn push(&self, task: task::Notified<Arc<Handle>>) {
self.push_remote_task(&mut self.synced.lock(), task);
}
fn push_batch<I>(&self, iter: I)
where
I: Iterator<Item = task::Notified<Arc<Handle>>>,
{
self.push_remote_task_batch(iter)
}
}
impl<'a> Lock<inject::Synced> for &'a Shared {
type Handle = SyncedGuard<'a>;
fn lock(self) -> Self::Handle {
SyncedGuard {
lock: self.synced.lock(),
}
}
}
impl<'a> Lock<Synced> for &'a Shared {
type Handle = SyncedGuard<'a>;
fn lock(self) -> Self::Handle {
SyncedGuard {
lock: self.synced.lock(),
}
}
}
impl task::Schedule for Arc<Handle> {
fn release(&self, task: &Task) -> Option<Task> {
self.shared.owned.remove(task)
}
fn schedule(&self, task: Notified) {
self.shared.schedule_task(task, false);
}
fn hooks(&self) -> TaskHarnessScheduleHooks {
TaskHarnessScheduleHooks {
task_terminate_callback: self.task_hooks.task_terminate_callback.clone(),
}
}
fn yield_now(&self, task: Notified) {
self.shared.schedule_task(task, true);
}
}
impl AsMut<Synced> for Synced {
fn as_mut(&mut self) -> &mut Synced {
self
}
}
pub(crate) struct SyncedGuard<'a> {
lock: crate::loom::sync::MutexGuard<'a, Synced>,
}
impl<'a> AsMut<inject::Synced> for SyncedGuard<'a> {
fn as_mut(&mut self) -> &mut inject::Synced {
&mut self.lock.inject
}
}
impl<'a> AsMut<Synced> for SyncedGuard<'a> {
fn as_mut(&mut self) -> &mut Synced {
&mut self.lock
}
}
#[track_caller]
fn with_current<R>(f: impl FnOnce(Option<&Context>) -> R) -> R {
use scheduler::Context::MultiThreadAlt;
context::with_scheduler(|ctx| match ctx {
Some(MultiThreadAlt(ctx)) => f(Some(ctx)),
_ => f(None),
})
}