use std::cell::UnsafeCell;
use std::collections::{HashMap, VecDeque};
use std::marker::PhantomData;
use std::process::abort;
use std::time::Duration;
use colored::Colorize;
#[cfg(feature = "futures-util")]
use futures_util::future::join_all;
use indicatif::{MultiProgress, ProgressDrawTarget, ProgressState, ProgressStyle};
use tokio::sync::mpsc::{self, Sender};
use tokio::task::JoinHandle;
use crate::helper::line_err;
use crate::{limit_string, MainProgress, Result, Uid, WorkerTemplate};
type Handle = JoinHandle<()>;
type Handles = HashMap<Uid, Handle>;
pub struct WorkerPool<D, S>
where
S: WorkerTemplate,
{
ui: MultiProgress,
channels: VecDeque<(Uid, Sender<D>)>,
handles: Handles,
main_progress: MainProgress<S>,
_unsafe_sync: PhantomData<UnsafeCell<()>>,
}
impl<D: Send + 'static, S: WorkerTemplate> WorkerPool<D, S> {
pub fn new(len: u64, template: S, draw_hz: u8) -> Self {
let target = ProgressDrawTarget::stderr_with_hz(draw_hz);
let ui = MultiProgress::with_draw_target(target);
let main_ui = MainProgress::new(len, ui.clone(), template);
ui.set_move_cursor(false);
Self {
ui,
channels: Default::default(),
handles: Default::default(),
main_progress: main_ui,
_unsafe_sync: PhantomData,
}
}
pub fn new_task_id(&self) -> Uid {
Uid::new(self.handles.len() as u32 + 1).unwrap()
}
pub fn spawn_worker<F, Fut>(&mut self, f: F) -> Uid
where
Fut: Future<Output = anyhow::Result<()>> + Send + 'static,
Fut::Output: Send + 'static,
F: Fn(D, MainProgress<S>) -> Fut + Send + 'static,
{
let task_id = self.new_task_id();
let (tx, rx) = mpsc::channel(1);
let handle = match super::WorkerHandleBuilder::default()
.fn_ptr(f)
.main_ui(self.main_ui())
.receiver(rx)
.build(task_id)
{
Ok(handle) => handle,
Err(build_error) => {
self.main_progress.println(build_error.to_string());
abort();
}
};
let handle: Handle = tokio::spawn(handle.run());
self.channels.push_back((task_id, tx));
self.handles.insert(task_id, handle);
task_id
}
pub fn main_ui(&self) -> MainProgress<S> {
self.main_progress.clone()
}
pub async fn sigint(&mut self) {
let prefix = "<C-c> Received, Waiting all background processes to finished";
self.main_progress.set_prefix(limit_string(116, prefix.bright_red().to_string(), None));
self.stop_all_workers().await;
}
async fn stop_all_workers(&mut self) {
self.close_all();
loop {
if self.handles.iter().filter(|(_, h)| h.is_finished()).count().ge(&self.handles.len()) {
break;
}
tokio::time::sleep(Duration::from_millis(1)).await;
}
self
.main_progress
.println("All background process is finished!".bright_green().to_string())
}
fn close_all(&mut self) {
while self.channels.pop_back().is_some() {
}
}
pub async fn send_seqcst(&mut self, mut data: D) -> Result<(), D> {
let timeout_1ms = Duration::from_millis(1);
'sending: while !self.channels.is_empty() {
self.channels.retain(|(_, tx)| !tx.is_closed());
let Some(channel) = self.channels.pop_front() else { continue };
let sent = channel.1.send_timeout(data, timeout_1ms).await;
self.channels.push_back(channel);
match sent {
Ok(_) => return Ok(()),
Err(error) => {
data = error.into_inner();
continue 'sending;
}
}
}
Err(data)
}
pub async fn send_to(&mut self, id: Uid, data: D) -> Result<(), D> {
if self.handles.is_empty() {
panic!("No worker has ever been spawned!");
}
self.channels.retain(|(_, tx)| !tx.is_closed());
for (worker_id, tx) in &self.channels {
if worker_id == &id {
if let Err(err) = tx.send(data).await {
_ = self.ui.println(err.to_string().bright_red().to_string());
return Err(err.0);
}
return Ok(());
}
}
Err(data)
}
pub fn thead_count(&self) -> usize {
self.handles.len()
}
pub async fn join_all(mut self) {
self.stop_all_workers().await;
#[cfg(feature = "futures-util")]
join_all(self.handles.into_values()).await;
#[cfg(not(feature = "futures-util"))]
for handle in self.handles.into_values() {
_ = handle.await;
}
}
}
impl<D: Send, S: WorkerTemplate> WorkerPool<D, S> {
pub fn get_style(template: impl AsRef<str>) -> ProgressStyle {
type PS = ProgressState;
ProgressStyle::with_template(template.as_ref())
.unwrap()
.progress_chars("──")
.tick_strings(&["◜", "◠", "◝", "◞", "◡", "◟"]) .with_key("date", |_: &PS, w: &mut dyn std::fmt::Write| {
_ = write!(w, "[{}]", crate::dt_now_rfc2822())
})
.with_key("|", |_: &PS, w: &mut dyn std::fmt::Write| _ = w.write_str("│"))
.with_key("-", |_: &PS, w: &mut dyn std::fmt::Write| _ = w.write_str("─"))
.with_key("l", |_: &PS, w: &mut dyn std::fmt::Write| _ = w.write_str("╰"))
.with_key("status", |ps: &PS, w: &mut dyn std::fmt::Write| {
_ = write!(
w,
"{}",
if ps.is_finished() {
"FINISHED".bright_green()
} else {
"RUNNING".bright_yellow()
}
)
})
}
pub fn println(&self, line: impl AsRef<str>) -> Result<(), std::io::Error> {
self.ui.println(line)
}
pub fn eprintln(&self, line: impl AsRef<str>) -> Result<(), std::io::Error> {
self.ui.println(line_err(line.as_ref()))
}
pub fn horizontal_line(len: usize) -> String {
"─".repeat(len)
}
pub fn vertical_line(len: usize) -> String {
"│".repeat(len)
}
}
#[cfg(test)]
mod test {
use static_assertions::assert_not_impl_any;
use super::*;
use crate::DefaultTemplate;
#[test]
fn worker_pool_should_not_be_sync() {
assert_not_impl_any!(WorkerPool<String, DefaultTemplate>: Sync);
}
}