use std::{
alloc::{alloc, handle_alloc_error, Layout},
future::Future,
marker::PhantomData,
mem::transmute,
pin::Pin,
ptr::{metadata, NonNull},
sync::atomic::{AtomicBool, Ordering},
task::{Context, Poll},
};
use tokio::{
runtime::{Handle, RuntimeFlavor},
task::{block_in_place, AbortHandle, Id, JoinSet},
};
pub(crate) fn split_spawn_task<'scope, F, T>(
join_set: &mut JoinSet<()>,
task: F,
) -> (Id, FutureHolder<'scope, T>)
where
F: Future<Output = T> + Send + 'scope,
T: Send + 'scope,
{
let task_wrapper = WriteOutput::new(task);
let allocation_info = allocate_future_block(task_wrapper);
let weak_future = unsafe { WeakFuture::new(allocation_info.alive, allocation_info.future) };
let handle = join_set.spawn(weak_future);
let future_holder = FutureHolder::new(handle.clone(), allocation_info);
(handle.id(), future_holder)
}
struct AllocationInfo<'scope, T> {
base: NonNull<u8>,
alive: NonNull<AtomicBool>,
output: NonNull<Option<T>>,
future: NonNull<dyn Future<Output = ()> + Send + 'scope>,
layout: Layout,
}
fn allocate_future_block<'scope, F, T>(
mut task_wrapper: WriteOutput<F, T>,
) -> AllocationInfo<'scope, T>
where
F: Future<Output = T> + Send + 'scope,
T: Send + 'scope,
{
let source_ptr =
NonNull::from(&mut task_wrapper) as NonNull<dyn Future<Output = ()> + Send + 'scope>;
let task_metadata = metadata(source_ptr.as_ptr());
let layout = Layout::new::<AtomicBool>();
let (layout, output_offset) = layout
.extend(Layout::new::<Option<T>>().pad_to_align())
.unwrap();
let (layout, future_offset) = layout
.extend(task_metadata.layout().pad_to_align())
.unwrap();
let allocation = unsafe { alloc(layout.pad_to_align()) };
let allocation = NonNull::new(allocation).unwrap_or_else(|| handle_alloc_error(layout));
let alive_ptr: NonNull<AtomicBool> = allocation.cast();
unsafe { alive_ptr.write(AtomicBool::new(true)) };
let output_ptr: NonNull<Option<T>> = unsafe { allocation.add(output_offset).cast() };
unsafe { task_wrapper.set_output_ptr(output_ptr) };
let future_byte_ptr = unsafe { allocation.add(future_offset) };
let future_ptr: NonNull<dyn Future<Output = ()> + Send + 'scope> =
NonNull::from_raw_parts(future_byte_ptr, task_metadata);
unsafe {
std::ptr::copy_nonoverlapping::<u8>(
source_ptr.as_ptr().cast(),
future_ptr.as_ptr().cast(),
task_metadata.size_of(),
);
}
std::mem::forget(task_wrapper);
AllocationInfo {
base: allocation,
alive: alive_ptr,
output: output_ptr,
future: future_ptr,
layout,
}
}
pub struct WriteOutput<F, T> {
future: F,
output_ptr: Option<NonNull<Option<T>>>,
}
unsafe impl<F, T> Send for WriteOutput<F, T> {}
impl<F, T> WriteOutput<F, T> {
pub fn new(future: F) -> Self {
Self {
future,
output_ptr: None,
}
}
pub unsafe fn set_output_ptr(&mut self, ptr: NonNull<Option<T>>) {
self.output_ptr = Some(ptr);
}
}
impl<F, T> Future for WriteOutput<F, T>
where
F: Future<Output = T>,
{
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = unsafe { self.get_unchecked_mut() };
let fut = unsafe { Pin::new_unchecked(&mut this.future) };
match fut.poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(output) => {
let ptr = this.output_ptr.expect("output pointer not set");
unsafe { ptr.as_ptr().write(Some(output)) };
Poll::Ready(())
}
}
}
}
pub struct WeakFuture {
alive: NonNull<AtomicBool>,
future: NonNull<dyn Future<Output = ()> + Send + 'static>,
}
unsafe impl Send for WeakFuture {}
impl WeakFuture {
unsafe fn new<'scope>(
alive: NonNull<AtomicBool>,
future: NonNull<dyn Future<Output = ()> + Send + 'scope>,
) -> Self {
let future: NonNull<dyn Future<Output = ()> + Send + 'static> = transmute(future);
Self { alive, future }
}
}
impl Future for WeakFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = unsafe { self.get_unchecked_mut() };
let fut = unsafe { Pin::new_unchecked(this.future.as_mut()) };
fut.poll(cx)
}
}
impl Drop for WeakFuture {
fn drop(&mut self) {
if Handle::current().runtime_flavor() == RuntimeFlavor::MultiThread {
unsafe { self.alive.as_ref().store(false, Ordering::Release) };
}
}
}
pub(crate) struct FutureHolder<'scope, T>
where
T: Send + 'scope,
{
abort_handle: AbortHandle,
allocation: AllocationInfo<'scope, T>,
_marker: PhantomData<&'scope ()>,
}
impl<'scope, T: Send + 'scope> FutureHolder<'scope, T> {
fn new(abort_handle: AbortHandle, allocation: AllocationInfo<'scope, T>) -> Self {
Self {
abort_handle,
allocation,
_marker: PhantomData,
}
}
pub(crate) fn get_output(mut self) -> Option<T> {
unsafe { self.allocation.output.as_mut() }.take()
}
fn spin_until_termination(&self) {
let alive: &AtomicBool = unsafe { self.allocation.base.cast().as_ref() };
if alive.load(Ordering::Acquire) {
block_in_place(|| {
while alive.load(Ordering::Acquire) {
std::hint::spin_loop();
}
});
}
}
pub(crate) fn abort(&self) {
self.abort_handle.abort();
}
}
impl<'scope, T: Send + 'scope> Drop for FutureHolder<'scope, T> {
fn drop(&mut self) {
self.abort_handle.abort();
if Handle::current().runtime_flavor() == RuntimeFlavor::MultiThread {
self.spin_until_termination();
}
unsafe {
std::ptr::drop_in_place(self.allocation.output.as_ptr());
std::ptr::drop_in_place(self.allocation.future.as_ptr());
std::alloc::dealloc(self.allocation.base.cast().as_ptr(), self.allocation.layout);
}
}
}