This commit is contained in:
Carl Lerche
2023-06-08 14:07:07 -07:00
parent e5371b3820
commit 0f605b51ca
7 changed files with 313 additions and 127 deletions
+2 -8
View File
@@ -1172,7 +1172,7 @@ cfg_rt_multi_thread! {
fn build_threaded_runtime(&mut self) -> io::Result<Runtime> {
use crate::loom::sys::num_cpus;
use crate::runtime::{Config, runtime::Scheduler};
use crate::runtime::scheduler::{self, MultiThread};
use crate::runtime::scheduler::MultiThread;
let core_threads = self.worker_threads.unwrap_or_else(num_cpus);
@@ -1187,7 +1187,7 @@ cfg_rt_multi_thread! {
let seed_generator_1 = self.seed_generator.next_generator();
let seed_generator_2 = self.seed_generator.next_generator();
let (scheduler, handle, launch) = MultiThread::new(
let (scheduler, handle) = MultiThread::new(
core_threads,
driver,
driver_handle,
@@ -1206,12 +1206,6 @@ cfg_rt_multi_thread! {
},
);
let handle = Handle { inner: scheduler::Handle::MultiThread(handle) };
// Spawn the thread pool workers
let _enter = handle.enter();
launch.launch();
Ok(Runtime::from_parts(Scheduler::MultiThread(scheduler), handle, blocking_pool))
}
}
+9
View File
@@ -123,6 +123,15 @@ cfg_rt! {
_ => panic!("not a CurrentThread handle"),
}
}
cfg_rt_multi_thread! {
pub(crate) fn expect_multi_thread(&self) -> &Arc<multi_thread::Handle> {
match self {
Handle::MultiThread(handle) => handle,
_ => panic!("not a `MultiThread` handle"),
}
}
}
}
cfg_metrics! {
@@ -43,8 +43,7 @@ impl Handle {
}
pub(crate) fn shutdown(&self) {
// self.close();
todo!()
self.shared.close();
}
pub(super) fn bind_new_task<T>(me: &Arc<Self>, future: T, id: task::Id) -> JoinHandle<T::Output>
@@ -55,8 +54,7 @@ impl Handle {
let (handle, notified) = me.shared.owned.bind(future, me.clone(), id);
if let Some(notified) = notified {
// me.schedule_task(notified, false);
todo!()
me.shared.schedule_task(notified, false);
}
handle
@@ -84,8 +84,7 @@ impl Idle {
/// work.
pub(super) fn transition_worker_to_parked(
&self,
shared: &Shared,
worker: usize,
synced: &mut Synced,
is_searching: bool,
) -> bool {
/*
@@ -15,13 +15,10 @@ use self::idle::Idle;
mod stats;
pub(crate) use stats::Stats;
mod park;
pub(crate) use park::{Parker, Unparker};
pub(crate) mod queue;
mod worker;
pub(crate) use worker::{Context, Launch, Shared};
pub(crate) use worker::{Context, Shared};
cfg_taskdump! {
mod trace;
@@ -37,8 +34,8 @@ cfg_not_taskdump! {
pub(crate) use worker::block_in_place;
use crate::loom::sync::Arc;
use crate::runtime::{
self,
blocking,
driver::{self, Driver},
scheduler, Config,
@@ -61,18 +58,17 @@ impl MultiThread {
blocking_spawner: blocking::Spawner,
seed_generator: RngSeedGenerator,
config: Config,
) -> (MultiThread, Arc<Handle>, Launch) {
let parker = Parker::new(driver);
let (handle, launch) = worker::create(
) -> (MultiThread, runtime::Handle) {
let handle = worker::create(
size,
parker,
driver,
driver_handle,
blocking_spawner,
seed_generator,
config,
);
(MultiThread, handle, launch)
(MultiThread, handle)
}
/// Blocks the current thread waiting for the future to complete.
@@ -119,6 +119,11 @@ impl<T> Local<T> {
LOCAL_QUEUE_CAPACITY
}
/// Returns `true` if there are no entries in the queue
pub(crate) fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// Returns false if there are any entries in the queue
///
/// Separate to is_stealable so that refactors of is_stealable to "protect"
+288 -103
View File
@@ -56,16 +56,16 @@
//! the inject queue indefinitely. This would be a ref-count cycle and a memory
//! leak.
use crate::loom::sync::{Arc, Condvar, Mutex};
use crate::loom::sync::{Arc, Condvar, Mutex, MutexGuard};
use crate::runtime;
use crate::runtime::context;
use crate::runtime::scheduler::multi_thread::{
idle, queue, Counters, Handle, Idle, Overflow, Parker, Stats, TraceStatus, Unparker,
idle, queue, Counters, Handle, Idle, Overflow, Stats, TraceStatus,
};
use crate::runtime::scheduler::{inject, Defer, Lock};
use crate::runtime::scheduler::{self, inject, Defer, Lock};
use crate::runtime::task::OwnedTasks;
use crate::runtime::{
blocking, coop, driver, scheduler, task, Config, Driver, SchedulerMetrics, WorkerMetrics,
blocking, coop, driver, task, Config, Driver, SchedulerMetrics, WorkerMetrics,
};
use crate::util::atomic_cell::AtomicCell;
use crate::util::rand::{FastRand, RngSeedGenerator};
@@ -157,17 +157,6 @@ pub(crate) struct Shared {
/// Condition variable used to unblock waiting workers
condvar: Condvar,
/// Power's Tokio's I/O, timers, etc... the responsibility of polling the
/// driver is shared across workers.
driver: AtomicCell<Driver>,
/// Cores that have observed the shutdown signal
///
/// The core is **not** placed back in the worker to avoid it from being
/// stolen by a thread that was spawned as part of `block_in_place`.
#[allow(clippy::vec_box)] // we're moving an already-boxed value
shutdown_cores: Mutex<Vec<Box<Core>>>,
/// The number of cores that have observed the trace signal.
pub(super) trace_status: TraceStatus,
@@ -191,20 +180,27 @@ pub(crate) struct Synced {
/// Cores not currently assigned to workers
cores: Vec<Box<Core>>,
/// Cores that have observed the shutdown signal
///
/// The core is **not** placed back in the worker to avoid it from being
/// stolen by a thread that was spawned as part of `block_in_place`.
shutdown_cores: Vec<Box<Core>>,
/// Synchronized state for `Idle`.
pub(super) idle: idle::Synced,
/// Synchronized state for `Inject`.
pub(crate) inject: inject::Synced,
/// Power's Tokio's I/O, timers, etc... the responsibility of polling the
/// driver is shared across workers.
driver: Option<Box<Driver>>,
}
/// Used to communicate with a worker from other threads.
struct Remote {
/// Steals tasks from this worker.
pub(super) steal: queue::Steal<Arc<Handle>>,
/// Unparks the associated worker thread
unpark: Unparker,
}
/// Thread-local context
@@ -219,9 +215,6 @@ pub(crate) struct Context {
pub(crate) defer: Defer,
}
/// Starts the workers
pub(crate) struct Launch(Vec<Arc<Worker>>);
/// Running a task may consume the core. If the core is still available when
/// running the task completes, it is returned. Otherwise, the worker will need
/// to stop processing.
@@ -241,24 +234,20 @@ const MAX_LIFO_POLLS_PER_TICK: usize = 3;
pub(super) fn create(
size: usize,
park: Parker,
driver: Driver,
driver_handle: driver::Handle,
blocking_spawner: blocking::Spawner,
seed_generator: RngSeedGenerator,
config: Config,
) -> (Arc<Handle>, Launch) {
/*
) -> runtime::Handle {
let mut cores = Vec::with_capacity(size);
let mut remotes = Vec::with_capacity(size);
let mut parkers = Vec::with_capacity(size);
let mut worker_metrics = Vec::with_capacity(size);
// Create the local queues
for i in 0..size {
let (steal, run_queue) = queue::local();
let park = park.clone();
let unpark = park.unpark();
let metrics = WorkerMetrics::from_config(&config);
let stats = Stats::new(&metrics);
@@ -276,8 +265,7 @@ pub(super) fn create(
rand: FastRand::from_seed(config.seed_generator.next_seed()),
}));
parkers.push(park);
remotes.push(Remote { steal, unpark });
remotes.push(Remote { steal });
worker_metrics.push(metrics);
}
@@ -292,10 +280,13 @@ pub(super) fn create(
idle,
owned: OwnedTasks::new(),
synced: Mutex::new(Synced {
cores,
shutdown_cores: Vec::with_capacity(size),
idle: idle_synced,
inject: inject_synced,
driver: Some(Box::new(driver)),
}),
shutdown_cores: Mutex::new(vec![]),
condvar: Condvar::new(),
trace_status: TraceStatus::new(remotes_len),
config,
scheduler_metrics: SchedulerMetrics::new(),
@@ -307,19 +298,23 @@ pub(super) fn create(
seed_generator,
});
let mut launch = Launch(vec![]);
let rt_handle = runtime::Handle {
inner: scheduler::Handle::MultiThread(handle),
};
for (core, park) in cores.drain(..).zip(parkers.drain(..)) {
launch.0.push(Arc::new(Worker {
// Eagerly start worker threads
for _ in 0..size {
let handle = rt_handle.inner.expect_multi_thread();
let worker = Worker {
handle: handle.clone(),
park,
core: AtomicCell::new(Some(core)),
}));
};
handle
.blocking_spawner
.spawn_blocking(&rt_handle, move || run(worker));
}
(handle, launch)
*/
todo!()
rt_handle
}
#[track_caller]
@@ -436,17 +431,6 @@ where
todo!()
}
impl Launch {
pub(crate) fn launch(mut self) {
/*
for worker in self.0.drain(..) {
runtime::spawn_blocking(move || run(worker));
}
*/
todo!();
}
}
fn run(mut worker: Worker) {
struct AbortOnPanic;
@@ -488,12 +472,6 @@ fn run(mut worker: Worker) {
// Run the worker
worker.run(&cx);
/*
// This should always be an error. It only returns a `Result` to support
// using `?` to short circuit.
assert!(cx.run(core).is_err());
*/
// Check if there are any deferred tasks to notify. This can happen when
// the worker core is lost due to `block_in_place()` being called from
// within the task.
@@ -506,11 +484,13 @@ impl Worker {
fn run(&mut self, cx: &Context) {
// First, acquire a core. If no cores are available, the thread will
// block until one becomes available.
let mut core = self.acquire_core();
// Start as "processing" tasks as polling tasks from the local queue
// will be one of the first things we do.
core.stats.start_processing_scheduled_tasks();
//
// Acquiring a core will also pull tasks from the injection queue and
// run one, if found.
let mut core = match self.acquire_core(cx, self.shared().synced.lock()) {
Ok(core) => core,
Err(_) => return,
};
while !core.is_shutdown {
self.assert_lifo_enabled_is_correct(&core);
@@ -549,15 +529,16 @@ impl Worker {
Err(_) => return,
};
} else {
/*
// Wait for work
core = if !self.defer.is_empty() {
self.park_timeout(core, Some(Duration::from_millis(0)))
core = if !cx.defer.is_empty() {
// Just run maintenance
self.park_yield(core)
} else {
self.park(core)
match self.park(cx, core) {
Ok(core) => core,
Err(_) => return,
}
};
*/
todo!()
}
}
@@ -569,13 +550,49 @@ impl Worker {
todo!()
}
fn acquire_core(&mut self) -> Box<Core> {
let mut core = todo!();
fn acquire_core(&self, cx: &Context, mut synced: MutexGuard<Synced>) -> RunResult {
// Wait until a core is available, then exit the loop.
let mut core = loop {
if let Some(core) = synced.cores.pop() {
break core;
}
// TODO: not always the case
assert!(cx.defer.is_empty());
synced = self.shared().condvar.wait(synced).unwrap();
};
// Reset `lifo_enabled` here in case the core was previously stolen from
// a task that had the LIFO slot disabled.
self.reset_lifo_enabled(&mut core);
todo!()
// At this point, the local queue should be empty
debug_assert!(core.run_queue.is_empty());
// Update shutdown state while locked
core.is_shutdown = self.shared().inject.is_closed(&synced.inject);
if core.is_shutdown {
// Currently shutting down, don't do any more work
return Ok(core);
}
// TODO: don't hardcode 128
let n = core.run_queue.max_capacity() / 2;
let maybe_task = self.next_remote_task_batch(&mut synced, &mut core, n);
drop(synced);
// Start as "processing" tasks as polling tasks from the local queue
// will be one of the first things we do.
core.stats.start_processing_scheduled_tasks();
if let Some(task) = maybe_task {
self.run_task(cx, core, task)
} else {
Ok(core)
}
}
fn next_task(&self, core: &mut Core) -> Option<Notified> {
@@ -605,25 +622,8 @@ impl Worker {
core.run_queue.max_capacity() / 2,
);
// The worker is currently idle, pull a batch of work from the
// injection queue. We don't want to pull *all* the work so other
// workers can also get some.
let n = usize::min(
self.shared().inject.len() / self.shared().remotes.len() + 1,
cap,
);
let mut synced = self.shared().synced.lock();
// safety: passing in the correct `inject::Synced`.
let mut tasks = unsafe { self.shared().inject.pop_n(&mut synced.inject, n) };
// Pop the first task to return immedietly
let ret = tasks.next();
// Push the rest of the on the run queue
core.run_queue.push_back(tasks);
ret
self.next_remote_task_batch(&mut synced, core, cap)
}
}
@@ -633,10 +633,40 @@ impl Worker {
}
let mut synced = self.shared().synced.lock();
// safety: passing in correct `idle::Synced`
self.next_remote_task_synced(&mut synced)
}
fn next_remote_task_synced(&self, synced: &mut Synced) -> Option<Notified> {
// safety: we only have access to a valid `Synced` in this file.
unsafe { self.shared().inject.pop(&mut synced.inject) }
}
fn next_remote_task_batch(
&self,
synced: &mut Synced,
core: &mut Core,
max: usize,
) -> Option<Notified> {
// The worker is currently idle, pull a batch of work from the
// injection queue. We don't want to pull *all* the work so other
// workers can also get some.
let n = usize::min(
self.shared().inject.len() / self.shared().remotes.len() + 1,
max,
);
// safety: passing in the correct `inject::Synced`.
let mut tasks = unsafe { self.shared().inject.pop_n(&mut synced.inject, n) };
// Pop the first task to return immedietly
let ret = tasks.next();
// Push the rest of the on the run queue
core.run_queue.push_back(tasks);
ret
}
fn next_local_task(&self, core: &mut Core) -> Option<Notified> {
core.lifo_slot.take().or_else(|| core.run_queue.pop())
}
@@ -768,13 +798,6 @@ impl Worker {
core.stats.end_processing_scheduled_tasks();
/*
// Call `park` with a 0 timeout. This enables the I/O driver, timer, ...
// to run without actually putting the thread to sleep.
core = self.park_timeout(core, Some(Duration::from_millis(0)));
*/
todo!();
// Run regularly scheduled maintenance
self.maintenance(&mut core);
@@ -786,6 +809,15 @@ impl Worker {
/// Runs maintenance work such as checking the pool's state.
fn maintenance(&self, core: &mut Core) {
/*
// Call `park` with a 0 timeout. This enables the I/O driver, timer, ...
// to run without actually putting the thread to sleep.
core = self.park_timeout(core, Some(Duration::from_millis(0)));
*/
if true {
todo!();
}
core.stats.submit(&self.shared().worker_metrics[core.index]);
if !core.is_shutdown {
@@ -800,6 +832,57 @@ impl Worker {
}
}
fn park_yield(&self, mut core: Box<Core>) -> Box<Core> {
/*
// Call `park` with a 0 timeout. This enables the I/O driver, timer, ...
// to run without actually putting the thread to sleep.
core = self.park_timeout(core, Some(Duration::from_millis(0)));
*/
if true {
todo!();
}
self.maintenance(&mut core);
core
}
fn park(&self, cx: &Context, mut core: Box<Core>) -> RunResult {
if let Some(f) = &self.shared().config.before_park {
f();
}
if self.transition_to_parked(&mut core) {
debug_assert!(!core.is_shutdown);
debug_assert!(!core.is_traced);
core.stats.about_to_park();
core = self.do_park(cx, core)?;
} else {
// Just run maintenance and carry on
core = self.park_yield(core);
}
if let Some(f) = &self.shared().config.after_unpark {
f();
}
Ok(core)
}
fn do_park(&self, cx: &Context, mut core: Box<Core>) -> RunResult {
let mut synced = self.shared().synced.lock();
// Return `core` to shared
synced.cores.push(core);
if let Some(mut driver) = synced.driver.take() {
todo!()
} else {
synced = self.shared().condvar.wait(synced).unwrap();
self.acquire_core(cx, synced)
}
}
fn transition_to_searching(&self, core: &mut Core) -> bool {
if !core.is_searching {
core.is_searching = self.shared().idle.transition_worker_to_searching();
@@ -822,6 +905,39 @@ impl Worker {
}
}
/// Prepares the worker state for parking.
///
/// Returns true if the transition happened, false if there is work to do first.
fn transition_to_parked(&self, core: &mut Core) -> bool {
// Workers should not park if they have work to do
if core.lifo_slot.is_some() || core.run_queue.has_tasks() || core.is_traced {
return false;
}
// When the final worker transitions **out** of searching to parked, it
// must check all the queues one last time in case work materialized
// between the last work scan and transitioning out of searching.
let is_last_searcher = self
.shared()
.idle
.transition_worker_to_parked(todo!(), core.is_searching);
// The worker is no longer searching. Setting this is the local cache
// only.
core.is_searching = false;
if is_last_searcher {
// worker.handle.notify_if_work_pending();
todo!()
}
true
}
fn transition_from_parked(&self, core: &mut Core) -> bool {
todo!()
}
/// Signals all tasks to shut down, and waits for them to complete. Must run
/// before we enter the single-threaded phase of shutdown processing.
fn pre_shutdown(&self, core: &mut Core) {
@@ -836,28 +952,31 @@ impl Worker {
///
/// If all workers have reached this point, the final cleanup is performed.
fn shutdown_core(&self, core: Box<Core>) {
let mut cores = self.shared().shutdown_cores.lock();
cores.push(core);
let mut synced = self.shared().synced.lock();
synced.cores.push(core);
if cores.len() != self.shared().remotes.len() {
if synced.cores.len() != self.shared().remotes.len() {
return;
}
debug_assert!(self.shared().owned.is_empty());
for mut core in cores.drain(..) {
for mut core in synced.cores.drain(..) {
// Drain tasks from the local queue
while self.next_local_task(&mut core).is_some() {}
}
// Shutdown the driver
let mut driver = self.shared().driver.take().expect("driver missing");
let mut driver = synced.driver.take().expect("driver missing");
driver.shutdown(&self.handle.driver);
// Drain the injection queue
//
// We already shut down every task, so we can simply drop the tasks.
while let Some(task) = self.next_remote_task() {
// We already shut down every task, so we can simply drop the tasks. We
// cannot call `next_remote_task()` because we already hold the lock.
//
// safety: passing in correct `idle::Synced`
while let Some(task) = self.next_remote_task_synced(&mut synced) {
drop(task);
}
}
@@ -898,6 +1017,65 @@ impl Context {
}
impl Shared {
pub(super) fn schedule_task(&self, task: Notified, is_yield: bool) {
/*
with_current(|maybe_cx| {
if let Some(cx) = maybe_cx {
// Make sure the task is part of the **current** scheduler.
if self.ptr_eq(&cx.worker.handle) {
// And the current thread still holds a core
if let Some(core) = cx.core.borrow_mut().as_mut() {
self.schedule_local(core, task, is_yield);
return;
}
}
}
// Otherwise, use the inject queue.
self.push_remote_task(task);
self.notify_parked_remote();
})
*/
todo!()
}
fn schedule_local(&self, core: &mut Core, task: Notified, is_yield: bool) {
/*
core.stats.inc_local_schedule_count();
// Spawning from the worker thread. If scheduling a "yield" then the
// task must always be pushed to the back of the queue, enabling other
// tasks to be executed. If **not** a yield, then there is more
// flexibility and the task may go to the front of the queue.
let should_notify = if is_yield || !core.lifo_enabled {
core.run_queue
.push_back_or_overflow(task, self, &mut core.stats);
true
} else {
// Push to the LIFO slot
let prev = core.lifo_slot.take();
let ret = prev.is_some();
if let Some(prev) = prev {
core.run_queue
.push_back_or_overflow(prev, self, &mut core.stats);
}
core.lifo_slot = Some(task);
ret
};
// Only notify if not currently parked. If `park` is `None`, then the
// scheduling is from a resource driver. As notifications often come in
// batches, the notification is delayed until the park is complete.
if should_notify && core.park.is_some() {
self.notify_parked_local();
}
*/
todo!()
}
fn notify_parked_local(&self) {
/*
super::counters::inc_num_inc_notify_local();
@@ -928,6 +1106,13 @@ impl Shared {
self.inject.push(&mut synced.inject, task);
}
}
pub(super) fn close(&self) {
if self.inject.close(&mut self.synced.lock().inject) {
// self.notify_all();
todo!()
}
}
}
impl Overflow<Arc<Handle>> for Shared {