mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-24 00:00:11 +02:00
ThreadPool refactoring (#299)
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
use park::{BoxPark, BoxUnpark};
|
||||
use task::{Task, Queue};
|
||||
use worker::WorkerState;
|
||||
use worker::state::{State, PUSHED_MASK};
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::Ordering::{Acquire, AcqRel, Relaxed};
|
||||
|
||||
use deque;
|
||||
|
||||
// TODO: None of the fields should be public
|
||||
//
|
||||
// It would also be helpful to split up the state across what fields /
|
||||
// operations are thread-safe vs. which ones require ownership of the worker.
|
||||
pub(crate) struct WorkerEntry {
|
||||
// Worker state. This is mutated when notifying the worker.
|
||||
pub state: AtomicUsize,
|
||||
@@ -18,10 +21,10 @@ pub(crate) struct WorkerEntry {
|
||||
next_sleeper: UnsafeCell<usize>,
|
||||
|
||||
// Worker half of deque
|
||||
pub deque: deque::Deque<Task>,
|
||||
deque: deque::Deque<Task>,
|
||||
|
||||
// Stealer half of deque
|
||||
pub steal: deque::Stealer<Task>,
|
||||
steal: deque::Stealer<Task>,
|
||||
|
||||
// Thread parker
|
||||
pub park: UnsafeCell<BoxPark>,
|
||||
@@ -39,7 +42,7 @@ impl WorkerEntry {
|
||||
let s = w.stealer();
|
||||
|
||||
WorkerEntry {
|
||||
state: AtomicUsize::new(WorkerState::default().into()),
|
||||
state: AtomicUsize::new(State::default().into()),
|
||||
next_sleeper: UnsafeCell::new(0),
|
||||
deque: w,
|
||||
steal: s,
|
||||
@@ -49,6 +52,30 @@ impl WorkerEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Atomically load the worker's state
|
||||
///
|
||||
/// # Ordering
|
||||
///
|
||||
/// An `Acquire` ordering is established on the entry's state variable.
|
||||
pub fn load_state(&self) -> State {
|
||||
self.state.load(Acquire).into()
|
||||
}
|
||||
|
||||
/// Atomically unset the pushed flag.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// The state *before* the push flag is unset.
|
||||
///
|
||||
/// # Ordering
|
||||
///
|
||||
/// The specified ordering is established on the entry's state variable.
|
||||
pub fn fetch_unset_pushed(&self, ordering: Ordering) -> State {
|
||||
self.state.fetch_and(!PUSHED_MASK, ordering).into()
|
||||
}
|
||||
|
||||
/// Submit a task to this worker while currently on the same thread that is
|
||||
/// running the worker.
|
||||
#[inline]
|
||||
pub fn submit_internal(&self, task: Task) {
|
||||
self.push_internal(task);
|
||||
@@ -58,7 +85,11 @@ impl WorkerEntry {
|
||||
/// to the worker. Internal submissions go through another path.
|
||||
///
|
||||
/// Returns `false` if the worker needs to be spawned.
|
||||
pub fn submit_external(&self, task: Task, mut state: WorkerState) -> bool {
|
||||
///
|
||||
/// # Ordering
|
||||
///
|
||||
/// The `state` must have been obtained with an `Acquire` ordering.
|
||||
pub fn submit_external(&self, task: Task, mut state: State) -> bool {
|
||||
use worker::Lifecycle::*;
|
||||
|
||||
// Push the task onto the external queue
|
||||
@@ -95,6 +126,86 @@ impl WorkerEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Signals to the worker that it should stop
|
||||
///
|
||||
/// `state` is the last observed state for the worker. This allows skipping
|
||||
/// the initial load from the state atomic.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns `Ok` when the worker was successfully signaled.
|
||||
///
|
||||
/// Returns `Err` if the worker has already terminated.
|
||||
pub fn signal_stop(&self, mut state: State) -> Result<(), ()> {
|
||||
use worker::Lifecycle::*;
|
||||
|
||||
// Transition the worker state to signaled
|
||||
loop {
|
||||
let mut next = state;
|
||||
|
||||
match state.lifecycle() {
|
||||
Shutdown => {
|
||||
return Err(());
|
||||
}
|
||||
Running | Sleeping => {}
|
||||
Notified | Signaled => {
|
||||
// These two states imply that the worker is active, thus it
|
||||
// will eventually see the shutdown signal, so we don't need
|
||||
// to do anything.
|
||||
//
|
||||
// The worker is forced to see the shutdown signal
|
||||
// eventually as:
|
||||
//
|
||||
// a) No more work will arrive
|
||||
// b) The shutdown signal is stored as the head of the
|
||||
// sleep, stack which will prevent the worker from going to
|
||||
// sleep again.
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
next.set_lifecycle(Signaled);
|
||||
|
||||
let actual = self.state.compare_and_swap(
|
||||
state.into(), next.into(), AcqRel).into();
|
||||
|
||||
if actual == state {
|
||||
break;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
// Wakeup the worker
|
||||
self.wakeup();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop a task
|
||||
///
|
||||
/// This **must** only be called by the thread that owns the worker entry.
|
||||
/// This function is not `Sync`.
|
||||
pub fn pop_task(&self) -> deque::Steal<Task> {
|
||||
self.deque.steal()
|
||||
}
|
||||
|
||||
/// Steal a task
|
||||
///
|
||||
/// This is called by *other* workers to steal a task for processing. This
|
||||
/// function is `Sync`.
|
||||
pub fn steal_task(&self) -> deque::Steal<Task> {
|
||||
self.steal.steal()
|
||||
}
|
||||
|
||||
/// Drain (and drop) all tasks that are queued for work.
|
||||
///
|
||||
/// This is called when the pool is shutting down.
|
||||
pub fn drain_tasks(&self) {
|
||||
while let Some(_) = self.deque.pop() {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_external(&self, task: Task) {
|
||||
self.inbound.push(task);
|
||||
|
||||
@@ -5,13 +5,11 @@ pub(crate) use self::entry::{
|
||||
WorkerEntry as Entry,
|
||||
};
|
||||
pub(crate) use self::state::{
|
||||
// TODO: Rename `State`
|
||||
WorkerState,
|
||||
State,
|
||||
Lifecycle,
|
||||
PUSHED_MASK,
|
||||
};
|
||||
|
||||
use pool::{Inner, PoolState};
|
||||
use pool::{self, Pool};
|
||||
use notifier::Notifier;
|
||||
use sender::Sender;
|
||||
use task::Task;
|
||||
@@ -33,7 +31,7 @@ use std::time::{Duration, Instant};
|
||||
#[derive(Debug)]
|
||||
pub struct Worker {
|
||||
// Shared scheduler data
|
||||
pub(crate) inner: Arc<Inner>,
|
||||
pub(crate) inner: Arc<Pool>,
|
||||
|
||||
// WorkerEntry index
|
||||
pub(crate) id: WorkerId,
|
||||
@@ -58,7 +56,7 @@ pub struct WorkerId {
|
||||
thread_local!(static CURRENT_WORKER: Cell<*const Worker> = Cell::new(0 as *const _));
|
||||
|
||||
impl Worker {
|
||||
pub(crate) fn spawn(id: WorkerId, inner: &Arc<Inner>) {
|
||||
pub(crate) fn spawn(id: WorkerId, inner: &Arc<Pool>) {
|
||||
trace!("spawning new worker thread; id={}", id.idx);
|
||||
|
||||
let mut th = thread::Builder::new();
|
||||
@@ -85,7 +83,7 @@ impl Worker {
|
||||
let wref = &worker;
|
||||
|
||||
// Create another worker... It's ok, this is just a new type around
|
||||
// `Inner` that is expected to stay on the current thread.
|
||||
// `Pool` that is expected to stay on the current thread.
|
||||
CURRENT_WORKER.with(|c| {
|
||||
c.set(wref as *const _);
|
||||
|
||||
@@ -202,10 +200,10 @@ impl Worker {
|
||||
fn check_run_state(&self, first: bool) -> bool {
|
||||
use self::Lifecycle::*;
|
||||
|
||||
let mut state: WorkerState = self.entry().state.load(Acquire).into();
|
||||
let mut state: State = self.entry().state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
let pool_state: PoolState = self.inner.state.load(Acquire).into();
|
||||
let pool_state: pool::State = self.inner.state.load(Acquire).into();
|
||||
|
||||
if pool_state.is_terminated() {
|
||||
return false;
|
||||
@@ -256,7 +254,7 @@ impl Worker {
|
||||
use deque::Steal::*;
|
||||
|
||||
// Poll the internal queue for a task to run
|
||||
match self.entry().deque.steal() {
|
||||
match self.entry().pop_task() {
|
||||
Data(task) => {
|
||||
self.run_task(task, notify, sender);
|
||||
true
|
||||
@@ -280,7 +278,7 @@ impl Worker {
|
||||
|
||||
loop {
|
||||
if idx < len {
|
||||
match self.inner.workers[idx].steal.steal() {
|
||||
match self.inner.workers[idx].steal_task() {
|
||||
Data(task) => {
|
||||
trace!("stole task");
|
||||
|
||||
@@ -320,7 +318,7 @@ impl Worker {
|
||||
self.entry().push_internal(task);
|
||||
}
|
||||
Complete => {
|
||||
let mut state: PoolState = self.inner.state.load(Acquire).into();
|
||||
let mut state: pool::State = self.inner.state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
let mut next = state;
|
||||
@@ -400,7 +398,7 @@ impl Worker {
|
||||
|
||||
trace!("Worker::sleep; worker={:?}", self);
|
||||
|
||||
let mut state: WorkerState = self.entry().state.load(Acquire).into();
|
||||
let mut state: State = self.entry().state.load(Acquire).into();
|
||||
|
||||
// The first part of the sleep process is to transition the worker state
|
||||
// to "pushed". Now, it may be that the worker is already pushed on the
|
||||
@@ -573,11 +571,12 @@ impl Drop for Worker {
|
||||
trace!("shutting down thread; idx={}", self.id.idx);
|
||||
|
||||
if self.should_finalize.get() {
|
||||
// Drain all work
|
||||
// Get all inbound work and push it onto the work queue. The work
|
||||
// queue is drained in the next step.
|
||||
self.drain_inbound();
|
||||
|
||||
while let Some(_) = self.entry().deque.pop() {
|
||||
}
|
||||
// Drain the work queue
|
||||
self.entry().drain_tasks();
|
||||
|
||||
// TODO: Drain the work queue...
|
||||
self.inner.worker_terminated();
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
|
||||
/// Tracks worker state
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) struct WorkerState(usize);
|
||||
pub(crate) struct State(usize);
|
||||
|
||||
/// Set when the worker is pushed onto the scheduler's stack of sleeping
|
||||
/// threads.
|
||||
@@ -13,7 +12,7 @@ pub(crate) const PUSHED_MASK: usize = 0b001;
|
||||
const LIFECYCLE_MASK: usize = 0b1110;
|
||||
const LIFECYCLE_SHIFT: usize = 1;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
|
||||
#[repr(usize)]
|
||||
pub(crate) enum Lifecycle {
|
||||
/// The worker does not currently have an associated thread.
|
||||
@@ -34,7 +33,7 @@ pub(crate) enum Lifecycle {
|
||||
Signaled = 4 << LIFECYCLE_SHIFT,
|
||||
}
|
||||
|
||||
impl WorkerState {
|
||||
impl State {
|
||||
/// Returns true if the worker entry is pushed in the sleeper stack
|
||||
pub fn is_pushed(&self) -> bool {
|
||||
self.0 & PUSHED_MASK == PUSHED_MASK
|
||||
@@ -74,28 +73,28 @@ impl WorkerState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WorkerState {
|
||||
fn default() -> WorkerState {
|
||||
impl Default for State {
|
||||
fn default() -> State {
|
||||
// All workers will start pushed in the sleeping stack
|
||||
WorkerState(PUSHED_MASK)
|
||||
State(PUSHED_MASK)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for WorkerState {
|
||||
impl From<usize> for State {
|
||||
fn from(src: usize) -> Self {
|
||||
WorkerState(src)
|
||||
State(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkerState> for usize {
|
||||
fn from(src: WorkerState) -> Self {
|
||||
impl From<State> for usize {
|
||||
fn from(src: State) -> Self {
|
||||
src.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WorkerState {
|
||||
impl fmt::Debug for State {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("WorkerState")
|
||||
fmt.debug_struct("worker::State")
|
||||
.field("lifecycle", &self.lifecycle())
|
||||
.field("is_pushed", &self.is_pushed())
|
||||
.finish()
|
||||
@@ -127,16 +126,6 @@ impl From<Lifecycle> for usize {
|
||||
}
|
||||
}
|
||||
|
||||
impl cmp::PartialOrd for Lifecycle {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Lifecycle) -> Option<cmp::Ordering> {
|
||||
let a: usize = (*self).into();
|
||||
let b: usize = (*other).into();
|
||||
|
||||
a.partial_cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user