use std::{
collections::HashMap,
future::Future,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Weak,
},
};
use parking_lot::Mutex;
use crate::async_runtime::{global_executor, Executor, Task};
use super::{select, CondWait, Either};
pub type TaskID = usize;
pub struct TaskGroup {
inner: Arc<Inner>,
}
struct Inner {
tasks: Mutex<HashMap<TaskID, TaskHandler>>,
next_id: AtomicUsize,
executor: Executor,
}
impl Inner {
fn remove(&self, id: TaskID) -> Option<TaskHandler> {
self.tasks.lock().remove(&id)
}
}
impl TaskGroup {
pub fn new() -> Self {
Self::with_inner(global_executor())
}
pub fn with_executor(executor: Executor) -> Self {
Self::with_inner(executor)
}
fn with_inner(executor: Executor) -> Self {
Self {
inner: Arc::new(Inner {
tasks: Mutex::new(HashMap::new()),
next_id: AtomicUsize::new(0),
executor,
}),
}
}
pub fn spawn<T, Fut>(&self, fut: Fut) -> TaskID
where
T: Send + Sync + 'static,
Fut: Future<Output = T> + Send + 'static,
{
self.spawn_then(fut, |_| async {})
}
pub fn spawn_then<T, Fut, CallbackF, CallbackFut>(
&self,
fut: Fut,
callback: CallbackF,
) -> TaskID
where
T: Send + Sync + 'static,
Fut: Future<Output = T> + Send + 'static,
CallbackF: FnOnce(TaskResult<T>) -> CallbackFut + Send + 'static,
CallbackFut: Future<Output = ()> + Send + 'static,
{
let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
let mut tasks = self.inner.tasks.lock();
let task = TaskHandler::new(
self.inner.executor.clone(),
fut,
callback,
Arc::downgrade(&self.inner),
id,
);
tasks.insert(id, task);
id
}
pub fn remove(&self, id: TaskID) -> Option<TaskHandler> {
self.inner.remove(id)
}
pub fn is_empty(&self) -> bool {
self.inner.tasks.lock().is_empty()
}
pub fn len(&self) -> usize {
self.inner.tasks.lock().len()
}
pub async fn cancel(&self) {
let handlers: Vec<TaskHandler> = self.inner.tasks.lock().drain().map(|(_, h)| h).collect();
for handler in handlers {
handler.cancel().await;
}
}
}
impl Default for TaskGroup {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub enum TaskResult<T> {
Completed(T),
Cancelled,
}
impl<T: std::fmt::Debug> std::fmt::Display for TaskResult<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
TaskResult::Cancelled => write!(f, "Task cancelled"),
TaskResult::Completed(res) => write!(f, "Task completed: {res:?}"),
}
}
}
pub struct TaskHandler {
task: Task<()>,
stop_signal: Arc<CondWait>,
cancel_flag: Arc<CondWait>,
}
impl TaskHandler {
fn new<T, Fut, CallbackF, CallbackFut>(
ex: Executor,
fut: Fut,
callback: CallbackF,
group: Weak<Inner>,
id: TaskID,
) -> TaskHandler
where
T: Send + Sync + 'static,
Fut: Future<Output = T> + Send + 'static,
CallbackF: FnOnce(TaskResult<T>) -> CallbackFut + Send + 'static,
CallbackFut: Future<Output = ()> + Send + 'static,
{
let stop_signal = Arc::new(CondWait::new());
let stop_signal_c = stop_signal.clone();
let cancel_flag = Arc::new(CondWait::new());
let cancel_flag_c = cancel_flag.clone();
let task = ex.spawn(async move {
let result = select(stop_signal_c.wait(), fut).await;
let result = match result {
Either::Left(_) => TaskResult::Cancelled,
Either::Right(res) => TaskResult::Completed(res),
};
callback(result).await;
cancel_flag_c.signal().await;
if let Some(group) = group.upgrade() {
if let Some(handler) = group.remove(id) {
handler.detach();
}
}
});
TaskHandler {
task,
stop_signal,
cancel_flag,
}
}
fn detach(self) {
self.task.detach();
}
async fn cancel(self) {
self.stop_signal.signal().await;
self.cancel_flag.wait().await;
self.task.cancel().await;
}
}
#[cfg(test)]
mod tests {
use std::{future, sync::Arc};
use crate::async_runtime::block_on;
use crate::async_util::sleep;
use super::*;
#[cfg(feature = "tokio")]
#[test]
fn test_task_group_with_tokio_executor() {
let ex = Arc::new(tokio::runtime::Runtime::new().unwrap());
ex.clone().block_on(async move {
let group = Arc::new(TaskGroup::with_executor(ex.into()));
group.spawn_then(future::ready(0), |res| async move {
assert!(matches!(res, TaskResult::Completed(0)));
});
group.spawn_then(future::pending::<()>(), |res| async move {
assert!(matches!(res, TaskResult::Cancelled));
});
let groupc = group.clone();
group.spawn_then(
async move {
groupc.spawn_then(future::pending::<()>(), |res| async move {
assert!(matches!(res, TaskResult::Cancelled));
});
},
|res| async move {
assert!(matches!(res, TaskResult::Completed(_)));
},
);
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
group.cancel().await;
});
}
#[cfg(feature = "smol")]
#[test]
fn test_task_group_with_smol_executor() {
let ex = Arc::new(smol::Executor::new());
smol::block_on(ex.clone().run(async move {
let group = Arc::new(TaskGroup::with_executor(ex.into()));
group.spawn_then(future::ready(0), |res| async move {
assert!(matches!(res, TaskResult::Completed(0)));
});
group.spawn_then(future::pending::<()>(), |res| async move {
assert!(matches!(res, TaskResult::Cancelled));
});
let groupc = group.clone();
group.spawn_then(
async move {
groupc.spawn_then(future::pending::<()>(), |res| async move {
assert!(matches!(res, TaskResult::Cancelled));
});
},
|res| async move {
assert!(matches!(res, TaskResult::Completed(_)));
},
);
smol::Timer::after(std::time::Duration::from_millis(50)).await;
group.cancel().await;
}));
}
#[test]
fn test_task_group() {
block_on(async {
let group = Arc::new(TaskGroup::new());
group.spawn_then(future::ready(0), |res| async move {
assert!(matches!(res, TaskResult::Completed(0)));
});
group.spawn_then(future::pending::<()>(), |res| async move {
assert!(matches!(res, TaskResult::Cancelled));
});
let groupc = group.clone();
group.spawn_then(
async move {
groupc.spawn_then(future::pending::<()>(), |res| async move {
assert!(matches!(res, TaskResult::Cancelled));
});
},
|res| async move {
assert!(matches!(res, TaskResult::Completed(_)));
},
);
sleep(std::time::Duration::from_millis(50)).await;
group.cancel().await;
});
}
#[test]
fn test_task_group_removes_finished_tasks() {
block_on(async {
let group = Arc::new(TaskGroup::new());
group.spawn(future::ready(0));
group.spawn(future::pending::<()>());
sleep(std::time::Duration::from_millis(50)).await;
assert_eq!(group.len(), 1);
group.cancel().await;
assert!(group.is_empty());
});
}
#[test]
fn test_task_group_remove_by_id() {
block_on(async {
let group = Arc::new(TaskGroup::new());
let id = group.spawn(future::pending::<()>());
assert_eq!(group.len(), 1);
let handler = group.remove(id).expect("task is present");
assert!(group.is_empty());
handler.cancel().await;
});
}
}