feat(napi): call sync functions within tokio runtime (#1242)

This commit is contained in:
Jacob Kiesel 2022-08-03 10:12:35 -06:00 committed by GitHub
parent 3d2ca94392
commit 94e8e54b38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 55 additions and 4 deletions

View file

@ -21,10 +21,12 @@ impl TryToTokens for NapiFn {
let native_call = if !self.is_async {
quote! {
napi::bindgen_prelude::within_runtime_if_available(move || {
let #receiver_ret_name = {
#receiver(#(#arg_names),*)
};
#ret
})
}
} else {
let call = if self.is_ret_result {

View file

@ -162,6 +162,16 @@ pub mod bindgen_prelude {
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
};
// This function's signature must be kept in sync with the one in tokio_runtime.rs, otherwise napi
// will fail to compile without the `tokio_rt` feature.
/// If the feature `tokio_rt` has been enabled this will enter the runtime context and
/// then call the provided closure. Otherwise it will just call the provided closure.
#[cfg(not(all(feature = "tokio_rt", feature = "napi4")))]
pub fn within_runtime_if_available<F: FnOnce() -> T, T>(f: F) -> T {
f()
}
}
#[doc(hidden)]

View file

@ -59,6 +59,16 @@ where
RT.0.spawn(fut);
}
// This function's signature must be kept in sync with the one in lib.rs, otherwise napi
// will fail to compile with the `tokio_rt` feature.
/// If the feature `tokio_rt` has been enabled this will enter the runtime context and
/// then call the provided closure. Otherwise it will just call the provided closure.
pub fn within_runtime_if_available<F: FnOnce() -> T, T>(f: F) -> T {
let _rt_guard = RT.0.enter();
f()
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn execute_tokio_future<
Data: 'static + Send,

View file

@ -25,6 +25,7 @@ napi-derive = { path = "../../crates/macro", features = ["type-def"] }
serde = "1"
serde_derive = "1"
serde_json = "1"
tokio = { version = "1.20.0", features = ["full"] }
[build-dependencies]
napi-build = { path = "../../crates/build" }

View file

@ -179,6 +179,7 @@ Generated by [AVA](https://avajs.dev).
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊
export function useTokioWithoutAsync(): void␊
export function getBuffer(): Buffer␊
export function appendBuffer(buf: Buffer): Buffer␊
export function getEmptyBuffer(): Buffer␊

View file

@ -103,6 +103,7 @@ import {
receiveObjectWithClassField,
AnotherClassForEither,
receiveDifferentClass,
useTokioWithoutAsync,
} from '../'
test('export const', (t) => {
@ -691,6 +692,12 @@ Napi4Test('await Promise in rust', async (t) => {
t.is(result, fx + 100)
})
Napi4Test('Run function which uses tokio internally but is not async', (t) => {
useTokioWithoutAsync()
// The prior didn't throw an exception, so it worked.
t.assert(true)
})
Napi4Test('Promise should reject raw error in rust', async (t) => {
const fxError = new Error('What is Happy Planet')
const err = await t.throwsAsync(() => asyncPlus100(Promise.reject(fxError)))

View file

@ -169,6 +169,7 @@ export function callThreadsafeFunction(callback: (...args: any[]) => any): void
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void
export function useTokioWithoutAsync(): void
export function getBuffer(): Buffer
export function appendBuffer(buf: Buffer): Buffer
export function getEmptyBuffer(): Buffer

View file

@ -39,4 +39,5 @@ mod string;
mod symbol;
mod task;
mod threadsafe_function;
mod tokio_outside_async;
mod typed_array;

View file

@ -0,0 +1,18 @@
use std::time::Duration;
use tokio::{sync::oneshot, time::Instant};
#[napi]
pub fn use_tokio_without_async() {
let (sender, receiver) = oneshot::channel();
let handle = tokio::task::spawn(async {
// If this panics, the test failed.
sender.send(true).unwrap();
});
let start = Instant::now();
while !handle.is_finished() {
if start.elapsed() > Duration::from_secs(5) {
panic!("The future never resolved.");
}
}
assert_eq!(receiver.blocking_recv(), Ok(true));
}