use std::future::Future;
use std::time::Duration;
use futures::channel::oneshot;
use crate::error::Error;
use crate::error::Result;
#[cfg(rings_native)]
pub(crate) fn spawn_detached<F>(future: F)
where F: Future<Output = ()> + Send + 'static {
tokio::spawn(future);
}
#[cfg(rings_browser)]
pub(crate) fn spawn_detached<F>(future: F)
where F: Future<Output = ()> + 'static {
wasm_bindgen_futures::spawn_local(future);
}
#[cfg(rings_native)]
pub(crate) async fn run_detached<F, T>(future: F) -> Result<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let (sender, receiver) = oneshot::channel();
spawn_detached(async move {
let output = future.await;
let _ = sender.send(output);
});
receiver
.await
.map_err(|_| Error::DetachedExtensionTaskClosed)
}
#[cfg(rings_browser)]
pub(crate) async fn run_detached<F, T>(future: F) -> Result<T>
where
F: Future<Output = T> + 'static,
T: 'static,
{
let (sender, receiver) = oneshot::channel();
spawn_detached(async move {
let output = future.await;
let _ = sender.send(output);
});
receiver
.await
.map_err(|_| Error::DetachedExtensionTaskClosed)
}
#[cfg(rings_native)]
pub(crate) async fn sleep(duration: Duration) -> Result<()> {
futures_timer::Delay::new(duration).await;
Ok(())
}
#[cfg(rings_browser)]
pub(crate) async fn sleep(duration: Duration) -> Result<()> {
let millis = i32::try_from(duration.as_millis()).unwrap_or(i32::MAX);
rings_core::utils::js_utils::window_sleep(millis)
.await
.map(|_| ())
.map_err(|error| crate::error::Error::JsError(format!("{error:?}")))
}
#[cfg(all(test, rings_native))]
mod tests {
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Notify;
use super::run_detached;
use super::Error;
use super::Result;
#[tokio::test]
async fn test_cancelling_waiter_does_not_cancel_owned_detached_work() {
let started = Arc::new(Notify::new());
let release = Arc::new(Notify::new());
let completed = Arc::new(Notify::new());
let waiter = {
let started = started.clone();
let release = release.clone();
let completed = completed.clone();
tokio::spawn(run_detached(async move {
started.notify_one();
release.notified().await;
completed.notify_one();
}))
};
started.notified().await;
waiter.abort();
let _ = waiter.await;
release.notify_one();
tokio::time::timeout(Duration::from_secs(1), completed.notified())
.await
.expect("owned detached work must outlive its cancelled waiter");
}
#[tokio::test]
async fn test_detached_task_failure_preserves_typed_closed_error() {
let result: Result<()> = run_detached(async {
panic!("injected detached extension task failure");
})
.await;
assert!(matches!(result, Err(Error::DetachedExtensionTaskClosed)));
}
}