use std::future::Future;
#[cfg(target_arch = "wasm32")]
use std::cell::Cell;
#[cfg(target_arch = "wasm32")]
use std::rc::Rc;
mod sealed {
pub trait Sealed {}
impl<T: ?Sized> Sealed for T {}
}
#[cfg(not(target_arch = "wasm32"))]
#[doc(hidden)]
pub trait TaskSend: sealed::Sealed + Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send + ?Sized> TaskSend for T {}
#[cfg(target_arch = "wasm32")]
#[doc(hidden)]
pub trait TaskSend: sealed::Sealed {}
#[cfg(target_arch = "wasm32")]
impl<T: ?Sized> TaskSend for T {}
#[doc(hidden)]
pub trait TaskFuture<T>: Future<Output = T> + TaskSend {}
impl<T, F> TaskFuture<T> for F where F: Future<Output = T> + TaskSend + ?Sized {}
#[cfg(any(
all(not(target_arch = "wasm32"), feature = "runtime-tokio"),
target_arch = "wasm32",
))]
pub(crate) trait Runtime {
fn spawn<F>(&self, future: F) -> TaskHandle
where
F: Future<Output = ()> + TaskSend + 'static;
#[allow(dead_code)]
fn yield_now(&self) -> impl Future<Output = ()>;
}
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
#[derive(Default)]
pub(crate) struct TokioRuntime;
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
pub(crate) fn default_runtime() -> TokioRuntime {
TokioRuntime
}
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
impl Runtime for TokioRuntime {
fn spawn<F>(&self, future: F) -> TaskHandle
where
F: Future<Output = ()> + TaskSend + 'static,
{
TaskHandle(tokio::spawn(future))
}
async fn yield_now(&self) {
tokio::task::yield_now().await;
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
pub(crate) fn ensure_runtime_available() -> crate::Result<()> {
tokio::runtime::Handle::try_current()
.map(|_| ())
.map_err(|_| crate::Error::RuntimeRequired)
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn ensure_runtime_available() -> crate::Result<()> {
Ok(())
}
#[cfg(target_arch = "wasm32")]
#[derive(Default)]
pub(crate) struct WasmRuntime;
#[cfg(target_arch = "wasm32")]
pub(crate) fn default_runtime() -> WasmRuntime {
WasmRuntime
}
#[cfg(target_arch = "wasm32")]
impl Runtime for WasmRuntime {
fn spawn<F>(&self, future: F) -> TaskHandle
where
F: Future<Output = ()> + TaskSend + 'static,
{
use futures_util::future::{AbortHandle, Abortable, FutureExt};
let (abort, registration) = AbortHandle::new_pair();
let (completed_tx, completed_rx) = futures_channel::oneshot::channel();
let finished = Rc::new(Cell::new(false));
let finished_for_task = Rc::clone(&finished);
wasm_bindgen_futures::spawn_local(async move {
if std::panic::AssertUnwindSafe(Abortable::new(future, registration))
.catch_unwind()
.await
.is_err()
{
tracing::error!("task panicked on the WASM runtime");
}
finished_for_task.set(true);
let _ = completed_tx.send(());
});
TaskHandle {
abort,
completed: completed_rx,
finished,
}
}
async fn yield_now(&self) {
let (tx, rx) = futures_channel::oneshot::channel();
wasm_bindgen_futures::spawn_local(async move {
let _ = tx.send(());
});
let _ = rx.await;
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
pub(crate) struct TaskHandle(tokio::task::JoinHandle<()>);
#[cfg(target_arch = "wasm32")]
pub(crate) struct TaskHandle {
abort: futures_util::future::AbortHandle,
completed: futures_channel::oneshot::Receiver<()>,
finished: Rc<Cell<bool>>,
}
#[cfg(any(
all(not(target_arch = "wasm32"), feature = "runtime-tokio"),
target_arch = "wasm32",
))]
impl TaskHandle {
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
pub(crate) fn abort(&self) {
self.0.abort();
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn abort(&self) {
self.abort.abort();
}
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
pub(crate) fn is_finished(&self) -> bool {
self.0.is_finished()
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn is_finished(&self) -> bool {
self.finished.get()
}
#[cfg(all(not(target_arch = "wasm32"), feature = "runtime-tokio"))]
pub(crate) async fn join(self) {
let _ = self.0.await;
}
#[cfg(target_arch = "wasm32")]
pub(crate) async fn join(self) {
let _ = self.completed.await;
}
}
#[cfg(all(test, not(target_arch = "wasm32"), feature = "runtime-tokio"))]
mod tests {
use std::future::pending;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use super::{Runtime, TokioRuntime};
struct DropSignal(Arc<AtomicBool>);
impl Drop for DropSignal {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
#[tokio::test]
async fn aborting_a_task_then_joining_waits_for_its_future_to_drop() {
let dropped = Arc::new(AtomicBool::new(false));
let signal = Arc::clone(&dropped);
let (started_tx, started_rx) = tokio::sync::oneshot::channel();
let runtime = TokioRuntime;
let handle = runtime.spawn(async move {
let _signal = DropSignal(signal);
let _ = started_tx.send(());
pending::<()>().await;
});
started_rx.await.expect("task starts before it is aborted");
handle.abort();
handle.join().await;
assert!(dropped.load(Ordering::SeqCst));
}
#[tokio::test]
async fn yield_now_hands_execution_back_to_the_runtime() {
let (tx, mut rx) = tokio::sync::oneshot::channel();
let handle = TokioRuntime.spawn(async move {
TokioRuntime.yield_now().await;
let _ = tx.send(());
});
handle.join().await;
assert!(
rx.try_recv().is_ok(),
"the yielding task completes after yielding"
);
}
}