#![cfg_attr(not(feature = "macros"), doc = r"`async_ffi`")]
#![cfg_attr(feature = "macros", doc = r"[`async_ffi`]")]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{
convert::{TryFrom, TryInto},
fmt,
future::Future,
marker::PhantomData,
mem::{self, ManuallyDrop},
pin::Pin,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use macros::async_ffi;
pub const ABI_VERSION: u32 = 2;
#[repr(C, u8)]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
pub enum FfiPoll<T> {
Ready(T),
Pending,
Panicked,
}
struct DropBomb(&'static str);
impl DropBomb {
fn with<T, F: FnOnce() -> T>(message: &'static str, f: F) -> T {
let bomb = DropBomb(message);
let ret = f();
mem::forget(bomb);
ret
}
}
impl Drop for DropBomb {
fn drop(&mut self) {
use std::io::Write;
let mut stderr = std::io::stderr();
let _ = stderr.write_all(b"async-ffi: abort due to panic across the FFI boundary in ");
let _ = stderr.write_all(self.0.as_bytes());
let _ = stderr.write_all(b"\n");
std::process::abort();
}
}
#[repr(C)]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
pub struct FfiContext<'a> {
waker: *const FfiWakerBase,
_marker: PhantomData<&'a FfiWakerBase>,
}
impl<'a> FfiContext<'a> {
unsafe fn new(waker: &'a FfiWaker) -> Self {
Self {
waker: (waker as *const FfiWaker).cast::<FfiWakerBase>(),
_marker: PhantomData,
}
}
pub fn with_context<T, F: FnOnce(&mut Context) -> T>(&mut self, closure: F) -> T {
static RUST_WAKER_VTABLE: RawWakerVTable = {
unsafe fn clone(data: *const ()) -> RawWaker {
let waker = data.cast::<FfiWaker>();
let cloned = ((*(*waker).base.vtable).clone)(waker.cast());
RawWaker::new(cloned.cast(), &RUST_WAKER_VTABLE)
}
unsafe fn wake(data: *const ()) {
let waker = data.cast::<FfiWaker>();
((*(*waker).base.vtable).wake)(waker.cast());
}
unsafe fn wake_by_ref(data: *const ()) {
let waker = data.cast::<FfiWaker>();
((*(*waker).base.vtable).wake_by_ref)(waker.cast());
}
unsafe fn drop(data: *const ()) {
let waker = data.cast::<FfiWaker>();
((*(*waker).base.vtable).drop)(waker.cast());
}
RawWakerVTable::new(clone, wake, wake_by_ref, drop)
};
let waker = unsafe {
ManuallyDrop::new(Waker::from_raw(RawWaker::new(
self.waker.cast(),
&RUST_WAKER_VTABLE,
)))
};
let mut ctx = Context::from_waker(&waker);
closure(&mut ctx)
}
}
pub trait ContextExt {
fn with_ffi_context<T, F: FnOnce(&mut FfiContext) -> T>(&mut self, closure: F) -> T;
}
impl<'a> ContextExt for Context<'a> {
fn with_ffi_context<T, F: FnOnce(&mut FfiContext) -> T>(&mut self, closure: F) -> T {
static C_WAKER_VTABLE_OWNED: FfiWakerVTable = {
unsafe extern "C" fn clone(data: *const FfiWakerBase) -> *const FfiWakerBase {
DropBomb::with("Waker::clone", || {
let data = data as *mut FfiWaker;
let waker: Waker = (*(*data).waker.owned).clone();
Box::into_raw(Box::new(FfiWaker {
base: FfiWakerBase {
vtable: &C_WAKER_VTABLE_OWNED,
},
waker: WakerUnion {
owned: ManuallyDrop::new(waker),
},
}))
.cast()
})
}
unsafe extern "C" fn wake(data: *const FfiWakerBase) {
DropBomb::with("Waker::wake", || {
let b = Box::from_raw(data as *mut FfiWaker);
ManuallyDrop::into_inner(b.waker.owned).wake();
});
}
unsafe extern "C" fn wake_by_ref(data: *const FfiWakerBase) {
DropBomb::with("Waker::wake_by_ref", || {
let data = data as *mut FfiWaker;
(*data).waker.owned.wake_by_ref();
});
}
unsafe extern "C" fn drop(data: *const FfiWakerBase) {
DropBomb::with("Waker::drop", || {
let mut b = Box::from_raw(data as *mut FfiWaker);
ManuallyDrop::drop(&mut b.waker.owned);
mem::drop(b);
});
}
FfiWakerVTable {
clone,
wake,
wake_by_ref,
drop,
}
};
static C_WAKER_VTABLE_REF: FfiWakerVTable = {
unsafe extern "C" fn clone(data: *const FfiWakerBase) -> *const FfiWakerBase {
DropBomb::with("Waker::clone", || {
let data = data as *mut FfiWaker;
let waker: Waker = (*(*data).waker.reference).clone();
Box::into_raw(Box::new(FfiWaker {
base: FfiWakerBase {
vtable: &C_WAKER_VTABLE_OWNED,
},
waker: WakerUnion {
owned: ManuallyDrop::new(waker),
},
}))
.cast()
})
}
unsafe extern "C" fn wake_by_ref(data: *const FfiWakerBase) {
DropBomb::with("Waker::wake_by_ref", || {
let data = data as *mut FfiWaker;
(*(*data).waker.reference).wake_by_ref();
});
}
unsafe extern "C" fn unreachable(_: *const FfiWakerBase) {
std::process::abort();
}
FfiWakerVTable {
clone,
wake: unreachable,
wake_by_ref,
drop: unreachable,
}
};
let waker = FfiWaker {
base: FfiWakerBase {
vtable: &C_WAKER_VTABLE_REF,
},
waker: WakerUnion {
reference: self.waker(),
},
};
let mut ctx = unsafe { FfiContext::new(&waker) };
closure(&mut ctx)
}
}
#[repr(C)]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
struct FfiWakerBase {
vtable: *const FfiWakerVTable,
}
#[repr(C)]
struct FfiWaker {
base: FfiWakerBase,
waker: WakerUnion,
}
#[repr(C)]
union WakerUnion {
reference: *const Waker,
owned: ManuallyDrop<Waker>,
unknown: (),
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
#[repr(C)]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
struct FfiWakerVTable {
clone: unsafe extern "C" fn(*const FfiWakerBase) -> *const FfiWakerBase,
wake: unsafe extern "C" fn(*const FfiWakerBase),
wake_by_ref: unsafe extern "C" fn(*const FfiWakerBase),
drop: unsafe extern "C" fn(*const FfiWakerBase),
}
#[repr(transparent)]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
pub struct BorrowingFfiFuture<'a, T>(LocalBorrowingFfiFuture<'a, T>);
pub type FfiFuture<T> = BorrowingFfiFuture<'static, T>;
pub trait FutureExt: Future + Sized {
fn into_ffi<'a>(self) -> BorrowingFfiFuture<'a, Self::Output>
where
Self: Send + 'a,
{
BorrowingFfiFuture::new(self)
}
fn into_local_ffi<'a>(self) -> LocalBorrowingFfiFuture<'a, Self::Output>
where
Self: 'a,
{
LocalBorrowingFfiFuture::new(self)
}
}
impl<F> FutureExt for F where F: Future + Sized {}
#[derive(Debug)]
pub struct PollPanicked {
_private: (),
}
impl fmt::Display for PollPanicked {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FFI poll function panicked")
}
}
impl std::error::Error for PollPanicked {}
impl<T> FfiPoll<T> {
pub fn from_poll(poll: Poll<T>) -> Self {
match poll {
Poll::Ready(r) => Self::Ready(r),
Poll::Pending => Self::Pending,
}
}
pub fn try_into_poll(self) -> Result<Poll<T>, PollPanicked> {
match self {
Self::Ready(r) => Ok(Poll::Ready(r)),
Self::Pending => Ok(Poll::Pending),
Self::Panicked => Err(PollPanicked { _private: () }),
}
}
}
impl<T> From<Poll<T>> for FfiPoll<T> {
fn from(poll: Poll<T>) -> Self {
Self::from_poll(poll)
}
}
impl<T> TryFrom<FfiPoll<T>> for Poll<T> {
type Error = PollPanicked;
fn try_from(ffi_poll: FfiPoll<T>) -> Result<Self, PollPanicked> {
ffi_poll.try_into_poll()
}
}
impl<'a, T> BorrowingFfiFuture<'a, T> {
pub fn new<F: Future<Output = T> + Send + 'a>(fut: F) -> Self {
Self(LocalBorrowingFfiFuture::new(fut))
}
}
unsafe impl<T> Send for BorrowingFfiFuture<'_, T> {}
impl<T> Future for BorrowingFfiFuture<'_, T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(ctx)
}
}
#[repr(C)]
#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
pub struct LocalBorrowingFfiFuture<'a, T> {
fut_ptr: *mut (),
poll_fn: unsafe extern "C" fn(fut_ptr: *mut (), context_ptr: *mut FfiContext) -> FfiPoll<T>,
drop_fn: unsafe extern "C" fn(*mut ()),
_marker: PhantomData<&'a ()>,
}
pub type LocalFfiFuture<T> = LocalBorrowingFfiFuture<'static, T>;
impl<'a, T> LocalBorrowingFfiFuture<'a, T> {
pub fn new<F: Future<Output = T> + 'a>(fut: F) -> Self {
unsafe extern "C" fn poll_fn<F: Future>(
fut_ptr: *mut (),
context_ptr: *mut FfiContext,
) -> FfiPoll<F::Output> {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let fut_pin = Pin::new_unchecked(&mut *fut_ptr.cast::<F>());
(*context_ptr).with_context(|ctx| F::poll(fut_pin, ctx))
})) {
Ok(p) => p.into(),
Err(payload) => {
DropBomb::with("drop of panic payload from Future::poll", move || {
drop(payload);
});
FfiPoll::Panicked
}
}
}
unsafe extern "C" fn drop_fn<T>(ptr: *mut ()) {
DropBomb::with("Future::drop", || {
drop(Box::from_raw(ptr.cast::<T>()));
});
}
let ptr = Box::into_raw(Box::new(fut));
Self {
fut_ptr: ptr.cast(),
poll_fn: poll_fn::<F>,
drop_fn: drop_fn::<F>,
_marker: PhantomData,
}
}
}
impl<T> Drop for LocalBorrowingFfiFuture<'_, T> {
fn drop(&mut self) {
unsafe { (self.drop_fn)(self.fut_ptr) };
}
}
impl<T> Future for LocalBorrowingFfiFuture<'_, T> {
type Output = T;
fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
ctx.with_ffi_context(|ctx| unsafe { (self.poll_fn)(self.fut_ptr, ctx) })
.try_into()
.unwrap_or_else(|_| panic!("FFI future panicked"))
}
}