mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-15 00:00:15 +02:00
812 lines
26 KiB
Rust
812 lines
26 KiB
Rust
mod entry;
|
|
mod stack;
|
|
mod state;
|
|
|
|
pub(crate) use self::entry::{
|
|
WorkerEntry as Entry,
|
|
};
|
|
pub(crate) use self::stack::Stack;
|
|
pub(crate) use self::state::{
|
|
State,
|
|
Lifecycle,
|
|
};
|
|
|
|
use pool::{self, Pool, BackupId};
|
|
use notifier::Notifier;
|
|
use sender::Sender;
|
|
use shutdown::ShutdownTrigger;
|
|
use task::{self, Task, CanBlock};
|
|
|
|
use tokio_executor;
|
|
|
|
use futures::{Poll, Async};
|
|
|
|
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;
|
|
|
|
/// Thread worker
|
|
///
|
|
/// This is passed to the [`around_worker`] callback set on [`Builder`]. This
|
|
/// callback is only expected to call [`run`] on it.
|
|
///
|
|
/// [`Builder`]: struct.Builder.html
|
|
/// [`around_worker`]: struct.Builder.html#method.around_worker
|
|
/// [`run`]: struct.Worker.html#method.run
|
|
#[derive(Debug)]
|
|
pub struct Worker {
|
|
// Shared scheduler data
|
|
pub(crate) pool: Arc<Pool>,
|
|
|
|
// WorkerEntry index
|
|
pub(crate) id: WorkerId,
|
|
|
|
// Backup thread ID assigned to processing this worker.
|
|
backup_id: BackupId,
|
|
|
|
// Set to the task that is currently being polled by the worker. This is
|
|
// needed so that `blocking` blocks are able to interact with this task.
|
|
//
|
|
// This has to be a raw pointer to make it compile, but great care is taken
|
|
// when this is set.
|
|
current_task: CurrentTask,
|
|
|
|
// Set when the thread is in blocking mode.
|
|
is_blocking: Cell<bool>,
|
|
|
|
// Set when the worker should finalize on drop
|
|
should_finalize: Cell<bool>,
|
|
|
|
// Completes the shutdown process when the `ThreadPool` and all `Worker`s get dropped.
|
|
trigger: Arc<ShutdownTrigger>,
|
|
|
|
// Keep the value on the current thread.
|
|
_p: PhantomData<Rc<()>>,
|
|
}
|
|
|
|
/// Tracks the state related to the currently running task.
|
|
#[derive(Debug)]
|
|
struct CurrentTask {
|
|
/// This has to be a raw pointer to make it compile, but great care is taken
|
|
/// when this is set.
|
|
task: Cell<Option<*const Arc<Task>>>,
|
|
|
|
/// Tracks the blocking capacity allocation state.
|
|
can_block: Cell<CanBlock>,
|
|
}
|
|
|
|
/// Identifies 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) 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 new(
|
|
id: WorkerId,
|
|
backup_id: BackupId,
|
|
pool: Arc<Pool>,
|
|
trigger: Arc<ShutdownTrigger>,
|
|
) -> Worker {
|
|
Worker {
|
|
pool,
|
|
id,
|
|
backup_id,
|
|
current_task: CurrentTask::new(),
|
|
is_blocking: Cell::new(false),
|
|
should_finalize: Cell::new(false),
|
|
trigger,
|
|
_p: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_blocking(&self) -> bool {
|
|
self.is_blocking.get()
|
|
}
|
|
|
|
/// Run the worker
|
|
///
|
|
/// Returns `true` if the thread should keep running as a `backup` thread.
|
|
pub(crate) fn do_run(&self) -> bool {
|
|
// Create another worker... It's ok, this is just a new type around
|
|
// `Pool` that is expected to stay on the current thread.
|
|
CURRENT_WORKER.with(|c| {
|
|
c.set(self as *const _);
|
|
|
|
let pool = self.pool.clone();
|
|
let mut sender = Sender { pool };
|
|
|
|
// 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) = self.pool.config.around_worker {
|
|
callback.call(self, enter);
|
|
} else {
|
|
self.run();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Can't be in blocking mode and finalization mode
|
|
debug_assert!(!self.is_blocking.get() || !self.should_finalize.get());
|
|
|
|
self.is_blocking.get()
|
|
}
|
|
|
|
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 }))
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Transition the current worker to a blocking worker
|
|
pub(crate) fn transition_to_blocking(&self) -> Poll<(), ::BlockingError> {
|
|
use self::CanBlock::*;
|
|
|
|
// If we get this far, then `current_task` has been set.
|
|
let task_ref = self.current_task.get_ref();
|
|
|
|
// First step is to acquire blocking capacity for the task.
|
|
match self.current_task.can_block() {
|
|
// Capacity to block has already been allocated to this task.
|
|
Allocated => {}
|
|
|
|
// The task has already requested capacity to block, but there is
|
|
// none yet available.
|
|
NoCapacity => return Ok(Async::NotReady),
|
|
|
|
// The task has yet to ask for capacity
|
|
CanRequest => {
|
|
// Atomically attempt to acquire blocking capacity, and if none
|
|
// is available, register the task to be notified once capacity
|
|
// becomes available.
|
|
match self.pool.poll_blocking_capacity(task_ref)? {
|
|
Async::Ready(()) => {
|
|
self.current_task.set_can_block(Allocated);
|
|
}
|
|
Async::NotReady => {
|
|
self.current_task.set_can_block(NoCapacity);
|
|
return Ok(Async::NotReady);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The task has been allocated blocking capacity. At this point, this is
|
|
// when the current thread transitions from a worker to a backup thread.
|
|
// To do so requires handing over the worker to another backup thread.
|
|
|
|
if self.is_blocking.get() {
|
|
// The thread is already in blocking mode, so there is nothing else
|
|
// to do. Return `Ready` and allow the caller to block the thread.
|
|
return Ok(().into());
|
|
}
|
|
|
|
trace!("transition to blocking state");
|
|
|
|
// Transitioning to blocking requires handing over the worker state to
|
|
// another thread so that the work queue can continue to be processed.
|
|
|
|
self.pool.spawn_thread(self.id.clone(), &self.pool);
|
|
|
|
// Track that the thread has now fully entered the blocking state.
|
|
self.is_blocking.set(true);
|
|
|
|
Ok(().into())
|
|
}
|
|
|
|
/// Transition from blocking
|
|
pub(crate) fn transition_from_blocking(&self) {
|
|
// TODO: Attempt to take ownership of the worker again.
|
|
}
|
|
|
|
/// 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 MAX_SPINS: usize = 3;
|
|
const LIGHT_SLEEP_INTERVAL: usize = 32;
|
|
|
|
// Get the notifier.
|
|
let notify = Arc::new(Notifier {
|
|
pool: self.pool.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, transferring all tasks to the internal
|
|
// queue.
|
|
let consistent = self.drain_inbound();
|
|
|
|
// Run the next available task
|
|
if self.try_run_task(¬ify) {
|
|
if self.is_blocking.get() {
|
|
// Exit out of the run state
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if !consistent {
|
|
spin_cnt = 0;
|
|
continue;
|
|
}
|
|
|
|
spin_cnt += 1;
|
|
|
|
// Yield the thread several times before it actually goes to sleep.
|
|
if spin_cnt <= MAX_SPINS {
|
|
thread::yield_now();
|
|
continue;
|
|
}
|
|
|
|
tick = 0;
|
|
spin_cnt = 0;
|
|
|
|
// Starting to get sleeeeepy
|
|
if !self.sleep() {
|
|
return;
|
|
}
|
|
|
|
// If there still isn't any work to do, shutdown the worker?
|
|
}
|
|
|
|
// The pool is terminating. However, transitioning the pool state to
|
|
// terminated is the very first step of the finalization process. Other
|
|
// threads may not see this state and try to spawn a new thread. To
|
|
// ensure consistency, before the current thread shuts down, it must
|
|
// return the backup token to the stack.
|
|
//
|
|
// The returned result is ignored because `Err` represents the pool
|
|
// shutting down. We are currently aware of this fact.
|
|
let _ = self.pool.release_backup(self.backup_id);
|
|
|
|
self.should_finalize.set(true);
|
|
}
|
|
|
|
/// Try to run a task
|
|
///
|
|
/// Returns `true` if work was found.
|
|
#[inline]
|
|
fn try_run_task(&self, notify: &Arc<Notifier>) -> bool {
|
|
if self.try_run_owned_task(notify) {
|
|
return true;
|
|
}
|
|
|
|
self.try_steal_task(notify)
|
|
}
|
|
|
|
/// 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::*;
|
|
|
|
debug_assert!(!self.is_blocking.get());
|
|
|
|
let mut state: State = self.entry().state.load(Acquire).into();
|
|
|
|
loop {
|
|
let pool_state: pool::State = self.pool.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;
|
|
}
|
|
|
|
// `first` is set to true the first time this function is called after
|
|
// the thread has started.
|
|
//
|
|
// This check is to handle the scenario where a worker gets signaled
|
|
// while it is already happily running. The `is_signaled` state is
|
|
// intended to wake up a worker that has been previously sleeping in
|
|
// effect increasing the number of active workers. If this is the first
|
|
// time `check_run_state` is called, then being in a signalled state is
|
|
// normal and the thread was started to handle it. However, if this is
|
|
// **not** the first time the fn was called, then the number of active
|
|
// workers has not been increased by the signal, so `signal_work` has to
|
|
// be called again to try to wake up another worker.
|
|
//
|
|
// For example, if the thread pool is configured to allow 4 workers.
|
|
// Worker 1 is processing tasks from its `deque`. Worker 2 receives its
|
|
// first task. Worker 2 will pick a random worker to signal. It does
|
|
// this by popping off the sleep stack, but there is no guarantee that
|
|
// workers on the sleep stack are actually sleeping. It is possible that
|
|
// Worker 1 gets signaled.
|
|
//
|
|
// Without this check, in the above case, no additional workers will get
|
|
// started, which results in the thread pool permanently being at 2
|
|
// workers even though it should reach 4.
|
|
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.pool.signal_work(&self.pool);
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
/// Runs the next task on this worker's queue.
|
|
///
|
|
/// Returns `true` if work was found.
|
|
fn try_run_owned_task(&self, notify: &Arc<Notifier>) -> bool {
|
|
use deque::Pop;
|
|
|
|
// Poll the internal queue for a task to run
|
|
match self.entry().pop_task() {
|
|
Pop::Data(task) => {
|
|
self.run_task(task, notify);
|
|
true
|
|
}
|
|
Pop::Empty => false,
|
|
Pop::Retry => true,
|
|
}
|
|
}
|
|
|
|
/// Tries to steal a task from another worker.
|
|
///
|
|
/// Returns `true` if work was found
|
|
fn try_steal_task(&self, notify: &Arc<Notifier>) -> bool {
|
|
use deque::Steal;
|
|
|
|
debug_assert!(!self.is_blocking.get());
|
|
|
|
let len = self.pool.workers.len();
|
|
let mut idx = self.pool.rand_usize() % len;
|
|
let mut found_work = false;
|
|
let start = idx;
|
|
|
|
loop {
|
|
if idx < len {
|
|
match self.pool.workers[idx].steal_tasks(self.entry()) {
|
|
Steal::Data(task) => {
|
|
trace!("stole task");
|
|
|
|
self.run_task(task, notify);
|
|
|
|
trace!("try_steal_task -- signal_work; self={}; from={}",
|
|
self.id.0, idx);
|
|
|
|
// Signal other workers that work is available
|
|
//
|
|
// TODO: Should this be called here or before
|
|
// `run_task`?
|
|
self.pool.signal_work(&self.pool);
|
|
|
|
return true;
|
|
}
|
|
Steal::Empty => {}
|
|
Steal::Retry => found_work = true,
|
|
}
|
|
|
|
idx += 1;
|
|
} else {
|
|
idx = 0;
|
|
}
|
|
|
|
if idx == start {
|
|
break;
|
|
}
|
|
}
|
|
|
|
found_work
|
|
}
|
|
|
|
fn run_task(&self, task: Arc<Task>, notify: &Arc<Notifier>) {
|
|
use task::Run::*;
|
|
|
|
let run = self.run_task2(&task, notify);
|
|
|
|
// TODO: Try to claim back the worker state in case the backup thread
|
|
// did not start up fast enough. This is a performance optimization.
|
|
|
|
match run {
|
|
Idle => {}
|
|
Schedule => {
|
|
if self.is_blocking.get() {
|
|
// The future has been notified while it was running.
|
|
// However, the future also entered a blocking section,
|
|
// which released the worker state from this thread.
|
|
//
|
|
// This means that scheduling the future must be done from
|
|
// a point of view external to the worker set.
|
|
//
|
|
// We have to call `submit_external` instead of `submit`
|
|
// here because `self` is still set as the current worker.
|
|
self.pool.submit_external(task, &self.pool);
|
|
} else {
|
|
self.entry().push_internal(task);
|
|
}
|
|
}
|
|
Complete => {
|
|
let mut state: pool::State = self.pool.state.load(Acquire).into();
|
|
|
|
loop {
|
|
let mut next = state;
|
|
next.dec_num_futures();
|
|
|
|
let actual = self.pool.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.pool.terminate_sleeping_workers();
|
|
}
|
|
}
|
|
|
|
// The worker's run loop will detect the shutdown state
|
|
// next iteration.
|
|
return;
|
|
}
|
|
|
|
state = actual;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Actually run the task. This is where `Worker::current_task` is set.
|
|
///
|
|
/// Great care is needed to ensure that `current_task` is unset in this
|
|
/// function.
|
|
fn run_task2(&self,
|
|
task: &Arc<Task>,
|
|
notify: &Arc<Notifier>)
|
|
-> task::Run
|
|
{
|
|
struct Guard<'a> {
|
|
worker: &'a Worker,
|
|
allocated_at_run: bool
|
|
}
|
|
|
|
impl<'a> Drop for Guard<'a> {
|
|
fn drop(&mut self) {
|
|
// A task is allocated at run when it was explicitly notified
|
|
// that the task has capacity to block. When this happens, that
|
|
// capacity is automatically allocated to the notified task.
|
|
// This capacity is "use it or lose it", so if the thread is not
|
|
// transitioned to blocking in this call, then another task has
|
|
// to be notified.
|
|
if self.allocated_at_run && !self.worker.is_blocking.get() {
|
|
self.worker.pool.notify_blocking_task(&self.worker.pool);
|
|
}
|
|
|
|
self.worker.current_task.clear();
|
|
}
|
|
}
|
|
|
|
let can_block = task.consume_blocking_allocation();
|
|
|
|
// Set `current_task`
|
|
self.current_task.set(task, can_block);
|
|
|
|
// Create the guard, this ensures that `current_task` is unset when the
|
|
// function returns, even if the return is caused by a panic.
|
|
let _g = Guard {
|
|
worker: self,
|
|
allocated_at_run: can_block == CanBlock::Allocated
|
|
};
|
|
|
|
task.run(notify)
|
|
}
|
|
|
|
/// 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 {
|
|
// TODO: Why is this called on every iteration? Would it
|
|
// not be better to only signal when work was found
|
|
// after waking up?
|
|
trace!("found work while draining; signal_work");
|
|
self.pool.signal_work(&self.pool);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
Inconsistent => {
|
|
if found_work {
|
|
trace!("found work while draining; signal_work");
|
|
self.pool.signal_work(&self.pool);
|
|
}
|
|
|
|
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::*;
|
|
|
|
// Putting a worker to sleep is a multipart operation. This is, in part,
|
|
// due to the fact that a worker can be notified without it being popped
|
|
// from the sleep stack. Extra care is needed to deal with this.
|
|
|
|
trace!("Worker::sleep; worker={:?}", self.id);
|
|
|
|
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
|
|
// 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.0);
|
|
|
|
// We obtained permission to push the worker into the
|
|
// sleeper queue.
|
|
if let Err(_) = self.pool.push_sleeper(self.id.0) {
|
|
trace!(" sleeping -- push to stack failed; idx={}", self.id.0);
|
|
// 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.0);
|
|
|
|
// 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 {
|
|
unsafe {
|
|
(*self.entry().park.get())
|
|
.park()
|
|
.unwrap();
|
|
}
|
|
|
|
trace!(" -> wakeup; idx={}", self.id.0);
|
|
|
|
// Reload the state
|
|
state = self.entry().state.load(Acquire).into();
|
|
|
|
// If the worker has been notified, transition back to running.
|
|
match state.lifecycle() {
|
|
Sleeping => {
|
|
// Still sleeping. Park again.
|
|
}
|
|
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 state to
|
|
// `Sleeping`. No other thread can concurrently
|
|
// transition to `Shutdown` or `Running`.
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 {
|
|
debug_assert!(!self.is_blocking.get());
|
|
&self.pool.workers[self.id.0]
|
|
}
|
|
}
|
|
|
|
impl Drop for Worker {
|
|
fn drop(&mut self) {
|
|
trace!("shutting down thread; idx={}", self.id.0);
|
|
|
|
if self.should_finalize.get() {
|
|
// Get all inbound work and push it onto the work queue. The work
|
|
// queue is drained in the next step.
|
|
self.drain_inbound();
|
|
|
|
// Drain the work queue
|
|
self.entry().drain_tasks();
|
|
|
|
// TODO: Drain the work queue...
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== impl CurrentTask =====
|
|
|
|
impl CurrentTask {
|
|
/// Returns a default `CurrentTask` representing no task.
|
|
fn new() -> CurrentTask {
|
|
CurrentTask {
|
|
task: Cell::new(None),
|
|
can_block: Cell::new(CanBlock::CanRequest),
|
|
}
|
|
}
|
|
|
|
/// Returns a reference to the task.
|
|
fn get_ref(&self) -> &Arc<Task> {
|
|
unsafe { &*self.task.get().unwrap() }
|
|
}
|
|
|
|
fn can_block(&self) -> CanBlock {
|
|
self.can_block.get()
|
|
}
|
|
|
|
fn set_can_block(&self, can_block: CanBlock) {
|
|
self.can_block.set(can_block);
|
|
}
|
|
|
|
fn set(&self, task: &Arc<Task>, can_block: CanBlock) {
|
|
self.task.set(Some(task as *const _));
|
|
self.can_block.set(can_block);
|
|
}
|
|
|
|
/// Reset the `CurrentTask` to null state.
|
|
fn clear(&self) {
|
|
self.task.set(None);
|
|
self.can_block.set(CanBlock::CanRequest);
|
|
}
|
|
}
|
|
|
|
// ===== impl WorkerId =====
|
|
|
|
impl WorkerId {
|
|
/// Returns a `WorkerId` representing the worker entry at index `idx`.
|
|
pub(crate) fn new(idx: usize) -> WorkerId {
|
|
WorkerId(idx)
|
|
}
|
|
|
|
/// Returns this identifier represented as an integer.
|
|
///
|
|
/// Worker identifiers in a single thread pool are guaranteed to correspond to integers in the
|
|
/// range `0..pool_size`.
|
|
pub fn to_usize(&self) -> usize {
|
|
self.0
|
|
}
|
|
}
|