use std::{
future::Future,
mem::MaybeUninit,
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
};
use crate::{erasedfuture::ErasedFuture, Shared};
trait BlockingFuture<B: BlockingFutureReturn<R>, R>: Future<Output = B> {}
trait BlockingFutureReturn<R> {
fn map_return(self) -> R;
}
trait BlockingFutureFn<R: Send + 'static>: Send + 'static {}
struct BlockingFutureReturnNewType<T: Send + 'static>(T);
impl<T: Send + 'static> From<T> for BlockingFutureReturnNewType<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: Send + 'static> BlockingFutureReturn<T> for BlockingFutureReturnNewType<T> {
fn map_return(self) -> T {
self.0
}
}
impl<T: Send + 'static, E> BlockingFutureReturn<T> for Result<BlockingFutureReturnNewType<T>, E> {
fn map_return(self) -> T {
match self {
Ok(t) => t.0,
Err(_) => panic!("Panic!"),
}
}
}
impl<T, R: BlockingFutureReturn<T>, F: Future<Output = R>> BlockingFuture<R, T> for F {}
impl<F: FnOnce() -> BlockingFutureReturnNewType<R> + Send + 'static, R: Send + 'static>
BlockingFutureFn<R> for F
{
}
#[allow(unused)]
async fn spawn_blocking<
T: Send + 'static,
FN: FnOnce() -> BlockingFutureReturnNewType<T>,
B: BlockingFutureReturn<T>,
F: BlockingFuture<B, T>,
>(
f: fn(FN) -> F,
f2: FN,
) -> T {
let res = f(f2).await;
res.map_return()
}
#[allow(clippy::type_complexity)]
pub struct Spawner {
spawner: fn(
AtomicPtr<()>,
AtomicPtr<()>,
AtomicPtr<()>,
fn(AtomicPtr<()>, AtomicPtr<()>, AtomicPtr<()>),
) -> ErasedFuture<()>,
}
impl Spawner {
#[doc(hidden)]
pub const fn __new(
f: fn(
AtomicPtr<()>,
AtomicPtr<()>,
AtomicPtr<()>,
fn(AtomicPtr<()>, AtomicPtr<()>, AtomicPtr<()>),
) -> ErasedFuture<()>,
) -> Self {
Self { spawner: f }
}
pub(crate) async fn spawn_blocking_map<A: Send, B: Send, F: FnOnce(&mut A) -> B>(
&self,
mut a: A,
f: F,
) -> B {
let input = AtomicPtr::new(&mut a as *mut A as *mut ());
let mut output_storage = MaybeUninit::<B>::uninit();
let output = AtomicPtr::new(output_storage.as_mut_ptr() as *mut ());
let mut f = (AtomicBool::new(false), Some(f));
let context = AtomicPtr::new((&mut f) as *mut (AtomicBool, Option<F>) as *mut ());
(self.spawner)(context, input, output, |context, input, output| {
let input = input.into_inner() as *mut A;
let output = output.into_inner() as *mut B;
unsafe {
let f = (context.into_inner() as *mut (AtomicBool, Option<F>))
.as_mut()
.unwrap();
std::ptr::write(output, (f.1.take().unwrap())(input.as_mut().unwrap()));
f.0.store(true, Ordering::Release);
}
})
.await;
if f.0.load(Ordering::Acquire) {
unsafe { output_storage.assume_init() }
} else {
panic!("Spawned operation panic!ed and did not complete");
}
}
}
macro_rules! const_assert {
($x:expr $(,)?) => {
#[allow(unknown_lints, clippy::eq_op)]
const _: [(); 0 - !{
const ASSERT: bool = $x;
ASSERT
} as usize] = [];
};
}
const_assert!(std::mem::size_of::<Shared<()>>() == std::mem::size_of::<Shared<[usize; 1000]>>());
const_assert!(
std::mem::size_of::<Shared<()>>() == std::mem::size_of::<Shared<&(dyn std::any::Any)>>()
);
#[macro_export]
macro_rules! make_spawner {
($id:path) => {{
use std::sync::atomic::AtomicPtr;
let x = |context: AtomicPtr<()>,
input: AtomicPtr<()>,
output: AtomicPtr<()>,
f: fn(AtomicPtr<()>, AtomicPtr<()>, AtomicPtr<()>)| {
$crate::_ErasedFuturePrivate::new_map(
$id(move || {
f(context, input, output);
}),
drop,
)
};
Spawner::__new(x)
}};
}
#[cfg(test)]
mod test {
use super::*;
use crate::Shared;
use futures::FutureExt;
#[allow(unused)]
#[allow(unconditional_recursion)]
fn ensure_blocking_future<R, B: BlockingFutureReturn<R>, F: BlockingFuture<B, R>>() {
type Empty = BlockingFutureReturnNewType<()>;
ensure_blocking_future::<(), _, tokio::task::JoinHandle<Empty>>();
ensure_blocking_future::<(), _, smol::Task<Empty>>();
ensure_blocking_future::<(), _, async_std::task::JoinHandle<Empty>>();
}
#[allow(unused)]
fn ensure_blocking_fn<R: Send + 'static, F: BlockingFutureFn<R>>(f: F) -> F {
f
}
#[allow(unused)]
fn ensure_blocking_fn_parent() {
let f = || BlockingFutureReturnNewType::<()>(());
let f = ensure_blocking_fn(f);
let _ = tokio::task::spawn_blocking(f);
}
#[allow(unused)]
async fn ensure_blocking_runners() {
let x = spawn_blocking(tokio::task::spawn_blocking, || ().into()).await;
let x = spawn_blocking(smol::unblock, || ().into()).await;
let x = spawn_blocking(async_std::task::spawn_blocking, || ().into()).await;
}
#[allow(unused)]
fn ensure_spawner_send_sync() {
fn ensure_send<T: Sync>() {}
fn ensure_sync<T: Sync>() {}
ensure_send::<Spawner>();
ensure_sync::<Spawner>();
}
#[tokio::test]
async fn test_blocking() {
let spawner = make_spawner!(tokio::task::spawn_blocking);
let out = spawner.spawn_blocking_map(1, |input| *input + 1).await;
assert_eq!(2, out);
}
#[tokio::test]
async fn test_blocking_is_send() {
fn ensure_send<T: Send>(t: T) -> T {
t
}
let spawner = make_spawner!(tokio::task::spawn_blocking);
let f = spawner.spawn_blocking_map(1, |x| *x);
let f = ensure_send(f);
assert_eq!(f.await, 1);
}
#[tokio::test]
async fn test_blocking_panic() {
let spawner = make_spawner!(tokio::task::spawn_blocking);
let out = spawner
.spawn_blocking_map(1, |_| {
panic!("Fail!");
#[allow(unreachable_code)]
{
1
}
})
.catch_unwind()
.await;
assert!(out.is_err());
}
#[tokio::test]
async fn test_blocking_with_lifetime() {
let shared = Shared::new("123");
let lock = spawn_blocking(tokio::task::spawn_blocking, move || {
shared.read_owned().into()
})
.await;
assert_eq!(lock, "123");
}
}