pub fn yield_now() -> YieldNow {
YieldNow { yielded: false }
}
#[derive(Debug)]
pub struct YieldNow {
yielded: bool,
}
impl core::future::Future for YieldNow {
type Output = ();
fn poll(
mut self: core::pin::Pin<&mut Self>,
context: &mut core::task::Context<'_>,
) -> core::task::Poll<()> {
if self.yielded {
core::task::Poll::Ready(())
} else {
self.yielded = true;
context.waker().wake_by_ref();
core::task::Poll::Pending
}
}
}
#[cfg(all(feature = "runtime-tokio", not(loom)))]
pub use join_set::{JoinError, JoinSet};
#[cfg(all(feature = "std", any(not(feature = "runtime-tokio"), loom)))]
pub use portable_join_set::{AbortHandle, JoinError, JoinSet};
#[cfg(all(feature = "runtime-tokio", not(loom)))]
mod join_set {
use std::future::Future;
pub use tokio::task::JoinError;
#[derive(Debug)]
pub struct JoinSet<T>(tokio::task::JoinSet<T>);
impl<T: 'static> JoinSet<T> {
#[must_use]
pub fn new() -> Self {
Self(tokio::task::JoinSet::new())
}
pub fn spawn<F>(&mut self, future: F) -> tokio::task::AbortHandle
where
F: Future<Output = T> + Send + 'static,
T: Send,
{
self.0.spawn(future)
}
pub async fn join_next(&mut self) -> Option<Result<T, JoinError>> {
self.0.join_next().await
}
pub fn abort_all(&mut self) {
self.0.abort_all();
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<T: 'static> Default for JoinSet<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
#[proxima::test(runtime = "tokio")]
async fn spawn_then_join_next_returns_value() {
let mut set: JoinSet<u32> = JoinSet::new();
set.spawn(async { 42 });
let outcome = set.join_next().await.expect("one in flight");
assert_eq!(outcome.expect("not cancelled"), 42);
assert!(set.is_empty());
}
#[proxima::test(runtime = "tokio")]
async fn drop_aborts_running_tasks() {
let counter = Arc::new(AtomicUsize::new(0));
let observed_counts = {
let mut set: JoinSet<()> = JoinSet::new();
for _ in 0..3 {
let counter = counter.clone();
set.spawn(async move {
loop {
tokio::time::sleep(Duration::from_millis(1)).await;
counter.fetch_add(1, Ordering::Release);
}
});
}
tokio::time::sleep(Duration::from_millis(20)).await;
let count_before_drop = counter.load(Ordering::Acquire);
drop(set);
tokio::time::sleep(Duration::from_millis(20)).await;
let count_after_grace = counter.load(Ordering::Acquire);
(count_before_drop, count_after_grace)
};
let (before, after) = observed_counts;
assert!(before > 0, "tasks should run before drop");
assert!(
after <= before + 5,
"tasks should stop within grace; before={before} after={after}"
);
}
}
}
#[cfg(all(feature = "std", any(not(feature = "runtime-tokio"), loom)))]
mod portable_join_set {
use std::future::Future;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use std::sync::mpsc;
use crate::sync::notify::Notify;
#[derive(Debug)]
pub struct JoinError {
panicked: bool,
cancelled: bool,
}
impl JoinError {
#[must_use]
pub fn is_panic(&self) -> bool {
self.panicked
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.cancelled
}
}
impl core::fmt::Display for JoinError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.panicked {
write!(formatter, "task panicked")
} else if self.cancelled {
write!(formatter, "task aborted")
} else {
write!(formatter, "task join error")
}
}
}
impl std::error::Error for JoinError {}
#[derive(Debug, Clone, Copy)]
pub struct AbortHandle;
pub struct JoinSet<T> {
sender: mpsc::Sender<Result<T, JoinError>>,
receiver: mpsc::Receiver<Result<T, JoinError>>,
notify: Arc<Notify>,
outstanding: usize,
}
impl<T: Send + 'static> JoinSet<T> {
#[must_use]
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel();
Self {
sender,
receiver,
notify: Arc::new(Notify::new()),
outstanding: 0,
}
}
pub fn spawn<F>(&mut self, future: F) -> AbortHandle
where
F: Future<Output = T> + Send + 'static,
{
self.outstanding += 1;
let sender = self.sender.clone();
let notify = self.notify.clone();
std::thread::spawn(move || {
let outcome =
catch_unwind(AssertUnwindSafe(|| futures::executor::block_on(future)));
let result = match outcome {
Ok(value) => Ok(value),
Err(_) => Err(JoinError {
panicked: true,
cancelled: false,
}),
};
let _ = sender.send(result);
notify.notify_one();
});
AbortHandle
}
pub async fn join_next(&mut self) -> Option<Result<T, JoinError>> {
loop {
if self.outstanding == 0 {
return None;
}
match self.receiver.try_recv() {
Ok(result) => {
self.outstanding -= 1;
return Some(result);
}
Err(mpsc::TryRecvError::Empty) => self.notify.notified().await,
Err(mpsc::TryRecvError::Disconnected) => {
self.outstanding = 0;
return None;
}
}
}
}
pub fn abort_all(&mut self) {
while self.outstanding > 0 {
self.outstanding -= 1;
let _ = self.sender.send(Err(JoinError {
panicked: false,
cancelled: true,
}));
}
self.notify.notify_waiters();
}
#[must_use]
pub fn len(&self) -> usize {
self.outstanding
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.outstanding == 0
}
}
impl<T: Send + 'static> Default for JoinSet<T> {
fn default() -> Self {
Self::new()
}
}
}