// Metrics about the pool.
metrics: SpawnerMetrics,
}
struct Shared {
queue: VecDeque<Task>,
num_notify: u32,
shutdown: bool,
shutdown_tx: Option<shutdown::Sender>, /// Prior to shutdown, we clean up `JoinHandles` by having each timed-out /// thread join on the previous timed-out thread. This is not strictly /// necessary but helps avoid Valgrind false positives, see /// <https://github.com/tokio-rs/tokio/commit/646fbae76535e397ef79dbcaacb945d4c829f666> /// for more information.
last_exiting_thread: Option<thread::JoinHandle<()>>, /// This holds the `JoinHandles` for all running threads; on shutdown, the thread /// calling shutdown handles joining on these.
worker_threads: HashMap<usize, thread::JoinHandle<()>>, /// This is a counter used to iterate `worker_threads` in a consistent order (for loom's /// benefit).
worker_thread_index: usize,
}
pub(crate) enum SpawnError { /// Pool is shutting down and the task was not scheduled
ShuttingDown, /// There are no worker threads available to take the task /// and the OS failed to spawn a new one
NoThreads(io::Error),
}
impl From<SpawnError> for io::Error { fn from(e: SpawnError) -> Self { match e {
SpawnError::ShuttingDown => {
io::Error::new(io::ErrorKind::Other, "blocking pool shutting down")
}
SpawnError::NoThreads(e) => e,
}
}
}
/// Runs the provided function on an executor dedicated to blocking operations. /// Tasks will be scheduled as non-mandatory, meaning they may not get executed /// in case of runtime shutdown. #[track_caller] #[cfg_attr(target_os = "wasi", allow(dead_code))] pub(crate) fn spawn_blocking<F, R>(func: F) -> JoinHandle<R> where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{ let rt = Handle::current();
rt.spawn_blocking(func)
}
cfg_fs! { #[cfg_attr(any(
all(loom, not(test)), // the function is covered by loom tests
test
), allow(dead_code))] /// Runs the provided function on an executor dedicated to blocking /// operations. Tasks will be scheduled as mandatory, meaning they are /// guaranteed to run unless a shutdown is already taking place. In case a /// shutdown is already taking place, `None` will be returned. pub(crate) fn spawn_mandatory_blocking<F, R>(func: F) -> Option<JoinHandle<R>> where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{ let rt = Handle::current();
rt.inner.blocking_spawner().spawn_mandatory_blocking(&rt, func)
}
}
// The function can be called multiple times. First, by explicitly // calling `shutdown` then by the drop handler calling `shutdown`. This // prevents shutting down twice. if shared.shutdown { return;
}
let last_exited_thread = std::mem::take(&mut shared.last_exiting_thread); let workers = std::mem::take(&mut shared.worker_threads);
drop(shared);
ifself.shutdown_rx.wait(timeout) { let _ = last_exited_thread.map(thread::JoinHandle::join);
// Loom requires that execution be deterministic, so sort by thread ID before joining. // (HashMaps use a randomly-seeded hash function, so the order is nondeterministic) #[cfg(loom)] let workers: Vec<(usize, thread::JoinHandle<()>)> = { letmut workers: Vec<_> = workers.into_iter().collect();
workers.sort_by_key(|(id, _)| *id);
workers
};
for (_id, handle) in workers { let _ = handle.join();
}
}
}
}
impl Drop for BlockingPool { fn drop(&mutself) { self.shutdown(None);
}
}
match spawn_result {
Ok(()) => join_handle, // Compat: do not panic here, return the join_handle even though it will never resolve
Err(SpawnError::ShuttingDown) => join_handle,
Err(SpawnError::NoThreads(e)) => {
panic!("OS can't spawn worker thread: {}", e)
}
}
}
cfg_fs! { #[track_caller] #[cfg_attr(any(
all(loom, not(test)), // the function is covered by loom tests
test
), allow(dead_code))] pub(crate) fn spawn_mandatory_blocking<F, R>(&self, rt: &Handle, func: F) -> Option<JoinHandle<R>> where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{ let (join_handle, spawn_result) = if cfg!(debug_assertions) && std::mem::size_of::<F>() > BOX_FUTURE_THRESHOLD { self.spawn_blocking_inner( Box::new(func),
Mandatory::Mandatory,
None,
rt,
)
} else { self.spawn_blocking_inner(
func,
Mandatory::Mandatory,
None,
rt,
)
};
if shared.shutdown { // Shutdown the task: it's fine to shutdown this task (even if // mandatory) because it was scheduled after the shutdown of the // runtime began.
task.task.shutdown();
// no need to even push this task; it would never get picked up return Err(SpawnError::ShuttingDown);
}
ifself.inner.metrics.num_idle_threads() == 0 { // No threads are able to process the task.
ifself.inner.metrics.num_threads() == self.inner.thread_cap { // At max number of threads
} else {
assert!(shared.shutdown_tx.is_some()); let shutdown_tx = shared.shutdown_tx.clone();
iflet Some(shutdown_tx) = shutdown_tx { let id = shared.worker_thread_index;
matchself.spawn_thread(shutdown_tx, rt, id) {
Ok(handle) => { self.inner.metrics.inc_num_threads();
shared.worker_thread_index += 1;
shared.worker_threads.insert(id, handle);
}
Err(ref e) if is_temporary_os_thread_error(e)
&& self.inner.metrics.num_threads() > 0 =>
{ // OS temporarily failed to spawn a new thread. // The task will be picked up eventually by a currently // busy thread.
}
Err(e) => { // The OS refused to spawn the thread and there is no thread // to pick up the task that has just been pushed to the queue. return Err(SpawnError::NoThreads(e));
}
}
}
}
} else { // Notify an idle worker thread. The notification counter // is used to count the needed amount of notifications // exactly. Thread libraries may generate spurious // wakeups, this counter is used to keep us in a // consistent state. self.inner.metrics.dec_num_idle_threads();
shared.num_notify += 1; self.inner.condvar.notify_one();
}
builder.spawn(move || { // Only the reference should be moved into the closure let _enter = rt.enter();
rt.inner.blocking_spawner().inner.run(id);
drop(shutdown_tx);
})
}
}
while !shared.shutdown { let lock_result = self.condvar.wait_timeout(shared, self.keep_alive).unwrap();
shared = lock_result.0; let timeout_result = lock_result.1;
if shared.num_notify != 0 { // We have received a legitimate wakeup, // acknowledge it by decrementing the counter // and transition to the BUSY state.
shared.num_notify -= 1; break;
}
// Even if the condvar "timed out", if the pool is entering the // shutdown phase, we want to perform the cleanup logic. if !shared.shutdown && timeout_result.timed_out() { // We'll join the prior timed-out thread's JoinHandle after dropping the lock. // This isn't done when shutting down, because the thread calling shutdown will // handle joining everything. let my_handle = shared.worker_threads.remove(&worker_thread_id);
join_on_thread = std::mem::replace(&mut shared.last_exiting_thread, my_handle);
break'main;
}
// Spurious wakeup detected, go back to sleep.
}
if shared.shutdown { // Drain the queue whilelet Some(task) = shared.queue.pop_front() { self.metrics.dec_queue_depth();
drop(shared);
task.shutdown_or_run_if_mandatory();
shared = self.shared.lock();
}
// Work was produced, and we "took" it (by decrementing num_notify). // This means that num_idle was decremented once for our wakeup. // But, since we are exiting, we need to "undo" that, as we'll stay idle. self.metrics.inc_num_idle_threads(); // NOTE: Technically we should also do num_notify++ and notify again, // but since we're shutting down anyway, that won't be necessary. break;
}
}
// Thread exit self.metrics.dec_num_threads();
// num_idle should now be tracked exactly, panic // with a descriptive message if it is not the // case. let prev_idle = self.metrics.dec_num_idle_threads();
assert!(
prev_idle >= self.metrics.num_idle_threads(), "num_idle_threads underflowed on thread exit"
);
if shared.shutdown && self.metrics.num_threads() == 0 { self.condvar.notify_one();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.