mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-26 00:00:16 +02:00
Threadpool refactor (#294)
* Switch worker lifecycle to an enum * Move some files around * Rename State -> PoolState
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
use park::{BoxPark, BoxUnpark};
|
||||
use task::{Task, Queue};
|
||||
use worker::WorkerState;
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
use deque;
|
||||
|
||||
// TODO: None of the fields should be public
|
||||
pub(crate) struct WorkerEntry {
|
||||
// Worker state. This is mutated when notifying the worker.
|
||||
pub state: AtomicUsize,
|
||||
|
||||
// Next entry in the parked Trieber stack
|
||||
next_sleeper: UnsafeCell<usize>,
|
||||
|
||||
// Worker half of deque
|
||||
pub deque: deque::Deque<Task>,
|
||||
|
||||
// Stealer half of deque
|
||||
pub steal: deque::Stealer<Task>,
|
||||
|
||||
// Thread parker
|
||||
pub park: UnsafeCell<BoxPark>,
|
||||
|
||||
// Thread unparker
|
||||
pub unpark: BoxUnpark,
|
||||
|
||||
// MPSC queue of jobs submitted to the worker from an external source.
|
||||
pub inbound: Queue,
|
||||
}
|
||||
|
||||
impl WorkerEntry {
|
||||
pub fn new(park: BoxPark, unpark: BoxUnpark) -> Self {
|
||||
let w = deque::Deque::new();
|
||||
let s = w.stealer();
|
||||
|
||||
WorkerEntry {
|
||||
state: AtomicUsize::new(WorkerState::default().into()),
|
||||
next_sleeper: UnsafeCell::new(0),
|
||||
deque: w,
|
||||
steal: s,
|
||||
inbound: Queue::new(),
|
||||
park: UnsafeCell::new(park),
|
||||
unpark,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn submit_internal(&self, task: Task) {
|
||||
self.push_internal(task);
|
||||
}
|
||||
|
||||
/// Submits a task to the worker. This assumes that the caller is external
|
||||
/// 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 {
|
||||
use worker::Lifecycle::*;
|
||||
|
||||
// Push the task onto the external queue
|
||||
self.push_external(task);
|
||||
|
||||
loop {
|
||||
let mut next = state;
|
||||
next.notify();
|
||||
|
||||
let actual = self.state.compare_and_swap(
|
||||
state.into(), next.into(),
|
||||
AcqRel).into();
|
||||
|
||||
if state == actual {
|
||||
break;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
match state.lifecycle() {
|
||||
Sleeping => {
|
||||
// The worker is currently sleeping, the condition variable must
|
||||
// be signaled
|
||||
self.wakeup();
|
||||
true
|
||||
}
|
||||
Shutdown => false,
|
||||
Running | Notified | Signaled => {
|
||||
// In these states, the worker is active and will eventually see
|
||||
// the task that was just submitted.
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push_external(&self, task: Task) {
|
||||
self.inbound.push(task);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_internal(&self, task: Task) {
|
||||
self.deque.push(task);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wakeup(&self) {
|
||||
self.unpark.unpark();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_sleeper(&self) -> usize {
|
||||
unsafe { *self.next_sleeper.get() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_next_sleeper(&self, val: usize) {
|
||||
unsafe { *self.next_sleeper.get() = val; }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WorkerEntry {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("WorkerEntry")
|
||||
.field("state", &self.state.load(Relaxed))
|
||||
.field("next_sleeper", &"UnsafeCell<usize>")
|
||||
.field("deque", &self.deque)
|
||||
.field("steal", &self.steal)
|
||||
.field("park", &"UnsafeCell<BoxPark>")
|
||||
.field("unpark", &"BoxUnpark")
|
||||
.field("inbound", &self.inbound)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
mod entry;
|
||||
mod state;
|
||||
|
||||
pub(crate) use self::entry::{
|
||||
WorkerEntry as Entry,
|
||||
};
|
||||
pub(crate) use self::state::{
|
||||
// TODO: Rename `State`
|
||||
WorkerState,
|
||||
Lifecycle,
|
||||
PUSHED_MASK,
|
||||
};
|
||||
|
||||
use pool::{Inner, PoolState};
|
||||
use notifier::Notifier;
|
||||
use sender::Sender;
|
||||
use task::Task;
|
||||
|
||||
use tokio_executor;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Thread worker
|
||||
///
|
||||
/// This is passed to the `around_worker` callback set on `Builder`. This
|
||||
/// callback is only expected to call `run` on it.
|
||||
#[derive(Debug)]
|
||||
pub struct Worker {
|
||||
// Shared scheduler data
|
||||
pub(crate) inner: Arc<Inner>,
|
||||
|
||||
// WorkerEntry index
|
||||
pub(crate) id: WorkerId,
|
||||
|
||||
// Set when the worker should finalize on drop
|
||||
should_finalize: Cell<bool>,
|
||||
|
||||
// Keep the value on the current thread.
|
||||
_p: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
/// Identifiers a thread pool worker.
|
||||
///
|
||||
/// This identifier is unique scoped by the thread pool. It is possible that
|
||||
/// different thread pool instances share worker identifier values.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct WorkerId {
|
||||
pub(crate) idx: usize,
|
||||
}
|
||||
|
||||
// Pointer to the current worker info
|
||||
thread_local!(static CURRENT_WORKER: Cell<*const Worker> = Cell::new(0 as *const _));
|
||||
|
||||
impl Worker {
|
||||
pub(crate) fn spawn(id: WorkerId, inner: &Arc<Inner>) {
|
||||
trace!("spawning new worker thread; id={}", id.idx);
|
||||
|
||||
let mut th = thread::Builder::new();
|
||||
|
||||
if let Some(ref prefix) = inner.config.name_prefix {
|
||||
th = th.name(format!("{}{}", prefix, id.idx));
|
||||
}
|
||||
|
||||
if let Some(stack) = inner.config.stack_size {
|
||||
th = th.stack_size(stack);
|
||||
}
|
||||
|
||||
let inner = inner.clone();
|
||||
|
||||
th.spawn(move || {
|
||||
let worker = Worker {
|
||||
inner,
|
||||
id,
|
||||
should_finalize: Cell::new(false),
|
||||
_p: PhantomData,
|
||||
};
|
||||
|
||||
// Make sure the ref to the worker does not move
|
||||
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.
|
||||
CURRENT_WORKER.with(|c| {
|
||||
c.set(wref as *const _);
|
||||
|
||||
let inner = wref.inner.clone();
|
||||
let mut sender = Sender { inner };
|
||||
|
||||
// Enter an execution context
|
||||
let mut enter = tokio_executor::enter().unwrap();
|
||||
|
||||
tokio_executor::with_default(&mut sender, &mut enter, |enter| {
|
||||
if let Some(ref callback) = wref.inner.config.around_worker {
|
||||
callback.call(wref, enter);
|
||||
} else {
|
||||
wref.run();
|
||||
}
|
||||
});
|
||||
});
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn with_current<F: FnOnce(Option<&Worker>) -> R, R>(f: F) -> R {
|
||||
CURRENT_WORKER.with(move |c| {
|
||||
let ptr = c.get();
|
||||
|
||||
if ptr.is_null() {
|
||||
f(None)
|
||||
} else {
|
||||
f(Some(unsafe { &*ptr }))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a reference to the worker's identifier.
|
||||
///
|
||||
/// This identifier is unique scoped by the thread pool. It is possible that
|
||||
/// different thread pool instances share worker identifier values.
|
||||
pub fn id(&self) -> &WorkerId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Run the worker
|
||||
///
|
||||
/// This function blocks until the worker is shutting down.
|
||||
pub fn run(&self) {
|
||||
const LIGHT_SLEEP_INTERVAL: usize = 32;
|
||||
|
||||
// Get the notifier.
|
||||
let notify = Arc::new(Notifier {
|
||||
inner: Arc::downgrade(&self.inner),
|
||||
});
|
||||
let mut sender = Sender { inner: self.inner.clone() };
|
||||
|
||||
let mut first = true;
|
||||
let mut spin_cnt = 0;
|
||||
let mut tick = 0;
|
||||
|
||||
while self.check_run_state(first) {
|
||||
first = false;
|
||||
|
||||
// Poll inbound until empty, transfering all tasks to the internal
|
||||
// queue.
|
||||
let consistent = self.drain_inbound();
|
||||
|
||||
// Run the next available task
|
||||
if self.try_run_task(¬ify, &mut sender) {
|
||||
if tick % LIGHT_SLEEP_INTERVAL == 0 {
|
||||
self.sleep_light();
|
||||
}
|
||||
|
||||
tick = tick.wrapping_add(1);
|
||||
spin_cnt = 0;
|
||||
|
||||
// As long as there is work, keep looping.
|
||||
continue;
|
||||
}
|
||||
|
||||
// No work in this worker's queue, it is time to try stealing.
|
||||
if self.try_steal_task(¬ify, &mut sender) {
|
||||
if tick % LIGHT_SLEEP_INTERVAL == 0 {
|
||||
self.sleep_light();
|
||||
}
|
||||
|
||||
tick = tick.wrapping_add(1);
|
||||
spin_cnt = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !consistent {
|
||||
spin_cnt = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Starting to get sleeeeepy
|
||||
if spin_cnt < 61 {
|
||||
spin_cnt += 1;
|
||||
} else {
|
||||
tick = 0;
|
||||
|
||||
if !self.sleep() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If there still isn't any work to do, shutdown the worker?
|
||||
}
|
||||
|
||||
self.should_finalize.set(true);
|
||||
}
|
||||
|
||||
/// Checks the worker's current state, updating it as needed.
|
||||
///
|
||||
/// Returns `true` if the worker should run.
|
||||
#[inline]
|
||||
fn check_run_state(&self, first: bool) -> bool {
|
||||
use self::Lifecycle::*;
|
||||
|
||||
let mut state: WorkerState = self.entry().state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
let pool_state: PoolState = self.inner.state.load(Acquire).into();
|
||||
|
||||
if pool_state.is_terminated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut next = state;
|
||||
|
||||
match state.lifecycle() {
|
||||
Running => break,
|
||||
Notified | Signaled => {
|
||||
// transition back to running
|
||||
next.set_lifecycle(Running);
|
||||
}
|
||||
Shutdown | Sleeping => {
|
||||
// The worker should never be in these states when calling
|
||||
// this function.
|
||||
panic!("unexpected worker state; lifecycle={:?}", state.lifecycle());
|
||||
}
|
||||
}
|
||||
|
||||
let actual = self.entry().state.compare_and_swap(
|
||||
state.into(), next.into(), AcqRel).into();
|
||||
|
||||
if actual == state {
|
||||
break;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
// If this is the first iteration of the worker loop, then the state can
|
||||
// be signaled.
|
||||
if !first && state.is_signaled() {
|
||||
trace!("Worker::check_run_state; delegate signal");
|
||||
// This worker is not ready to be signaled, so delegate the signal
|
||||
// to another worker.
|
||||
self.inner.signal_work(&self.inner);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Runs the next task on this worker's queue.
|
||||
///
|
||||
/// Returns `true` if work was found.
|
||||
#[inline]
|
||||
fn try_run_task(&self, notify: &Arc<Notifier>, sender: &mut Sender) -> bool {
|
||||
use deque::Steal::*;
|
||||
|
||||
// Poll the internal queue for a task to run
|
||||
match self.entry().deque.steal() {
|
||||
Data(task) => {
|
||||
self.run_task(task, notify, sender);
|
||||
true
|
||||
}
|
||||
Empty => false,
|
||||
Retry => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to steal a task from another worker.
|
||||
///
|
||||
/// Returns `true` if work was found
|
||||
#[inline]
|
||||
fn try_steal_task(&self, notify: &Arc<Notifier>, sender: &mut Sender) -> bool {
|
||||
use deque::Steal::*;
|
||||
|
||||
let len = self.inner.workers.len();
|
||||
let mut idx = self.inner.rand_usize() % len;
|
||||
let mut found_work = false;
|
||||
let start = idx;
|
||||
|
||||
loop {
|
||||
if idx < len {
|
||||
match self.inner.workers[idx].steal.steal() {
|
||||
Data(task) => {
|
||||
trace!("stole task");
|
||||
|
||||
self.run_task(task, notify, sender);
|
||||
|
||||
trace!("try_steal_task -- signal_work; self={}; from={}",
|
||||
self.id.idx, idx);
|
||||
|
||||
// Signal other workers that work is available
|
||||
self.inner.signal_work(&self.inner);
|
||||
|
||||
return true;
|
||||
}
|
||||
Empty => {}
|
||||
Retry => found_work = true,
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
} else {
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
if idx == start {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found_work
|
||||
}
|
||||
|
||||
fn run_task(&self, task: Task, notify: &Arc<Notifier>, sender: &mut Sender) {
|
||||
use task::Run::*;
|
||||
|
||||
match task.run(notify, sender) {
|
||||
Idle => {}
|
||||
Schedule => {
|
||||
self.entry().push_internal(task);
|
||||
}
|
||||
Complete => {
|
||||
let mut state: PoolState = self.inner.state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
let mut next = state;
|
||||
next.dec_num_futures();
|
||||
|
||||
let actual = self.inner.state.compare_and_swap(
|
||||
state.into(), next.into(), AcqRel).into();
|
||||
|
||||
if actual == state {
|
||||
trace!("task complete; state={:?}", next);
|
||||
|
||||
if state.num_futures() == 1 {
|
||||
// If the thread pool has been flagged as shutdown,
|
||||
// start terminating workers. This involves waking
|
||||
// up any sleeping worker so that they can notice
|
||||
// the shutdown state.
|
||||
if next.is_terminated() {
|
||||
self.inner.terminate_sleeping_workers();
|
||||
}
|
||||
}
|
||||
|
||||
// The worker's run loop will detect the shutdown state
|
||||
// next iteration.
|
||||
return;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drains all tasks on the extern queue and pushes them onto the internal
|
||||
/// queue.
|
||||
///
|
||||
/// Returns `true` if the operation was able to complete in a consistent
|
||||
/// state.
|
||||
#[inline]
|
||||
fn drain_inbound(&self) -> bool {
|
||||
use task::Poll::*;
|
||||
|
||||
let mut found_work = false;
|
||||
|
||||
loop {
|
||||
let task = unsafe { self.entry().inbound.poll() };
|
||||
|
||||
match task {
|
||||
Empty => {
|
||||
if found_work {
|
||||
trace!("found work while draining; signal_work");
|
||||
self.inner.signal_work(&self.inner);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Inconsistent => {
|
||||
if found_work {
|
||||
trace!("found work while draining; signal_work");
|
||||
self.inner.signal_work(&self.inner);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Data(task) => {
|
||||
found_work = true;
|
||||
self.entry().push_internal(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Put the worker to sleep
|
||||
///
|
||||
/// Returns `true` if woken up due to new work arriving.
|
||||
fn sleep(&self) -> bool {
|
||||
use self::Lifecycle::*;
|
||||
|
||||
trace!("Worker::sleep; worker={:?}", self);
|
||||
|
||||
let mut state: WorkerState = 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
|
||||
// sleeper stack, in which case, we don't push again.
|
||||
|
||||
loop {
|
||||
let mut next = state;
|
||||
|
||||
match state.lifecycle() {
|
||||
Running => {
|
||||
// Try setting the pushed state
|
||||
next.set_pushed();
|
||||
|
||||
// Transition the worker state to sleeping
|
||||
next.set_lifecycle(Sleeping);
|
||||
}
|
||||
Notified | Signaled => {
|
||||
// No need to sleep, transition back to running and move on.
|
||||
next.set_lifecycle(Running);
|
||||
}
|
||||
Shutdown | Sleeping => {
|
||||
// The worker cannot transition to sleep when already in a
|
||||
// sleeping state.
|
||||
panic!("unexpected worker state; actual={:?}", state.lifecycle());
|
||||
}
|
||||
}
|
||||
|
||||
let actual = self.entry().state.compare_and_swap(
|
||||
state.into(), next.into(), AcqRel).into();
|
||||
|
||||
if actual == state {
|
||||
if state.is_notified() {
|
||||
// The previous state was notified, so we don't need to
|
||||
// sleep.
|
||||
return true;
|
||||
}
|
||||
|
||||
if !state.is_pushed() {
|
||||
debug_assert!(next.is_pushed());
|
||||
|
||||
trace!(" sleeping -- push to stack; idx={}", self.id.idx);
|
||||
|
||||
// We obtained permission to push the worker into the
|
||||
// sleeper queue.
|
||||
if let Err(_) = self.inner.push_sleeper(self.id.idx) {
|
||||
trace!(" sleeping -- push to stack failed; idx={}", self.id.idx);
|
||||
// The push failed due to the pool being terminated.
|
||||
//
|
||||
// This is true because the "work" being woken up for is
|
||||
// shutting down.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
trace!(" -> starting to sleep; idx={}", self.id.idx);
|
||||
|
||||
let sleep_until = self.inner.config.keep_alive
|
||||
.map(|dur| Instant::now() + dur);
|
||||
|
||||
// The state has been transitioned to sleeping, we can now wait by
|
||||
// calling the parker. This is done in a loop as condvars can wakeup
|
||||
// spuriously.
|
||||
loop {
|
||||
let mut drop_thread = false;
|
||||
|
||||
match sleep_until {
|
||||
Some(when) => {
|
||||
let now = Instant::now();
|
||||
|
||||
if when >= now {
|
||||
drop_thread = true;
|
||||
}
|
||||
|
||||
let dur = when - now;
|
||||
|
||||
unsafe {
|
||||
(*self.entry().park.get())
|
||||
.park_timeout(dur)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
unsafe {
|
||||
(*self.entry().park.get())
|
||||
.park()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!(" -> wakeup; idx={}", self.id.idx);
|
||||
|
||||
// Reload the state
|
||||
state = self.entry().state.load(Acquire).into();
|
||||
|
||||
loop {
|
||||
match state.lifecycle() {
|
||||
Sleeping => {}
|
||||
Notified | Signaled => {
|
||||
// Transition back to running
|
||||
loop {
|
||||
let mut next = state;
|
||||
next.set_lifecycle(Running);
|
||||
|
||||
let actual = self.entry().state.compare_and_swap(
|
||||
state.into(), next.into(), AcqRel).into();
|
||||
|
||||
if actual == state {
|
||||
return true;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
}
|
||||
Shutdown | Running => {
|
||||
// To get here, the block above transitioned the tate to
|
||||
// `Sleeping`. No other thread can concurrently
|
||||
// transition to `Shutdown` or `Running`.
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
if !drop_thread {
|
||||
// This goees back to the outer loop.
|
||||
break;
|
||||
}
|
||||
|
||||
let mut next = state;
|
||||
next.set_lifecycle(Shutdown);
|
||||
|
||||
let actual = self.entry().state.compare_and_swap(
|
||||
state.into(), next.into(), AcqRel).into();
|
||||
|
||||
if actual == state {
|
||||
// Transitioned to a shutdown state
|
||||
return false;
|
||||
}
|
||||
|
||||
state = actual;
|
||||
}
|
||||
|
||||
// The worker hasn't been notified, go back to sleep
|
||||
}
|
||||
}
|
||||
|
||||
/// This doesn't actually put the thread to sleep. It calls
|
||||
/// `park.park_timeout` with a duration of 0. This allows the park
|
||||
/// implementation to perform any work that might be done on an interval.
|
||||
fn sleep_light(&self) {
|
||||
unsafe {
|
||||
(*self.entry().park.get())
|
||||
.park_timeout(Duration::from_millis(0))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn entry(&self) -> &Entry {
|
||||
&self.inner.workers[self.id.idx]
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Worker {
|
||||
fn drop(&mut self) {
|
||||
trace!("shutting down thread; idx={}", self.id.idx);
|
||||
|
||||
if self.should_finalize.get() {
|
||||
// Drain all work
|
||||
self.drain_inbound();
|
||||
|
||||
while let Some(_) = self.entry().deque.pop() {
|
||||
}
|
||||
|
||||
// TODO: Drain the work queue...
|
||||
self.inner.worker_terminated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkerId {
|
||||
pub(crate) fn new(idx: usize) -> WorkerId {
|
||||
WorkerId { idx }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
|
||||
/// Tracks worker state
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) struct WorkerState(usize);
|
||||
|
||||
/// Set when the worker is pushed onto the scheduler's stack of sleeping
|
||||
/// threads.
|
||||
pub(crate) const PUSHED_MASK: usize = 0b001;
|
||||
|
||||
/// Manages the worker lifecycle part of the state
|
||||
const LIFECYCLE_MASK: usize = 0b1110;
|
||||
const LIFECYCLE_SHIFT: usize = 1;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
#[repr(usize)]
|
||||
pub(crate) enum Lifecycle {
|
||||
/// The worker does not currently have an associated thread.
|
||||
Shutdown = 0 << LIFECYCLE_SHIFT,
|
||||
|
||||
/// The worker is currently processing its task.
|
||||
Running = 1 << LIFECYCLE_SHIFT,
|
||||
|
||||
/// The worker is currently asleep in the condvar
|
||||
Sleeping = 2 << LIFECYCLE_SHIFT,
|
||||
|
||||
/// The worker has been notified it should process more work.
|
||||
Notified = 3 << LIFECYCLE_SHIFT,
|
||||
|
||||
/// A stronger form of notification. In this case, the worker is expected to
|
||||
/// wakeup and try to acquire more work... if it enters this state while
|
||||
/// already busy with other work, it is expected to signal another worker.
|
||||
Signaled = 4 << LIFECYCLE_SHIFT,
|
||||
}
|
||||
|
||||
impl WorkerState {
|
||||
/// Returns true if the worker entry is pushed in the sleeper stack
|
||||
pub fn is_pushed(&self) -> bool {
|
||||
self.0 & PUSHED_MASK == PUSHED_MASK
|
||||
}
|
||||
|
||||
pub fn set_pushed(&mut self) {
|
||||
self.0 |= PUSHED_MASK
|
||||
}
|
||||
|
||||
pub fn is_notified(&self) -> bool {
|
||||
use self::Lifecycle::*;
|
||||
|
||||
match self.lifecycle() {
|
||||
Notified | Signaled => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lifecycle(&self) -> Lifecycle {
|
||||
Lifecycle::from(self.0 & LIFECYCLE_MASK)
|
||||
}
|
||||
|
||||
pub fn set_lifecycle(&mut self, val: Lifecycle) {
|
||||
self.0 = (self.0 & !LIFECYCLE_MASK) | (val as usize)
|
||||
}
|
||||
|
||||
pub fn is_signaled(&self) -> bool {
|
||||
self.lifecycle() == Lifecycle::Signaled
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
use self::Lifecycle::Signaled;
|
||||
|
||||
if self.lifecycle() != Signaled {
|
||||
self.set_lifecycle(Signaled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WorkerState {
|
||||
fn default() -> WorkerState {
|
||||
// All workers will start pushed in the sleeping stack
|
||||
WorkerState(PUSHED_MASK)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for WorkerState {
|
||||
fn from(src: usize) -> Self {
|
||||
WorkerState(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkerState> for usize {
|
||||
fn from(src: WorkerState) -> Self {
|
||||
src.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WorkerState {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("WorkerState")
|
||||
.field("lifecycle", &self.lifecycle())
|
||||
.field("is_pushed", &self.is_pushed())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Lifecycle =====
|
||||
|
||||
impl From<usize> for Lifecycle {
|
||||
fn from(src: usize) -> Lifecycle {
|
||||
use self::Lifecycle::*;
|
||||
|
||||
debug_assert!(
|
||||
src == Shutdown as usize ||
|
||||
src == Running as usize ||
|
||||
src == Sleeping as usize ||
|
||||
src == Notified as usize ||
|
||||
src == Signaled as usize);
|
||||
|
||||
unsafe { ::std::mem::transmute(src) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Lifecycle> for usize {
|
||||
fn from(src: Lifecycle) -> usize {
|
||||
let v = src as usize;
|
||||
debug_assert!(v & LIFECYCLE_MASK == v);
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
use super::Lifecycle::*;
|
||||
|
||||
#[test]
|
||||
fn lifecycle_encode() {
|
||||
let lifecycles = &[
|
||||
Shutdown,
|
||||
Running,
|
||||
Sleeping,
|
||||
Notified,
|
||||
Signaled,
|
||||
];
|
||||
|
||||
for &lifecycle in lifecycles {
|
||||
let mut v: usize = lifecycle.into();
|
||||
v &= LIFECYCLE_MASK;
|
||||
|
||||
assert_eq!(lifecycle, Lifecycle::from(v));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lifecycle_ord() {
|
||||
assert!(Running >= Shutdown);
|
||||
assert!(Signaled >= Notified);
|
||||
assert!(Signaled >= Sleeping);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user