use crossbeam::channel::{bounded, Sender};
use std::fmt::{self, Debug};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread::{self, Builder, JoinHandle};
use std::{cmp, panic};
use crate::executor::strategy::Strategy;
use crate::executor::task::Task;
use crate::executor::Result;
pub struct WorkSharing {
sender: Option<Sender<Box<dyn Task>>>,
threads: Vec<JoinHandle<()>>,
pending: Arc<AtomicUsize>,
}
impl WorkSharing {
#[must_use]
pub fn new(num_workers: usize) -> Self {
Self::with_capacity(num_workers, 8 * num_workers)
}
#[must_use]
pub fn with_capacity(num_workers: usize, capacity: usize) -> Self {
let (sender, receiver) = bounded::<Box<dyn Task>>(capacity);
let pending = Arc::new(AtomicUsize::new(0));
let iter = (0..num_workers).map(|index| {
let receiver = receiver.clone();
let pending = Arc::clone(&pending);
let h = move || {
while let Ok(task) = receiver.recv() {
let _ = panic::catch_unwind(|| {
let subtasks = task.execute();
if !subtasks.is_empty() {
subtasks.execute();
}
});
pending.fetch_sub(1, Ordering::Acquire);
}
};
Builder::new()
.name(format!("zrx/executor/{}", index + 1))
.spawn(h)
.unwrap()
});
let threads = iter.collect();
Self {
sender: Some(sender),
threads,
pending,
}
}
}
impl Strategy for WorkSharing {
fn submit(&self, task: Box<dyn Task>) -> Result {
self.pending.fetch_add(1, Ordering::Release);
let sender = self.sender.as_ref().expect("invariant");
sender.try_send(task).map_err(|err| {
self.pending.fetch_sub(1, Ordering::Relaxed);
err.into()
})
}
#[inline]
fn num_workers(&self) -> usize {
self.threads.len()
}
#[inline]
fn num_tasks_running(&self) -> usize {
self.num_tasks_pending() - self.sender.as_ref().map_or(0, Sender::len)
}
#[inline]
fn num_tasks_pending(&self) -> usize {
self.pending.load(Ordering::Relaxed)
}
#[inline]
fn capacity(&self) -> usize {
self.sender
.as_ref()
.and_then(Sender::capacity)
.unwrap_or_default()
}
}
impl Default for WorkSharing {
#[inline]
fn default() -> Self {
Self::new(cmp::max(
thread::available_parallelism()
.map_or(1, |num| num.get().saturating_sub(1)),
1,
))
}
}
impl Drop for WorkSharing {
fn drop(&mut self) {
if let Some(sender) = self.sender.take() {
drop(sender);
}
for handle in self.threads.drain(..) {
let _ = handle.join();
}
}
}
impl Debug for WorkSharing {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("WorkSharing")
.field("workers", &self.num_workers())
.field("running", &self.num_tasks_running())
.field("pending", &self.num_tasks_pending())
.finish()
}
}