mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-22 00:00:11 +02:00
task: reduce duplication between basic_scheduler and LocalSet
This commit factors out the common task queue logic in the basic scheduler runtime and the `LocalSet` struct in `tokio::task`. This is because as more work was done on the `LocalSet`, it has gotten closer and closer to the basic scheduler in behavior, and factoring out the shared code reduces the risk of errors caused by `LocalSet` not doing something that the basic scheduler does. In particular, I noticed the basic scheduler has a flag that indicates the remote queue has been closed, which is set when dropping the scheduler. This prevents tasks from being added after the scheduler has started shutting down, stopping a potential task leak. Rather than duplicating this code in `LocalSet`, I thought it was probably better to factor it out into a shared type. There are a few cases where there are small differences in behavior, though, so there is still a need for separate types implemented _using_ the new `Queues` struct. However, it should cover most of the identical code. Signed-off-by: Eliza Weisman <[email protected]>
This commit is contained in:
@@ -7,7 +7,7 @@ use std::fmt;
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use std::task::{RawWaker, RawWakerVTable, Waker};
|
use std::task::{RawWaker, RawWakerVTable, Waker};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -29,15 +29,13 @@ pub(crate) struct Spawner {
|
|||||||
scheduler: Arc<SchedulerPriv>,
|
scheduler: Arc<SchedulerPriv>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The scheduler component.
|
pub(crate) struct Queues<S: 'static> {
|
||||||
pub(super) struct SchedulerPriv {
|
|
||||||
/// List of all active tasks spawned onto this executor.
|
/// List of all active tasks spawned onto this executor.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Must only be accessed from the primary thread
|
/// Must only be accessed from the primary thread
|
||||||
owned_tasks: UnsafeCell<task::OwnedList<Self>>,
|
owned_tasks: UnsafeCell<task::OwnedList<S>>,
|
||||||
|
|
||||||
/// Local run queue.
|
/// Local run queue.
|
||||||
///
|
///
|
||||||
/// Tasks notified from the current thread are pushed into this queue.
|
/// Tasks notified from the current thread are pushed into this queue.
|
||||||
@@ -46,16 +44,20 @@ pub(super) struct SchedulerPriv {
|
|||||||
///
|
///
|
||||||
/// References should not be handed out. Only call `push` / `pop` functions.
|
/// References should not be handed out. Only call `push` / `pop` functions.
|
||||||
/// Only call from the owning thread.
|
/// Only call from the owning thread.
|
||||||
local_queue: UnsafeCell<VecDeque<Task<SchedulerPriv>>>,
|
local_queue: UnsafeCell<VecDeque<Task<S>>>,
|
||||||
|
|
||||||
/// Remote run queue.
|
/// Remote run queue.
|
||||||
///
|
///
|
||||||
/// Tasks notified from another thread are pushed into this queue.
|
/// Tasks notified from another thread are pushed into this queue.
|
||||||
remote_queue: Mutex<RemoteQueue>,
|
remote_queue: Mutex<RemoteQueue<S>>,
|
||||||
|
|
||||||
/// Tasks pending drop
|
/// Tasks pending drop
|
||||||
pending_drop: task::TransferStack<Self>,
|
pending_drop: task::TransferStack<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scheduler component.
|
||||||
|
pub(super) struct SchedulerPriv {
|
||||||
|
queues: Queues<Self>,
|
||||||
/// Unpark the blocked thread
|
/// Unpark the blocked thread
|
||||||
unpark: Box<dyn Unpark>,
|
unpark: Box<dyn Unpark>,
|
||||||
}
|
}
|
||||||
@@ -73,10 +75,9 @@ struct LocalState<P> {
|
|||||||
park: P,
|
park: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub(crate) struct RemoteQueue<S: 'static> {
|
||||||
struct RemoteQueue {
|
|
||||||
/// FIFO list of tasks
|
/// FIFO list of tasks
|
||||||
queue: VecDeque<Task<SchedulerPriv>>,
|
queue: VecDeque<Task<S>>,
|
||||||
|
|
||||||
/// `true` when a task can be pushed into the queue, false otherwise.
|
/// `true` when a task can be pushed into the queue, false otherwise.
|
||||||
open: bool,
|
open: bool,
|
||||||
@@ -85,9 +86,6 @@ struct RemoteQueue {
|
|||||||
/// Max number of tasks to poll per tick.
|
/// Max number of tasks to poll per tick.
|
||||||
const MAX_TASKS_PER_TICK: usize = 61;
|
const MAX_TASKS_PER_TICK: usize = 61;
|
||||||
|
|
||||||
/// How often to check the remote queue first
|
|
||||||
const CHECK_REMOTE_INTERVAL: u8 = 13;
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static ACTIVE: Cell<*const SchedulerPriv> = Cell::new(ptr::null())
|
static ACTIVE: Cell<*const SchedulerPriv> = Cell::new(ptr::null())
|
||||||
}
|
}
|
||||||
@@ -101,13 +99,7 @@ where
|
|||||||
|
|
||||||
BasicScheduler {
|
BasicScheduler {
|
||||||
scheduler: Arc::new(SchedulerPriv {
|
scheduler: Arc::new(SchedulerPriv {
|
||||||
owned_tasks: UnsafeCell::new(task::OwnedList::new()),
|
queues: Queues::new(),
|
||||||
local_queue: UnsafeCell::new(VecDeque::with_capacity(64)),
|
|
||||||
remote_queue: Mutex::new(RemoteQueue {
|
|
||||||
queue: VecDeque::with_capacity(64),
|
|
||||||
open: true,
|
|
||||||
}),
|
|
||||||
pending_drop: task::TransferStack::new(),
|
|
||||||
unpark: Box::new(unpark),
|
unpark: Box::new(unpark),
|
||||||
}),
|
}),
|
||||||
local: LocalState { tick: 0, park },
|
local: LocalState { tick: 0, park },
|
||||||
@@ -155,9 +147,7 @@ where
|
|||||||
|
|
||||||
// Track the current scheduler
|
// Track the current scheduler
|
||||||
let _guard = ACTIVE.with(|cell| {
|
let _guard = ACTIVE.with(|cell| {
|
||||||
let guard = Guard {
|
let guard = Guard { old: cell.get() };
|
||||||
old: cell.get(),
|
|
||||||
};
|
|
||||||
|
|
||||||
cell.set(scheduler as *const SchedulerPriv);
|
cell.set(scheduler as *const SchedulerPriv);
|
||||||
|
|
||||||
@@ -188,7 +178,9 @@ where
|
|||||||
scheduler.tick(local);
|
scheduler.tick(local);
|
||||||
|
|
||||||
// Maintenance work
|
// Maintenance work
|
||||||
scheduler.drain_pending_drop();
|
unsafe {
|
||||||
|
scheduler.queues.drain_pending_drop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -216,6 +208,221 @@ impl Spawner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> Queues<S>
|
||||||
|
where
|
||||||
|
S: Schedule + 'static,
|
||||||
|
{
|
||||||
|
pub(crate) const INITIAL_CAPACITY: usize = 64;
|
||||||
|
|
||||||
|
/// How often to check the remote queue first
|
||||||
|
pub(crate) const CHECK_REMOTE_INTERVAL: u8 = 13;
|
||||||
|
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
owned_tasks: UnsafeCell::new(task::OwnedList::new()),
|
||||||
|
local_queue: UnsafeCell::new(VecDeque::with_capacity(Self::INITIAL_CAPACITY)),
|
||||||
|
pending_drop: task::TransferStack::new(),
|
||||||
|
remote_queue: Mutex::new(RemoteQueue {
|
||||||
|
queue: VecDeque::with_capacity(Self::INITIAL_CAPACITY),
|
||||||
|
open: true,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new task to the scheduler.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn add_task(&self, task: &Task<S>) {
|
||||||
|
(*self.owned_tasks.get()).insert(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a task to the local queue.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn push_local(&self, task: Task<S>) {
|
||||||
|
(*self.local_queue.get()).push_back(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push a task to the local queue.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn release_local(&self, task: &Task<S>) {
|
||||||
|
(*self.owned_tasks.get()).remove(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lock the remote queue, returning a `MutexGuard`.
|
||||||
|
///
|
||||||
|
/// This can be used to push to the remote queue and perform other
|
||||||
|
/// operations while holding the lock.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the remote queue mutex is poisoned.
|
||||||
|
pub(crate) fn remote(&self) -> MutexGuard<'_, RemoteQueue<S>> {
|
||||||
|
self.remote_queue
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock remote queue")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release a task from outside of the thread that owns the scheduler.
|
||||||
|
///
|
||||||
|
/// This simply pushes the task to the pending drop queue.
|
||||||
|
pub(crate) fn release_remote(&self, task: Task<S>) {
|
||||||
|
self.pending_drop.push(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next task from the remote *or* local queue.
|
||||||
|
///
|
||||||
|
/// Typically, this checks the local queue before the remote queue, and only
|
||||||
|
/// checks the remote queue if the local queue is empty. However, to avoid
|
||||||
|
/// starving the remote queue, it is checked first every
|
||||||
|
/// `CHECK_REMOTE_INTERVAL` ticks.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn next_task(&self, tick: u8) -> Option<Task<S>> {
|
||||||
|
if 0 == tick % Self::CHECK_REMOTE_INTERVAL {
|
||||||
|
self.next_remote_task().or_else(|| self.next_local_task())
|
||||||
|
} else {
|
||||||
|
self.next_local_task().or_else(|| self.next_remote_task())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next task from the local queue.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn next_local_task(&self) -> Option<Task<S>> {
|
||||||
|
(*self.local_queue.get()).pop_front()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next task from the remote queue.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// - If the mutex around the remote queue is poisoned _and_ the current
|
||||||
|
/// thread is not already panicking. This is safe to call in a `Drop` impl.
|
||||||
|
pub(crate) fn next_remote_task(&self) -> Option<Task<S>> {
|
||||||
|
// there is no semantic information in the `PoisonError`, and it
|
||||||
|
// doesn't implement `Debug`, but clippy thinks that it's bad to
|
||||||
|
// match all errors here...
|
||||||
|
#[allow(clippy::match_wild_err_arm)]
|
||||||
|
let mut lock = match self.remote_queue.lock() {
|
||||||
|
// If the lock is poisoned, but the thread is already panicking,
|
||||||
|
// avoid a double panic. This is necessary since `next_task` (which
|
||||||
|
// calls `next_remote_task`) can be called in the `Drop` impl.
|
||||||
|
Err(_) if std::thread::panicking() => return None,
|
||||||
|
Err(_) => panic!("mutex poisoned"),
|
||||||
|
Ok(lock) => lock,
|
||||||
|
};
|
||||||
|
lock.queue.pop_front()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if any owned tasks are still bound to this scheduler.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn has_tasks_remaining(&self) -> bool {
|
||||||
|
!(*self.owned_tasks.get()).is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain any tasks that have previously been released from other threads.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn drain_pending_drop(&self) {
|
||||||
|
for task in self.pending_drop.drain() {
|
||||||
|
(*self.owned_tasks.get()).remove(&task);
|
||||||
|
drop(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shut down the scheduler's owned task list.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This *must* be called only from the thread that owns the scheduler.
|
||||||
|
pub(crate) unsafe fn shutdown(&self) {
|
||||||
|
(*self.owned_tasks.get()).shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain the remote queue, and shut down its tasks.
|
||||||
|
///
|
||||||
|
/// This closes the remote queue. Any additional tasks added to it will be
|
||||||
|
/// shut down instead.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// - If the mutex around the remote queue is poisoned _and_ the current
|
||||||
|
/// thread is not already panicking. This is safe to call in a `Drop` impl.
|
||||||
|
pub(crate) fn close_remote(&self) {
|
||||||
|
#[allow(clippy::match_wild_err_arm)]
|
||||||
|
let mut lock = match self.remote_queue.lock() {
|
||||||
|
// If the lock is poisoned, but the thread is already panicking,
|
||||||
|
// avoid a double panic. This is necessary since this fn can be
|
||||||
|
// called in a drop impl.
|
||||||
|
Err(_) if std::thread::panicking() => return,
|
||||||
|
Err(_) => panic!("mutex poisoned"),
|
||||||
|
Ok(lock) => lock,
|
||||||
|
};
|
||||||
|
lock.open = false;
|
||||||
|
|
||||||
|
while let Some(task) = lock.queue.pop_front() {
|
||||||
|
task.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> fmt::Debug for Queues<S> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.debug_struct("Queues")
|
||||||
|
.field("owned_tasks", &self.owned_tasks)
|
||||||
|
.field("remote_queue", &self.remote_queue)
|
||||||
|
.field("local_queue", &self.local_queue)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl RemoteQueue ===
|
||||||
|
|
||||||
|
impl<S> RemoteQueue<S>
|
||||||
|
where
|
||||||
|
S: Schedule,
|
||||||
|
{
|
||||||
|
/// Schedule a remote task.
|
||||||
|
///
|
||||||
|
/// If the queue is open to accept new tasks, the task is pushed to the back
|
||||||
|
/// of the queue. Otherwise, if the queue is closed (the scheduler is
|
||||||
|
/// shutting down), the new task
|
||||||
|
/// will be shut down immediately.
|
||||||
|
pub(crate) fn schedule(&mut self, task: Task<S>) {
|
||||||
|
if self.open {
|
||||||
|
self.queue.push_back(task);
|
||||||
|
} else {
|
||||||
|
task.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> fmt::Debug for RemoteQueue<S> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt.debug_struct("RemoteQueue")
|
||||||
|
.field("queue", &self.queue)
|
||||||
|
.field("open", &self.open)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === impl SchedulerPriv ===
|
||||||
|
|
||||||
impl SchedulerPriv {
|
impl SchedulerPriv {
|
||||||
fn tick(&self, local: &mut LocalState<impl Park>) {
|
fn tick(&self, local: &mut LocalState<impl Park>) {
|
||||||
for _ in 0..MAX_TASKS_PER_TICK {
|
for _ in 0..MAX_TASKS_PER_TICK {
|
||||||
@@ -225,7 +432,7 @@ impl SchedulerPriv {
|
|||||||
// Increment the tick
|
// Increment the tick
|
||||||
local.tick = tick.wrapping_add(1);
|
local.tick = tick.wrapping_add(1);
|
||||||
|
|
||||||
let task = match self.next_task(tick) {
|
let task = match unsafe { self.queues.next_task(tick) } {
|
||||||
Some(task) => task,
|
Some(task) => task,
|
||||||
None => {
|
None => {
|
||||||
local.park.park().ok().expect("failed to park");
|
local.park.park().ok().expect("failed to park");
|
||||||
@@ -235,7 +442,7 @@ impl SchedulerPriv {
|
|||||||
|
|
||||||
if let Some(task) = task.run(&mut || Some(self.into())) {
|
if let Some(task) = task.run(&mut || Some(self.into())) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.schedule_local(task);
|
self.queues.push_local(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,15 +454,6 @@ impl SchedulerPriv {
|
|||||||
.expect("failed to park");
|
.expect("failed to park");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drain_pending_drop(&self) {
|
|
||||||
for task in self.pending_drop.drain() {
|
|
||||||
unsafe {
|
|
||||||
(*self.owned_tasks.get()).remove(&task);
|
|
||||||
}
|
|
||||||
drop(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Must be called from the same thread that holds the `BasicScheduler`
|
/// Must be called from the same thread that holds the `BasicScheduler`
|
||||||
@@ -266,63 +464,36 @@ impl SchedulerPriv {
|
|||||||
F::Output: Send + 'static,
|
F::Output: Send + 'static,
|
||||||
{
|
{
|
||||||
let (task, handle) = task::joinable(future);
|
let (task, handle) = task::joinable(future);
|
||||||
self.schedule_local(task);
|
self.queues.push_local(task);
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn schedule_local(&self, task: Task<Self>) {
|
|
||||||
(*self.local_queue.get()).push_back(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_task(&self, tick: u8) -> Option<Task<Self>> {
|
|
||||||
if 0 == tick % CHECK_REMOTE_INTERVAL {
|
|
||||||
self.next_remote_task().or_else(|| self.next_local_task())
|
|
||||||
} else {
|
|
||||||
self.next_local_task().or_else(|| self.next_remote_task())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_local_task(&self) -> Option<Task<Self>> {
|
|
||||||
unsafe { (*self.local_queue.get()).pop_front() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_remote_task(&self) -> Option<Task<Self>> {
|
|
||||||
self.remote_queue.lock().unwrap().queue.pop_front()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Schedule for SchedulerPriv {
|
impl Schedule for SchedulerPriv {
|
||||||
fn bind(&self, task: &Task<Self>) {
|
fn bind(&self, task: &Task<Self>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.owned_tasks.get()).insert(task);
|
self.queues.add_task(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release(&self, task: Task<Self>) {
|
fn release(&self, task: Task<Self>) {
|
||||||
self.pending_drop.push(task);
|
self.queues.release_remote(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release_local(&self, task: &Task<Self>) {
|
fn release_local(&self, task: &Task<Self>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.owned_tasks.get()).remove(task);
|
self.queues.release_local(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schedule(&self, task: Task<Self>) {
|
fn schedule(&self, task: Task<Self>) {
|
||||||
let is_current = ACTIVE.with(|cell| {
|
let is_current = ACTIVE.with(|cell| cell.get() == self as *const SchedulerPriv);
|
||||||
cell.get() == self as *const SchedulerPriv
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_current {
|
if is_current {
|
||||||
unsafe { self.schedule_local(task) };
|
unsafe { self.queues.push_local(task) };
|
||||||
} else {
|
} else {
|
||||||
let mut lock = self.remote_queue.lock().unwrap();
|
let mut lock = self.queues.remote();
|
||||||
|
lock.schedule(task);
|
||||||
if lock.open {
|
|
||||||
lock.queue.push_back(task);
|
|
||||||
} else {
|
|
||||||
task.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// while locked, call unpark
|
// while locked, call unpark
|
||||||
self.unpark.unpark();
|
self.unpark.unpark();
|
||||||
@@ -340,38 +511,37 @@ where
|
|||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Close the remote queue
|
// Close the remote queue
|
||||||
let mut lock = self.scheduler.remote_queue.lock().unwrap();
|
self.scheduler.queues.close_remote();
|
||||||
lock.open = false;
|
|
||||||
|
|
||||||
while let Some(task) = lock.queue.pop_front() {
|
|
||||||
task.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(lock);
|
|
||||||
|
|
||||||
// Drain all local tasks
|
// Drain all local tasks
|
||||||
while let Some(task) = self.scheduler.next_local_task() {
|
while let Some(task) = unsafe { self.scheduler.queues.next_local_task() } {
|
||||||
task.shutdown();
|
task.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release owned tasks
|
// Release owned tasks
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.scheduler.owned_tasks.get()).shutdown();
|
self.scheduler.queues.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scheduler.drain_pending_drop();
|
unsafe {
|
||||||
|
self.scheduler.queues.drain_pending_drop();
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until all tasks have been released.
|
// Wait until all tasks have been released.
|
||||||
while unsafe { !(*self.scheduler.owned_tasks.get()).is_empty() } {
|
while unsafe { self.scheduler.queues.has_tasks_remaining() } {
|
||||||
self.local.park.park().ok().expect("park failed");
|
self.local.park.park().ok().expect("park failed");
|
||||||
self.scheduler.drain_pending_drop();
|
unsafe {
|
||||||
|
self.scheduler.queues.drain_pending_drop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for SchedulerPriv {
|
impl fmt::Debug for SchedulerPriv {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt.debug_struct("Scheduler").finish()
|
fmt.debug_struct("Scheduler")
|
||||||
|
.field("queues", &self.queues)
|
||||||
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
cfg_rt_core! {
|
cfg_rt_core! {
|
||||||
mod basic_scheduler;
|
pub(crate) mod basic_scheduler;
|
||||||
use basic_scheduler::BasicScheduler;
|
use basic_scheduler::BasicScheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+32
-108
@@ -1,15 +1,13 @@
|
|||||||
//! Runs `!Send` futures on the current thread.
|
//! Runs `!Send` futures on the current thread.
|
||||||
|
use crate::runtime::basic_scheduler::Queues;
|
||||||
use crate::sync::AtomicWaker;
|
use crate::sync::AtomicWaker;
|
||||||
use crate::task::{self, JoinHandle, Schedule, Task, TransferStack};
|
use crate::task::{self, JoinHandle, Schedule, Task};
|
||||||
|
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::Cell;
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fmt;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::ptr::{self, NonNull};
|
use std::ptr::{self, NonNull};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
@@ -82,33 +80,12 @@ cfg_rt_util! {
|
|||||||
scheduler: Rc<Scheduler>,
|
scheduler: Rc<Scheduler>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Scheduler {
|
struct Scheduler {
|
||||||
/// List of all active tasks spawned onto this executor.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// Must only be accessed from the primary thread
|
|
||||||
tasks: UnsafeCell<task::OwnedList<Scheduler>>,
|
|
||||||
|
|
||||||
/// Local run local_queue.
|
|
||||||
///
|
|
||||||
/// Tasks notified from the current thread are pushed into this queue.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// References should not be handed out. Only call `push` / `pop` functions.
|
|
||||||
/// Only call from the owning thread.
|
|
||||||
local_queue: UnsafeCell<VecDeque<Task<Scheduler>>>,
|
|
||||||
|
|
||||||
tick: Cell<u8>,
|
tick: Cell<u8>,
|
||||||
|
|
||||||
/// Remote run queue.
|
queues: Queues<Self>,
|
||||||
///
|
|
||||||
/// Tasks notified from another thread are pushed into this queue.
|
|
||||||
remote_queue: Mutex<VecDeque<Task<Scheduler>>>,
|
|
||||||
|
|
||||||
/// Tasks pending drop
|
|
||||||
pending_drop: TransferStack<Self>,
|
|
||||||
|
|
||||||
/// Used to notify the `LocalFuture` when a task in the local task set is
|
/// Used to notify the `LocalFuture` when a task in the local task set is
|
||||||
/// notified.
|
/// notified.
|
||||||
@@ -167,11 +144,12 @@ cfg_rt_util! {
|
|||||||
let current = current
|
let current = current
|
||||||
.get()
|
.get()
|
||||||
.expect("`spawn_local` called from outside of a local::LocalSet!");
|
.expect("`spawn_local` called from outside of a local::LocalSet!");
|
||||||
|
let (task, handle) = task::joinable_local(future);
|
||||||
unsafe {
|
unsafe {
|
||||||
let (task, handle) = task::joinable_local(future);
|
current.as_ref().queues.push_local(task);
|
||||||
current.as_ref().schedule_local(task);
|
|
||||||
handle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,9 +157,6 @@ cfg_rt_util! {
|
|||||||
/// Max number of tasks to poll per tick.
|
/// Max number of tasks to poll per tick.
|
||||||
const MAX_TASKS_PER_TICK: usize = 61;
|
const MAX_TASKS_PER_TICK: usize = 61;
|
||||||
|
|
||||||
/// How often to check the remote queue first
|
|
||||||
const CHECK_REMOTE_INTERVAL: u8 = 13;
|
|
||||||
|
|
||||||
impl LocalSet {
|
impl LocalSet {
|
||||||
/// Returns a new local task set.
|
/// Returns a new local task set.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -234,7 +209,7 @@ impl LocalSet {
|
|||||||
unsafe {
|
unsafe {
|
||||||
// This is safe: since `LocalSet` is not Send or Sync, this is
|
// This is safe: since `LocalSet` is not Send or Sync, this is
|
||||||
// always being called from the local thread.
|
// always being called from the local thread.
|
||||||
self.scheduler.schedule_local(task);
|
self.scheduler.queues.push_local(task);
|
||||||
}
|
}
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
@@ -341,31 +316,32 @@ impl Schedule for Scheduler {
|
|||||||
fn bind(&self, task: &Task<Self>) {
|
fn bind(&self, task: &Task<Self>) {
|
||||||
assert!(self.is_current());
|
assert!(self.is_current());
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.tasks.get()).insert(task);
|
self.queues.add_task(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release(&self, task: Task<Self>) {
|
fn release(&self, task: Task<Self>) {
|
||||||
// This will be called when dropping the local runtime.
|
// This will be called when dropping the local runtime.
|
||||||
self.pending_drop.push(task);
|
self.queues.release_remote(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn release_local(&self, task: &Task<Self>) {
|
fn release_local(&self, task: &Task<Self>) {
|
||||||
debug_assert!(self.is_current());
|
debug_assert!(self.is_current());
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.tasks.get()).remove(task);
|
self.queues.release_local(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn schedule(&self, task: Task<Self>) {
|
fn schedule(&self, task: Task<Self>) {
|
||||||
if self.is_current() {
|
if self.is_current() {
|
||||||
unsafe {
|
unsafe { self.queues.push_local(task) };
|
||||||
self.schedule_local(task);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.remote_queue.lock().unwrap().push_back(task);
|
let mut lock = self.queues.remote();
|
||||||
|
lock.schedule(task);
|
||||||
|
|
||||||
self.waker.wake();
|
self.waker.wake();
|
||||||
|
|
||||||
|
drop(lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,11 +349,8 @@ impl Schedule for Scheduler {
|
|||||||
impl Scheduler {
|
impl Scheduler {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tasks: UnsafeCell::new(task::OwnedList::new()),
|
|
||||||
local_queue: UnsafeCell::new(VecDeque::with_capacity(64)),
|
|
||||||
tick: Cell::new(0),
|
tick: Cell::new(0),
|
||||||
pending_drop: TransferStack::new(),
|
queues: Queues::new(),
|
||||||
remote_queue: Mutex::new(VecDeque::with_capacity(64)),
|
|
||||||
waker: AtomicWaker::new(),
|
waker: AtomicWaker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,10 +374,6 @@ impl Scheduler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn schedule_local(&self, task: Task<Self>) {
|
|
||||||
(*self.local_queue.get()).push_back(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_current(&self) -> bool {
|
fn is_current(&self) -> bool {
|
||||||
CURRENT_TASK_SET
|
CURRENT_TASK_SET
|
||||||
.try_with(|current| {
|
.try_with(|current| {
|
||||||
@@ -416,40 +385,12 @@ impl Scheduler {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_task(&self, tick: u8) -> Option<Task<Self>> {
|
|
||||||
if 0 == tick % CHECK_REMOTE_INTERVAL {
|
|
||||||
self.next_remote_task().or_else(|| self.next_local_task())
|
|
||||||
} else {
|
|
||||||
self.next_local_task().or_else(|| self.next_remote_task())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_local_task(&self) -> Option<Task<Self>> {
|
|
||||||
unsafe { (*self.local_queue.get()).pop_front() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_remote_task(&self) -> Option<Task<Self>> {
|
|
||||||
// there is no semantic information in the `PoisonError`, and it
|
|
||||||
// doesn't implement `Debug`, but clippy thinks that it's bad to
|
|
||||||
// match all errors here...
|
|
||||||
#[allow(clippy::match_wild_err_arm)]
|
|
||||||
let mut lock = match self.remote_queue.lock() {
|
|
||||||
// If the lock is poisoned, but the thread is already panicking,
|
|
||||||
// avoid a double panic. This is necessary since `next_task` (which
|
|
||||||
// calls `next_remote_task`) can be called in the `Drop` impl.
|
|
||||||
Err(_) if std::thread::panicking() => return None,
|
|
||||||
Err(_) => panic!("mutex poisoned"),
|
|
||||||
Ok(lock) => lock,
|
|
||||||
};
|
|
||||||
lock.pop_front()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(&self) {
|
fn tick(&self) {
|
||||||
assert!(self.is_current());
|
assert!(self.is_current());
|
||||||
for _ in 0..MAX_TASKS_PER_TICK {
|
for _ in 0..MAX_TASKS_PER_TICK {
|
||||||
let tick = self.tick.get().wrapping_add(1);
|
let tick = self.tick.get().wrapping_add(1);
|
||||||
self.tick.set(tick);
|
self.tick.set(tick);
|
||||||
let task = match self.next_task(tick) {
|
let task = match unsafe { self.queues.next_task(tick) } {
|
||||||
Some(task) => task,
|
Some(task) => task,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
@@ -457,56 +398,39 @@ impl Scheduler {
|
|||||||
if let Some(task) = task.run(&mut || Some(self.into())) {
|
if let Some(task) = task.run(&mut || Some(self.into())) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// we are on the local thread, so this is okay.
|
// we are on the local thread, so this is okay.
|
||||||
self.schedule_local(task);
|
self.queues.push_local(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drain_pending_drop(&self) {
|
|
||||||
for task in self.pending_drop.drain() {
|
|
||||||
unsafe {
|
|
||||||
(*self.tasks.get()).remove(&task);
|
|
||||||
}
|
|
||||||
drop(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Scheduler {
|
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt.debug_struct("Scheduler { .. }").finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Scheduler {
|
impl Drop for Scheduler {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Close the remote queue
|
// Close the remote queue
|
||||||
let mut lock = self.remote_queue.lock().unwrap();
|
self.queues.close_remote();
|
||||||
|
|
||||||
while let Some(task) = lock.pop_front() {
|
|
||||||
task.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(lock);
|
|
||||||
|
|
||||||
// Drain all local tasks
|
// Drain all local tasks
|
||||||
while let Some(task) = self.next_local_task() {
|
while let Some(task) = unsafe { self.queues.next_local_task() } {
|
||||||
task.shutdown();
|
task.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release owned tasks
|
// Release owned tasks
|
||||||
unsafe {
|
unsafe {
|
||||||
(*self.tasks.get()).shutdown();
|
self.queues.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.drain_pending_drop();
|
unsafe {
|
||||||
|
self.queues.drain_pending_drop();
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until all tasks have been released.
|
// Wait until all tasks have been released.
|
||||||
// XXX: this is a busy loop, but we don't really have any way to park
|
// XXX: this is a busy loop, but we don't really have any way to park
|
||||||
// the thread here?
|
// the thread here?
|
||||||
while unsafe { !(*self.tasks.get()).is_empty() } {
|
unsafe {
|
||||||
self.drain_pending_drop();
|
while self.queues.has_tasks_remaining() {
|
||||||
|
self.queues.drain_pending_drop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user