use crate::loom::sync::atomic::{AtomicBool, AtomicUsize};
use crate::loom::sync::{Condvar, Mutex};
use crate::loom::thread;
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use std::time::Duration;
use super::pool::{ShutdownHandles, SpawnError, SpawnerMetrics, Task, ThreadManagementState};
#[cfg(not(loom))]
const NUM_SHARDS: usize = 16;
#[cfg(loom)]
const NUM_SHARDS: usize = 2;
struct Shard {
queue: VecDeque<Task>,
sealed: bool,
}
pub(super) struct ShardedImpl {
shards: [Mutex<Shard>; NUM_SHARDS],
non_empty_mask: AtomicUsize,
coord: Mutex<ShardedCoord>,
condvar: Condvar,
is_shutdown: AtomicBool,
#[cfg(loom)]
push_index: AtomicUsize,
}
struct ShardedCoord {
num_notify: u32,
thread_mgmt_state: ThreadManagementState,
}
impl ShardedImpl {
pub(super) fn new(thread_mgmt_state: ThreadManagementState) -> ShardedImpl {
ShardedImpl {
shards: std::array::from_fn(|_| {
Mutex::new(Shard {
queue: VecDeque::new(),
sealed: false,
})
}),
non_empty_mask: AtomicUsize::new(0),
coord: Mutex::new(ShardedCoord {
num_notify: 0,
thread_mgmt_state,
}),
condvar: Condvar::new(),
is_shutdown: AtomicBool::new(false),
#[cfg(loom)]
push_index: AtomicUsize::new(0),
}
}
#[cfg(not(loom))]
fn push_shard_index(&self) -> usize {
crate::runtime::context::thread_rng_n(NUM_SHARDS as u32) as usize
}
#[cfg(loom)]
fn push_shard_index(&self) -> usize {
self.push_index.fetch_add(1, Ordering::Relaxed) % NUM_SHARDS
}
fn push(&self, task: Task, metrics: &SpawnerMetrics) -> Result<(), Task> {
let index = self.push_shard_index();
let mut shard = self.shards[index].lock();
if shard.sealed {
return Err(task);
}
shard.queue.push_back(task);
metrics.inc_queue_depth();
if shard.queue.len() == 1 {
self.non_empty_mask.fetch_or(1 << index, Ordering::Relaxed);
}
Ok(())
}
fn pop(&self, preferred_shard: usize) -> Option<Task> {
let mask = self.non_empty_mask.load(Ordering::Relaxed);
if mask == 0 {
return None;
}
let start = preferred_shard % NUM_SHARDS;
for i in 0..NUM_SHARDS {
let index = (start + i) % NUM_SHARDS;
if mask & (1 << index) == 0 {
continue;
}
let mut shard = self.shards[index].lock();
match shard.queue.pop_front() {
Some(task) => {
if shard.queue.is_empty() {
self.non_empty_mask
.fetch_and(!(1 << index), Ordering::Relaxed);
}
return Some(task);
}
None => {
}
}
}
None
}
fn drain_and_seal(&self, metrics: &SpawnerMetrics, preferred_shard: usize) {
let start = preferred_shard % NUM_SHARDS;
for i in 0..NUM_SHARDS {
let index = (start + i) % NUM_SHARDS;
let tasks = {
let mut shard = self.shards[index].lock();
shard.sealed = true;
self.non_empty_mask
.fetch_and(!(1 << index), Ordering::Relaxed);
std::mem::take(&mut shard.queue)
};
for task in tasks {
metrics.dec_queue_depth();
task.shutdown_or_run_if_mandatory();
}
}
}
pub(super) fn spawn_task<F>(
&self,
task: Task,
metrics: &SpawnerMetrics,
on_no_idle: F,
) -> Result<(), SpawnError>
where
F: FnOnce(&mut ThreadManagementState) -> Result<(), SpawnError>,
{
if self.is_shutdown.load(Ordering::Acquire) {
task.shutdown();
return Err(SpawnError::ShuttingDown);
}
if let Err(task) = self.push(task, metrics) {
task.shutdown();
return Err(SpawnError::ShuttingDown);
}
let mut coord = self.coord.lock();
if coord.thread_mgmt_state.shutdown {
return Ok(());
}
if metrics.num_idle_threads() == 0 {
on_no_idle(&mut coord.thread_mgmt_state)?;
} else {
metrics.dec_num_idle_threads();
coord.num_notify += 1;
drop(coord);
self.condvar.notify_one();
}
Ok(())
}
pub(super) fn run_worker(
&self,
metrics: &SpawnerMetrics,
keep_alive: Duration,
worker_thread_id: usize,
) -> Option<thread::JoinHandle<()>> {
let mut join_on_thread = None;
let mut coord;
'main: loop {
while let Some(task) = self.pop(worker_thread_id) {
metrics.dec_queue_depth();
task.run();
}
coord = self.coord.lock();
if let Some(task) = self.pop(worker_thread_id) {
metrics.dec_queue_depth();
drop(coord);
task.run();
continue 'main;
}
metrics.inc_num_idle_threads();
while !coord.thread_mgmt_state.shutdown {
let lock_result = self.condvar.wait_timeout(coord, keep_alive).unwrap();
coord = lock_result.0;
let timeout_result = lock_result.1;
if coord.num_notify != 0 {
coord.num_notify -= 1;
drop(coord);
continue 'main;
}
if !coord.thread_mgmt_state.shutdown && timeout_result.timed_out() {
join_on_thread = coord.thread_mgmt_state.worker_timed_out(worker_thread_id);
break 'main;
}
}
drop(coord);
self.drain_and_seal(metrics, worker_thread_id);
coord = self.coord.lock();
break 'main;
}
metrics.dec_num_threads();
let prev_idle = metrics.dec_num_idle_threads();
assert_ne!(
prev_idle, 0,
"`num_idle_threads` underflowed on thread exit"
);
if coord.thread_mgmt_state.shutdown && metrics.num_threads() == 0 {
self.condvar.notify_one();
}
drop(coord);
join_on_thread
}
pub(super) fn begin_shutdown(&self, metrics: &SpawnerMetrics) -> Option<ShutdownHandles> {
let mut coord = self.coord.lock();
let handles = coord.thread_mgmt_state.begin_shutdown()?;
self.is_shutdown.store(true, Ordering::Release);
self.condvar.notify_all();
let no_workers = metrics.num_threads() == 0;
drop(coord);
if no_workers {
self.drain_and_seal(metrics, 0);
}
Some(handles)
}
}