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 f317e429..31385caf 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ 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)); +}