#![forbid(unsafe_code)]
#![warn(missing_docs, missing_debug_implementations)]
use std::{
future::Future,
panic::catch_unwind,
pin::Pin,
task::{Context, Poll},
thread,
};
use multitask::Executor;
use once_cell::sync::Lazy;
#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"]
#[derive(Debug)]
pub struct Task<T>(multitask::Task<T>);
impl<T> Task<T> {
pub fn spawn<F>(future: F) -> Task<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
static EXECUTOR: Lazy<Executor> = Lazy::new(|| {
for _ in 0..2 {
thread::spawn(|| {
enter(|| {
let (p, u) = parking::pair();
let ticker = EXECUTOR.ticker(move || drop(u.unpark()));
loop {
if let Ok(false) = catch_unwind(|| ticker.tick()) {
p.park();
}
}
})
});
}
Executor::new()
});
Task(EXECUTOR.spawn(future))
}
pub fn detach(self) {
self.0.detach();
}
pub async fn cancel(self) -> Option<T> {
self.0.cancel().await
}
}
impl<T> Future for Task<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
fn enter<T>(f: impl FnOnce() -> T) -> T {
use std::cell::Cell;
use tokio::runtime::Runtime;
thread_local! {
static NESTING: Cell<usize> = Cell::new(0);
}
static RT: Lazy<Runtime> = Lazy::new(|| Runtime::new().expect("cannot initialize tokio"));
NESTING.with(|nesting| {
let res = if nesting.get() == 0 {
nesting.replace(1);
RT.enter(f)
} else {
nesting.replace(nesting.get() + 1);
f()
};
nesting.replace(nesting.get() - 1);
res
})
}