feat(napi): call sync functions within tokio runtime (#1242)
This commit is contained in:
parent
3d2ca94392
commit
94e8e54b38
10 changed files with 55 additions and 4 deletions
|
@ -21,10 +21,12 @@ impl TryToTokens for NapiFn {
|
||||||
|
|
||||||
let native_call = if !self.is_async {
|
let native_call = if !self.is_async {
|
||||||
quote! {
|
quote! {
|
||||||
let #receiver_ret_name = {
|
napi::bindgen_prelude::within_runtime_if_available(move || {
|
||||||
#receiver(#(#arg_names),*)
|
let #receiver_ret_name = {
|
||||||
};
|
#receiver(#(#arg_names),*)
|
||||||
#ret
|
};
|
||||||
|
#ret
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let call = if self.is_ret_result {
|
let call = if self.is_ret_result {
|
||||||
|
|
|
@ -162,6 +162,16 @@ pub mod bindgen_prelude {
|
||||||
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
|
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
|
||||||
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
|
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)]
|
#[doc(hidden)]
|
||||||
|
|
|
@ -59,6 +59,16 @@ where
|
||||||
RT.0.spawn(fut);
|
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)]
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||||
pub fn execute_tokio_future<
|
pub fn execute_tokio_future<
|
||||||
Data: 'static + Send,
|
Data: 'static + Send,
|
||||||
|
|
|
@ -25,6 +25,7 @@ napi-derive = { path = "../../crates/macro", features = ["type-def"] }
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_derive = "1"
|
serde_derive = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
tokio = { version = "1.20.0", features = ["full"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = { path = "../../crates/build" }
|
napi-build = { path = "../../crates/build" }
|
||||||
|
|
|
@ -179,6 +179,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊
|
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void␊
|
||||||
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊
|
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void␊
|
||||||
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊
|
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void␊
|
||||||
|
export function useTokioWithoutAsync(): void␊
|
||||||
export function getBuffer(): Buffer␊
|
export function getBuffer(): Buffer␊
|
||||||
export function appendBuffer(buf: Buffer): Buffer␊
|
export function appendBuffer(buf: Buffer): Buffer␊
|
||||||
export function getEmptyBuffer(): Buffer␊
|
export function getEmptyBuffer(): Buffer␊
|
||||||
|
|
Binary file not shown.
|
@ -103,6 +103,7 @@ import {
|
||||||
receiveObjectWithClassField,
|
receiveObjectWithClassField,
|
||||||
AnotherClassForEither,
|
AnotherClassForEither,
|
||||||
receiveDifferentClass,
|
receiveDifferentClass,
|
||||||
|
useTokioWithoutAsync,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('export const', (t) => {
|
test('export const', (t) => {
|
||||||
|
@ -691,6 +692,12 @@ Napi4Test('await Promise in rust', async (t) => {
|
||||||
t.is(result, fx + 100)
|
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) => {
|
Napi4Test('Promise should reject raw error in rust', async (t) => {
|
||||||
const fxError = new Error('What is Happy Planet')
|
const fxError = new Error('What is Happy Planet')
|
||||||
const err = await t.throwsAsync(() => asyncPlus100(Promise.reject(fxError)))
|
const err = await t.throwsAsync(() => asyncPlus100(Promise.reject(fxError)))
|
||||||
|
|
1
examples/napi/index.d.ts
vendored
1
examples/napi/index.d.ts
vendored
|
@ -169,6 +169,7 @@ export function callThreadsafeFunction(callback: (...args: any[]) => any): void
|
||||||
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void
|
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void
|
||||||
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void
|
export function threadsafeFunctionFatalMode(cb: (...args: any[]) => any): void
|
||||||
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void
|
export function threadsafeFunctionFatalModeError(cb: (...args: any[]) => any): void
|
||||||
|
export function useTokioWithoutAsync(): void
|
||||||
export function getBuffer(): Buffer
|
export function getBuffer(): Buffer
|
||||||
export function appendBuffer(buf: Buffer): Buffer
|
export function appendBuffer(buf: Buffer): Buffer
|
||||||
export function getEmptyBuffer(): Buffer
|
export function getEmptyBuffer(): Buffer
|
||||||
|
|
|
@ -39,4 +39,5 @@ mod string;
|
||||||
mod symbol;
|
mod symbol;
|
||||||
mod task;
|
mod task;
|
||||||
mod threadsafe_function;
|
mod threadsafe_function;
|
||||||
|
mod tokio_outside_async;
|
||||||
mod typed_array;
|
mod typed_array;
|
||||||
|
|
18
examples/napi/src/tokio_outside_async.rs
Normal file
18
examples/napi/src/tokio_outside_async.rs
Normal 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));
|
||||||
|
}
|
Loading…
Reference in a new issue