From 94e8e54b38a475908452d23ec06e8b715d07ebb0 Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Wed, 3 Aug 2022 10:12:35 -0600 Subject: [PATCH] feat(napi): call sync functions within tokio runtime (#1242) --- crates/backend/src/codegen/fn.rs | 10 ++++++---- crates/napi/src/lib.rs | 10 ++++++++++ crates/napi/src/tokio_runtime.rs | 10 ++++++++++ examples/napi/Cargo.toml | 1 + examples/napi/__test__/typegen.spec.ts.md | 1 + examples/napi/__test__/typegen.spec.ts.snap | Bin 3409 -> 3418 bytes examples/napi/__test__/values.spec.ts | 7 +++++++ examples/napi/index.d.ts | 1 + examples/napi/src/lib.rs | 1 + examples/napi/src/tokio_outside_async.rs | 18 ++++++++++++++++++ 10 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 examples/napi/src/tokio_outside_async.rs diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index d1580709..f751b0fa 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -21,10 +21,12 @@ impl TryToTokens for NapiFn { let native_call = if !self.is_async { quote! { - let #receiver_ret_name = { - #receiver(#(#arg_names),*) - }; - #ret + napi::bindgen_prelude::within_runtime_if_available(move || { + let #receiver_ret_name = { + #receiver(#(#arg_names),*) + }; + #ret + }) } } else { let call = if self.is_ret_result { diff --git a/crates/napi/src/lib.rs b/crates/napi/src/lib.rs index 631338bc..476096fc 100644 --- a/crates/napi/src/lib.rs +++ b/crates/napi/src/lib.rs @@ -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 T, T>(f: F) -> T { + f() + } } #[doc(hidden)] diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index e4e197db..2706f7c4 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -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 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, diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 008ec378..e329beb3 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -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" } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index e80fb308..5e3dee5f 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -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␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index f317e4292c04469106b9d59e82237c021e94ebd9..31385caf9201c91330806182ce19ee1381e1871b 100644 GIT binary patch literal 3418 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 literal 3409 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 diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index c997328f..72dc4cd5 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -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))) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index ada4c0fc..b2500499 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -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 diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index 3cf73749..189f1eef 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -39,4 +39,5 @@ mod string; mod symbol; mod task; mod threadsafe_function; +mod tokio_outside_async; mod typed_array; diff --git a/examples/napi/src/tokio_outside_async.rs b/examples/napi/src/tokio_outside_async.rs new file mode 100644 index 00000000..b82a308b --- /dev/null +++ b/examples/napi/src/tokio_outside_async.rs @@ -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)); +}