async-ffi 0.5.0

FFI-compatible `Future`s
Documentation
#![allow(clippy::unused_async)]
use async_ffi::FutureExt as _;
use std::{
    future::Future,
    pin::Pin,
    ptr::addr_of_mut,
    rc::Rc,
    sync::Arc,
    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
    time::Duration,
};
use tokio::task;

#[tokio::test]
async fn call_test() {
    async fn foo(x: u32) -> u32 {
        x + 42
    }

    let ret = foo(1).into_ffi().await;
    assert_eq!(ret, 43);
}

#[tokio::test]
async fn complicate_test() {
    let (tx, mut rx) = tokio::sync::mpsc::channel(2);

    tokio::spawn(
        async move {
            tokio::time::sleep(Duration::from_millis(1))
                .into_ffi()
                .await;
            for i in 0..8 {
                tx.send(i).await.unwrap();
            }
        }
        .into_ffi(),
    );

    let mut v = Vec::new();
    while let Some(i) = rx.recv().await {
        v.push(i);
    }
    assert_eq!(v, [0, 1, 2, 3, 4, 5, 6, 7]);
}

#[test]
fn future_drop_test() {
    struct Dropper(Arc<()>);

    let rc = Arc::new(());

    let d = Dropper(rc.clone());
    let fut = async move { drop(d) }.into_ffi();
    assert_eq!(Arc::strong_count(&rc), 2);
    drop(fut);
    assert_eq!(Arc::strong_count(&rc), 1);
}

#[test]
fn waker_test() {
    static VTABLE: RawWakerVTable = {
        unsafe fn log(data: *const (), s: &str) {
            (*(data as *mut ()).cast::<Vec<String>>()).push(s.to_owned());
        }
        unsafe fn clone(data: *const ()) -> RawWaker {
            log(data, "clone");
            RawWaker::new(data, &VTABLE)
        }
        unsafe fn wake(data: *const ()) {
            log(data, "wake");
        }
        unsafe fn wake_by_ref(data: *const ()) {
            log(data, "wake_by_ref");
        }
        unsafe fn drop(data: *const ()) {
            log(data, "drop");
        }
        RawWakerVTable::new(clone, wake, wake_by_ref, drop)
    };

    struct Fut(usize, Option<Waker>);
    impl Future for Fut {
        type Output = i32;
        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            let w = cx.waker();
            match self.0 {
                0 => {
                    w.wake_by_ref();
                    self.0 = 1;
                    Poll::Pending
                }
                1 => {
                    let w2 = w.clone();
                    w2.wake_by_ref();
                    self.1 = Some(w2.clone());
                    drop(w2);
                    self.0 = 2;
                    Poll::Pending
                }
                2 => {
                    self.1.take().unwrap().wake();
                    self.0 = 3;
                    Poll::Ready(42)
                }
                _ => unreachable!(),
            }
        }
    }

    let mut v: Vec<String> = Vec::new();
    let v = addr_of_mut!(v);
    let waker = unsafe { Waker::from_raw(RawWaker::new(v as *const (), &VTABLE)) };
    let mut ctx = Context::from_waker(&waker);

    let look = || std::mem::take(unsafe { &mut *v });

    let mut c_fut = Fut(0, None).into_ffi();
    assert_eq!(Pin::new(&mut c_fut).poll(&mut ctx), Poll::Pending);
    assert_eq!(look(), &["wake_by_ref"]);
    assert_eq!(Pin::new(&mut c_fut).poll(&mut ctx), Poll::Pending);
    assert_eq!(look(), &["clone", "wake_by_ref", "clone", "drop"]);
    assert_eq!(Pin::new(&mut c_fut).poll(&mut ctx), Poll::Ready(42));
    assert_eq!(look(), &["wake"]);
}

#[tokio::test]
async fn non_send_future_test() {
    async fn foo(x: u32) -> u32 {
        let a = Rc::new(x);
        task::yield_now().await;
        *a + 42
    }

    let fut = foo(1).into_local_ffi();

    let local = task::LocalSet::new();
    let ret = local
        .run_until(async move { task::spawn_local(fut).await.unwrap() })
        .await;

    assert_eq!(ret, 43);
}

#[tokio::test]
async fn panic_inside_test() {
    let fut = async {
        let _ = std::panic::catch_unwind(|| {
            panic!("already caught inside");
        });
        42
    }
    .into_ffi();
    assert_eq!(fut.await, 42);
}

#[tokio::test]
#[should_panic = "FFI future panicked"]
async fn panic_propagate_test() {
    async {
        panic!("not caught inside");
    }
    .into_ffi()
    .await;
}

#[cfg(feature = "macros")]
#[tokio::test]
async fn macros() {
    #[async_ffi::async_ffi]
    async fn foo(x: u32) -> u32 {
        x + 42
    }

    const _: fn(u32) -> async_ffi::FfiFuture<u32> = foo;

    let ret = foo(1).await;
    assert_eq!(ret, 43);
}