async_ffi/
lib.rs

1//! # FFI-compatible [`Future`][`std::future::Future`]s
2//!
3//! Rust currently doesn't provide stable ABI nor stable layout of related structs like
4//! `dyn Future` or `Waker`.
5//! With this crate, we can wrap async blocks or async functions to make a `Future` FFI-safe.
6//!
7//! [`FfiFuture`] provides the same functionality as `Box<dyn Future<Output = T> + Send>` but
8//! it's FFI-compatible, aka. `repr(C)`. Any `Future<Output = T> + Send + 'static` can be converted
9//! into [`FfiFuture`] by calling [`into_ffi`][`FutureExt::into_ffi`] on it, after `use`ing the
10//! trait [`FutureExt`].
11//!
12//! [`FfiFuture`] implements `Future<Output = T> + Send`. You can `await` a [`FfiFuture`] just like
13//! a normal `Future` to wait and get the output.
14//!
15//! For non-[`Send`] or non-`'static` futures, see the section
16//! [Variants of `FfiFuture`](#variants-of-ffifuture) below.
17//!
18//! ## Examples
19//!
20//! Provide some async functions in library: (plugin side)
21//! ```
22//! # async fn do_some_io(_: u32) -> u32 { 0 }
23//! # async fn do_some_sleep(_: u32) {}
24//! // Compile with `crate-type = ["cdylib"]`.
25//! use async_ffi::{FfiFuture, FutureExt};
26//!
27//! #[no_mangle]
28//! pub extern "C" fn work(arg: u32) -> FfiFuture<u32> {
29//!     async move {
30//!         let ret = do_some_io(arg).await;
31//!         do_some_sleep(42).await;
32//!         ret
33//!     }
34//!     .into_ffi()
35//! }
36//! ```
37//!
38//! Execute async functions from external library: (host or executor side)
39//! ```
40//! use async_ffi::{FfiFuture, FutureExt};
41//!
42//! // #[link(name = "myplugin...")]
43//! extern "C" {
44//!     #[no_mangle]
45//!     fn work(arg: u32) -> FfiFuture<u32>;
46//! }
47//!
48//! async fn run_work(arg: u32) -> u32 {
49//!     unsafe { work(arg).await }
50//! }
51//! ```
52//!
53//! ## Proc-macro helpers
54//! If you enable the feature `macros` (disabled by default), an attribute-like procedural macro
55#![cfg_attr(not(feature = "macros"), doc = r"`async_ffi`")]
56#![cfg_attr(feature = "macros", doc = r"[`async_ffi`]")]
57//! is available at top-level. See its own documentation for details.
58//!
59//! With the macro, the example above can be simplified to:
60//! ```
61//! # #[cfg(feature = "macros")] {
62//! # async fn do_some_io(_: u32) -> u32 { 0 }
63//! # async fn do_some_sleep(_: u32) {}
64//! use async_ffi::async_ffi;
65//!
66//! #[no_mangle]
67//! #[async_ffi]
68//! pub async extern "C" fn work(arg: u32) -> u32 {
69//!     let ret = do_some_io(arg).await;
70//!     do_some_sleep(42).await;
71//!     ret
72//! }
73//! # }
74//! ```
75//!
76//! ## Panics
77//!
78//! You should know that
79//! [unwinding across an FFI boundary is Undefined Behaviour](https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-panics).
80//!
81//! ### Panic in `Future::poll`
82//!
83//! Since the body of `async fn` is translated to [`Future::poll`] by the compiler, the `poll`
84//! method is likely to panic. If this happen, the wrapped [`FfiFuture`] will catch unwinding
85//! with [`std::panic::catch_unwind`], returning [`FfiPoll::Panicked`] to cross the FFI boundary.
86//! And the other side (usually the plugin host) will get this value in the implementation of
87//! `<FfiFuture<T> as std::future::Future>::poll`, and explicit propagate the panic,
88//! just like [`std::sync::Mutex`]'s poisoning mechanism.
89//!
90//! ### Panic in `Future::drop` or any waker vtable functions `Waker::*`
91//!
92//! Unfortunately, this is very difficult to handle since drop cleanup and `Waker` functions are
93//! expected to be infallible. If these functions panic, we would just call [`std::process::abort`]
94//! to terminate the whole program.
95//!
96//! ## Variants of `FfiFuture`
97//!
98//! There are a few variants of [`FfiFuture`]. The table below shows their corresponding `std`
99//! type.
100//!
101//! | Type                                                          | The corresponding `std` type                   |
102//! |---------------------------------------------------------------|------------------------------------------------|
103//! | [`FfiFuture<T>`]                                              | `Box<dyn Future<Output = T> + Send + 'static>` |
104//! | [`LocalFfiFuture<T>`]                                         | `Box<dyn Future<Output = T> + 'static>`        |
105//! | [`BorrowingFfiFuture<'a, T>`][`BorrowingFfiFuture`]           | `Box<dyn Future<Output = T> + Send + 'a>`      |
106//! | [`LocalBorrowingFfiFuture<'a, T>`][`LocalBorrowingFfiFuture`] | `Box<dyn Future<Output = T> + 'a>`             |
107//!
108//! All of these variants are ABI-compatible to each other, since lifetimes and [`Send`] cannot be
109//! represented by the C ABI. These bounds are only checked in the Rust side. It's your duty to
110//! guarantee that the [`Send`] and lifetime bounds are respected in the foreign code of your
111//! external `fn`s.
112//!
113//! ## Performance and cost
114//!
115//! The conversion between `FfiFuture` and orinary `Future` is not cost-free. Currently
116//! [`FfiFuture::new`] and its alias [`FutureExt::into_ffi`] does one extra allocation.
117//! When `poll`ing an `FfiFuture`, the `Waker` supplied does one extra allocation when `clone`d.
118//!
119//! It's recommended to only wrap you `async` code once right at the FFI boundary, and use ordinary
120//! `Future` everywhere else. It's usually not a good idea to use `FfiFuture` in methods, trait
121//! methods, or generic codes.
122//!
123//! ## [`abi-stable`] support
124//!
125//! If you want to use this crate with [`abi-stable`] interfaces. You can enable the feature flag
126//! `abi_stable` (disabled by default), then the struct `FfiFuture` and friends would derive
127//! `abi_stable::StableAbi`.
128//!
129//! [`abi-stable`]: https://github.com/rodrimati1992/abi_stable_crates/
130#![deny(missing_docs)]
131#![cfg_attr(docsrs, feature(doc_cfg))]
132use std::{
133    convert::{TryFrom, TryInto},
134    fmt,
135    future::Future,
136    marker::PhantomData,
137    mem::{self, ManuallyDrop},
138    pin::Pin,
139    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
140};
141
142#[cfg(feature = "macros")]
143#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
144pub use macros::async_ffi;
145
146/// The ABI version of [`FfiFuture`] and all variants.
147/// Every non-compatible ABI change will increase this number, as well as the crate major version.
148pub const ABI_VERSION: u32 = 2;
149
150/// The FFI compatible [`std::task::Poll`]
151///
152/// [`std::task::Poll`]: std::task::Poll
153#[repr(C, u8)]
154#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
155pub enum FfiPoll<T> {
156    /// Represents that a value is immediately ready.
157    Ready(T),
158    /// Represents that a value is not ready yet.
159    Pending,
160    /// Represents that the future panicked
161    Panicked,
162}
163
164/// Abort on drop with a message.
165struct DropBomb(&'static str);
166
167impl DropBomb {
168    fn with<T, F: FnOnce() -> T>(message: &'static str, f: F) -> T {
169        let bomb = DropBomb(message);
170        let ret = f();
171        mem::forget(bomb);
172        ret
173    }
174}
175
176impl Drop for DropBomb {
177    fn drop(&mut self) {
178        use std::io::Write;
179        // Use `Stderr::write_all` instead of `eprintln!` to avoid panicking here.
180        let mut stderr = std::io::stderr();
181        let _ = stderr.write_all(b"async-ffi: abort due to panic across the FFI boundary in ");
182        let _ = stderr.write_all(self.0.as_bytes());
183        let _ = stderr.write_all(b"\n");
184        std::process::abort();
185    }
186}
187
188/// The FFI compatible [`std::task::Context`]
189///
190/// [`std::task::Context`]: std::task::Context
191#[repr(C)]
192#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
193pub struct FfiContext<'a> {
194    /// This waker is passed as borrow semantic.
195    /// The external fn must not `drop` or `wake` it.
196    waker: *const FfiWakerBase,
197    /// Lets the compiler know that this references the FfiWaker and should not outlive it
198    _marker: PhantomData<&'a FfiWakerBase>,
199}
200
201impl<'a> FfiContext<'a> {
202    /// SAFETY: Vtable functions of `waker` are unsafe, the caller must ensure they have a
203    /// sane behavior as a Waker. `with_context` relies on this to be safe.
204    unsafe fn new(waker: &'a FfiWaker) -> Self {
205        Self {
206            waker: (waker as *const FfiWaker).cast::<FfiWakerBase>(),
207            _marker: PhantomData,
208        }
209    }
210
211    /// Runs a closure with the [`FfiContext`] as a normal [`std::task::Context`].
212    ///
213    /// [`std::task::Context`]: std::task::Context
214    pub fn with_context<T, F: FnOnce(&mut Context) -> T>(&mut self, closure: F) -> T {
215        // C vtable functions are considered from FFI and they are not expected to unwind, so we don't
216        // need to wrap them with `DropBomb`.
217        static RUST_WAKER_VTABLE: RawWakerVTable = {
218            unsafe fn clone(data: *const ()) -> RawWaker {
219                let waker = data.cast::<FfiWaker>();
220                let cloned = ((*(*waker).base.vtable).clone)(waker.cast());
221                RawWaker::new(cloned.cast(), &RUST_WAKER_VTABLE)
222            }
223            unsafe fn wake(data: *const ()) {
224                let waker = data.cast::<FfiWaker>();
225                ((*(*waker).base.vtable).wake)(waker.cast());
226            }
227            unsafe fn wake_by_ref(data: *const ()) {
228                let waker = data.cast::<FfiWaker>();
229                ((*(*waker).base.vtable).wake_by_ref)(waker.cast());
230            }
231            unsafe fn drop(data: *const ()) {
232                let waker = data.cast::<FfiWaker>();
233                ((*(*waker).base.vtable).drop)(waker.cast());
234            }
235            RawWakerVTable::new(clone, wake, wake_by_ref, drop)
236        };
237
238        // SAFETY: `waker`'s vtable functions must have sane behaviors, this is the contract of
239        // `FfiContext::new`.
240        let waker = unsafe {
241            // The waker reference is borrowed from external context. We must not call drop on it.
242            ManuallyDrop::new(Waker::from_raw(RawWaker::new(
243                self.waker.cast(),
244                &RUST_WAKER_VTABLE,
245            )))
246        };
247        let mut ctx = Context::from_waker(&waker);
248
249        closure(&mut ctx)
250    }
251}
252
253/// Helper trait to provide convenience methods for converting a [`std::task::Context`] to [`FfiContext`]
254///
255/// [`std::task::Context`]: std::task::Context
256pub trait ContextExt {
257    /// Runs a closure with the [`std::task::Context`] as a [`FfiContext`].
258    ///
259    /// [`std::task::Context`]: std::task::Context
260    fn with_ffi_context<T, F: FnOnce(&mut FfiContext) -> T>(&mut self, closure: F) -> T;
261}
262
263impl<'a> ContextExt for Context<'a> {
264    fn with_ffi_context<T, F: FnOnce(&mut FfiContext) -> T>(&mut self, closure: F) -> T {
265        static C_WAKER_VTABLE_OWNED: FfiWakerVTable = {
266            unsafe extern "C" fn clone(data: *const FfiWakerBase) -> *const FfiWakerBase {
267                DropBomb::with("Waker::clone", || {
268                    let data = data as *mut FfiWaker;
269                    let waker: Waker = (*(*data).waker.owned).clone();
270                    Box::into_raw(Box::new(FfiWaker {
271                        base: FfiWakerBase {
272                            vtable: &C_WAKER_VTABLE_OWNED,
273                        },
274                        waker: WakerUnion {
275                            owned: ManuallyDrop::new(waker),
276                        },
277                    }))
278                    .cast()
279                })
280            }
281            // In this case, we must own `data`. This can only happen when the `data` pointer is returned from `clone`.
282            // Thus the it is `Box<FfiWaker>`.
283            unsafe extern "C" fn wake(data: *const FfiWakerBase) {
284                DropBomb::with("Waker::wake", || {
285                    let b = Box::from_raw(data as *mut FfiWaker);
286                    ManuallyDrop::into_inner(b.waker.owned).wake();
287                });
288            }
289            unsafe extern "C" fn wake_by_ref(data: *const FfiWakerBase) {
290                DropBomb::with("Waker::wake_by_ref", || {
291                    let data = data as *mut FfiWaker;
292                    (*data).waker.owned.wake_by_ref();
293                });
294            }
295            // Same as `wake`.
296            unsafe extern "C" fn drop(data: *const FfiWakerBase) {
297                DropBomb::with("Waker::drop", || {
298                    let mut b = Box::from_raw(data as *mut FfiWaker);
299                    ManuallyDrop::drop(&mut b.waker.owned);
300                    mem::drop(b);
301                });
302            }
303            FfiWakerVTable {
304                clone,
305                wake,
306                wake_by_ref,
307                drop,
308            }
309        };
310
311        static C_WAKER_VTABLE_REF: FfiWakerVTable = {
312            unsafe extern "C" fn clone(data: *const FfiWakerBase) -> *const FfiWakerBase {
313                DropBomb::with("Waker::clone", || {
314                    let data = data as *mut FfiWaker;
315                    let waker: Waker = (*(*data).waker.reference).clone();
316                    Box::into_raw(Box::new(FfiWaker {
317                        base: FfiWakerBase {
318                            vtable: &C_WAKER_VTABLE_OWNED,
319                        },
320                        waker: WakerUnion {
321                            owned: ManuallyDrop::new(waker),
322                        },
323                    }))
324                    .cast()
325                })
326            }
327            unsafe extern "C" fn wake_by_ref(data: *const FfiWakerBase) {
328                DropBomb::with("Waker::wake_by_ref", || {
329                    let data = data as *mut FfiWaker;
330                    (*(*data).waker.reference).wake_by_ref();
331                });
332            }
333            unsafe extern "C" fn unreachable(_: *const FfiWakerBase) {
334                std::process::abort();
335            }
336            FfiWakerVTable {
337                clone,
338                wake: unreachable,
339                wake_by_ref,
340                drop: unreachable,
341            }
342        };
343
344        let waker = FfiWaker {
345            base: FfiWakerBase {
346                vtable: &C_WAKER_VTABLE_REF,
347            },
348            waker: WakerUnion {
349                reference: self.waker(),
350            },
351        };
352
353        // SAFETY: The behavior of `waker` is sane since we forward them to another valid Waker.
354        // That waker must be safe to use due to the contract of `RawWaker::new`.
355        let mut ctx = unsafe { FfiContext::new(&waker) };
356
357        closure(&mut ctx)
358    }
359}
360
361// Inspired by Gary Guo (github.com/nbdd0121)
362//
363// The base is what can be accessed through FFI, and the regular struct contains
364// internal data (the original waker).
365#[repr(C)]
366#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
367struct FfiWakerBase {
368    vtable: *const FfiWakerVTable,
369}
370#[repr(C)]
371struct FfiWaker {
372    base: FfiWakerBase,
373    waker: WakerUnion,
374}
375
376#[repr(C)]
377union WakerUnion {
378    reference: *const Waker,
379    owned: ManuallyDrop<Waker>,
380    unknown: (),
381}
382
383#[derive(PartialEq, Eq, Hash, Clone, Copy)]
384#[repr(C)]
385#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
386struct FfiWakerVTable {
387    clone: unsafe extern "C" fn(*const FfiWakerBase) -> *const FfiWakerBase,
388    wake: unsafe extern "C" fn(*const FfiWakerBase),
389    wake_by_ref: unsafe extern "C" fn(*const FfiWakerBase),
390    drop: unsafe extern "C" fn(*const FfiWakerBase),
391}
392
393/// The FFI compatible future type with [`Send`] bound.
394///
395/// See [module level documentation](`crate`) for more details.
396#[repr(transparent)]
397#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
398pub struct BorrowingFfiFuture<'a, T>(LocalBorrowingFfiFuture<'a, T>);
399
400/// The FFI compatible future type with [`Send`] bound and `'static` lifetime,
401/// which is needed for most use cases.
402///
403/// See [module level documentation](`crate`) for more details.
404pub type FfiFuture<T> = BorrowingFfiFuture<'static, T>;
405
406/// Helper trait to provide conversion from `Future` to [`FfiFuture`] or [`LocalFfiFuture`].
407///
408/// See [module level documentation](`crate`) for more details.
409pub trait FutureExt: Future + Sized {
410    /// Convert a Rust `Future` implementing [`Send`] into a FFI-compatible [`FfiFuture`].
411    fn into_ffi<'a>(self) -> BorrowingFfiFuture<'a, Self::Output>
412    where
413        Self: Send + 'a,
414    {
415        BorrowingFfiFuture::new(self)
416    }
417
418    /// Convert a Rust `Future` into a FFI-compatible [`LocalFfiFuture`].
419    fn into_local_ffi<'a>(self) -> LocalBorrowingFfiFuture<'a, Self::Output>
420    where
421        Self: 'a,
422    {
423        LocalBorrowingFfiFuture::new(self)
424    }
425}
426
427impl<F> FutureExt for F where F: Future + Sized {}
428
429/// Represents that the poll function panicked.
430#[derive(Debug)]
431pub struct PollPanicked {
432    _private: (),
433}
434
435impl fmt::Display for PollPanicked {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        f.write_str("FFI poll function panicked")
438    }
439}
440
441impl std::error::Error for PollPanicked {}
442
443impl<T> FfiPoll<T> {
444    /// Converts a [`std::task::Poll`] to the [`FfiPoll`].
445    ///
446    /// [`std::task::Poll`]: std::task::Poll
447    pub fn from_poll(poll: Poll<T>) -> Self {
448        match poll {
449            Poll::Ready(r) => Self::Ready(r),
450            Poll::Pending => Self::Pending,
451        }
452    }
453
454    /// Try to convert a [`FfiPoll`] back to the [`std::task::Poll`].
455    ///
456    /// # Errors
457    /// Returns `Err(PollPanicked)` if the result indicates the poll function panicked.
458    ///
459    /// [`std::task::Poll`]: std::task::Poll
460    pub fn try_into_poll(self) -> Result<Poll<T>, PollPanicked> {
461        match self {
462            Self::Ready(r) => Ok(Poll::Ready(r)),
463            Self::Pending => Ok(Poll::Pending),
464            Self::Panicked => Err(PollPanicked { _private: () }),
465        }
466    }
467}
468
469impl<T> From<Poll<T>> for FfiPoll<T> {
470    fn from(poll: Poll<T>) -> Self {
471        Self::from_poll(poll)
472    }
473}
474
475impl<T> TryFrom<FfiPoll<T>> for Poll<T> {
476    type Error = PollPanicked;
477
478    fn try_from(ffi_poll: FfiPoll<T>) -> Result<Self, PollPanicked> {
479        ffi_poll.try_into_poll()
480    }
481}
482
483impl<'a, T> BorrowingFfiFuture<'a, T> {
484    /// Convert an [`std::future::Future`] implementing [`Send`] into a FFI-compatible [`FfiFuture`].
485    ///
486    /// Usually [`FutureExt::into_ffi`] is preferred and is identical to this method.
487    pub fn new<F: Future<Output = T> + Send + 'a>(fut: F) -> Self {
488        Self(LocalBorrowingFfiFuture::new(fut))
489    }
490}
491
492// SAFETY: This is safe since we allow only `Send` Future in `FfiFuture::new`.
493unsafe impl<T> Send for BorrowingFfiFuture<'_, T> {}
494
495impl<T> Future for BorrowingFfiFuture<'_, T> {
496    type Output = T;
497
498    fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
499        Pin::new(&mut self.0).poll(ctx)
500    }
501}
502
503/// The FFI compatible future type without [`Send`] bound.
504///
505/// Non-[`Send`] `Future`s can only be converted into [`LocalFfiFuture`]. It is not able to be
506/// `spawn`ed in a multi-threaded runtime, but is useful for thread-local futures, single-threaded
507/// runtimes, or single-threaded targets like `wasm32-unknown-unknown`.
508///
509/// See [module level documentation](`crate`) for more details.
510#[repr(C)]
511#[cfg_attr(feature = "abi_stable", derive(abi_stable::StableAbi))]
512pub struct LocalBorrowingFfiFuture<'a, T> {
513    fut_ptr: *mut (),
514    poll_fn: unsafe extern "C" fn(fut_ptr: *mut (), context_ptr: *mut FfiContext) -> FfiPoll<T>,
515    drop_fn: unsafe extern "C" fn(*mut ()),
516    _marker: PhantomData<&'a ()>,
517}
518
519/// The FFI compatible future type without `Send` bound but with `'static` lifetime.
520///
521/// See [module level documentation](`crate`) for more details.
522pub type LocalFfiFuture<T> = LocalBorrowingFfiFuture<'static, T>;
523
524impl<'a, T> LocalBorrowingFfiFuture<'a, T> {
525    /// Convert an [`std::future::Future`] into a FFI-compatible [`LocalFfiFuture`].
526    ///
527    /// Usually [`FutureExt::into_local_ffi`] is preferred and is identical to this method.
528    pub fn new<F: Future<Output = T> + 'a>(fut: F) -> Self {
529        unsafe extern "C" fn poll_fn<F: Future>(
530            fut_ptr: *mut (),
531            context_ptr: *mut FfiContext,
532        ) -> FfiPoll<F::Output> {
533            // The poll fn is likely to panic since it contains most of user logic.
534            match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
535                let fut_pin = Pin::new_unchecked(&mut *fut_ptr.cast::<F>());
536                (*context_ptr).with_context(|ctx| F::poll(fut_pin, ctx))
537            })) {
538                Ok(p) => p.into(),
539                Err(payload) => {
540                    // Panic payload may panic when dropped, ensure not propagate it.
541                    // https://github.com/rust-lang/rust/issues/86027
542                    DropBomb::with("drop of panic payload from Future::poll", move || {
543                        drop(payload);
544                    });
545                    FfiPoll::Panicked
546                }
547            }
548        }
549
550        unsafe extern "C" fn drop_fn<T>(ptr: *mut ()) {
551            DropBomb::with("Future::drop", || {
552                drop(Box::from_raw(ptr.cast::<T>()));
553            });
554        }
555
556        let ptr = Box::into_raw(Box::new(fut));
557        Self {
558            fut_ptr: ptr.cast(),
559            poll_fn: poll_fn::<F>,
560            drop_fn: drop_fn::<F>,
561            _marker: PhantomData,
562        }
563    }
564}
565
566impl<T> Drop for LocalBorrowingFfiFuture<'_, T> {
567    fn drop(&mut self) {
568        // SAFETY: This is safe since `drop_fn` is construct from `LocalBorrowingFfiFuture::new`
569        // and is a dropper
570        // `LocalBorrowingFfiFuture::new` and they are just a Box pointer and its corresponding
571        // dropper.
572        unsafe { (self.drop_fn)(self.fut_ptr) };
573    }
574}
575
576impl<T> Future for LocalBorrowingFfiFuture<'_, T> {
577    type Output = T;
578
579    fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
580        // SAFETY: This is safe since `poll_fn` is constructed from `LocalBorrowingFfiFuture::new`
581        // and it just forwards to the original safe `Future::poll`.
582        ctx.with_ffi_context(|ctx| unsafe { (self.poll_fn)(self.fut_ptr, ctx) })
583            .try_into()
584            // Propagate panic from FFI.
585            .unwrap_or_else(|_| panic!("FFI future panicked"))
586    }
587}