diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml index 1ce6ea2a..06f4b4a8 100644 --- a/.github/workflows/test-release.yaml +++ b/.github/workflows/test-release.yaml @@ -631,7 +631,7 @@ jobs: with: operating_system: freebsd version: '14.2' - memory: 8G + memory: 12G cpu_count: 3 environment_variables: 'DEBUG RUSTUP_IO_THREADS' shell: bash diff --git a/crates/napi/src/bindgen_runtime/async_iterator.rs b/crates/napi/src/bindgen_runtime/async_iterator.rs index 350efd19..94c291e3 100644 --- a/crates/napi/src/bindgen_runtime/async_iterator.rs +++ b/crates/napi/src/bindgen_runtime/async_iterator.rs @@ -278,20 +278,19 @@ fn generator_next_fn( }; let env = Env::from_raw(env); - let promise: crate::bindgen_runtime::PromiseRaw<'_, Option> = env - .spawn_future_with_callback(item, |env, value| { - if let Some(v) = value { - let mut obj = Object::new(env.0)?; - obj.set("value", v)?; - obj.set("done", false)?; - Ok(obj) - } else { - let mut obj = Object::new(env.0)?; - obj.set("value", ())?; - obj.set("done", true)?; - Ok(obj) - } - })?; + let promise = env.spawn_future_with_callback(item, |env, value| { + if let Some(v) = value { + let mut obj = Object::new(env.0)?; + obj.set("value", v)?; + obj.set("done", false)?; + Ok(obj) + } else { + let mut obj = Object::new(env.0)?; + obj.set("value", ())?; + obj.set("done", true)?; + Ok(obj) + } + })?; Ok(promise.inner) } diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index c3a61506..f8f91e32 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -1107,7 +1107,7 @@ impl Env { /// Spawn a future with a callback /// So you can access the `Env` and resolved value after the future completed pub fn spawn_future_with_callback< - T: 'static + Send + ToNapiValue, + T: 'static + Send, V: ToNapiValue, F: 'static + Send + Future>, R: 'static + FnOnce(Env, T) -> Result, @@ -1115,7 +1115,7 @@ impl Env { &self, fut: F, callback: R, - ) -> Result> { + ) -> Result> { use crate::tokio_runtime; let promise = tokio_runtime::execute_tokio_future(self.0, fut, move |env, val| unsafe { diff --git a/crates/napi/src/js_values/value_ref.rs b/crates/napi/src/js_values/value_ref.rs index 1d5612fa..b83e9eec 100644 --- a/crates/napi/src/js_values/value_ref.rs +++ b/crates/napi/src/js_values/value_ref.rs @@ -19,7 +19,11 @@ unsafe impl Sync for Ref {} impl Ref { pub fn new(env: &Env, value: &T) -> Result> { let mut raw_ref = ptr::null_mut(); - check_status!(unsafe { sys::napi_create_reference(env.0, value.raw(), 1, &mut raw_ref) })?; + check_status!( + unsafe { sys::napi_create_reference(env.0, value.raw(), 1, &mut raw_ref) }, + "Create napi_ref from {} failed", + std::any::type_name::() + )?; Ok(Ref { raw_ref, taken: false, @@ -66,6 +70,13 @@ impl Ref { } } +impl FromNapiValue for Ref { + unsafe fn from_napi_value(env: sys::napi_env, value: sys::napi_value) -> Result { + let val = T::from_napi_value(env, value)?; + Ref::new(&Env::from_raw(env), &val) + } +} + impl ToNapiValue for Ref { unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { let mut result = ptr::null_mut(); diff --git a/crates/napi/src/threadsafe_function.rs b/crates/napi/src/threadsafe_function.rs index 5ffc099a..19c3c106 100644 --- a/crates/napi/src/threadsafe_function.rs +++ b/crates/napi/src/threadsafe_function.rs @@ -1,5 +1,6 @@ #![allow(clippy::single_component_path_imports)] +use std::convert::identity; use std::marker::PhantomData; use std::os::raw::c_void; use std::ptr::{self, null_mut}; @@ -492,7 +493,7 @@ impl< "Receive value from threadsafe function sender failed", ) }) - .and_then(|ret| ret) + .and_then(identity) } } @@ -771,6 +772,10 @@ fn handle_call_js_cb_status(status: sys::napi_status, raw_env: sys::napi_env) { } } +/// This is a placeholder type that is used to indicate that the return value of a threadsafe function is unknown. +/// Use this type when you don't care about the return value of a threadsafe function. +/// +/// And you can't get the value of it as well because it's just a placeholder. pub struct UnknownReturnValue; impl TypeName for UnknownReturnValue { diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.md b/examples/napi/__tests__/__snapshots__/values.spec.ts.md index 740a4ec1..ec4acbc3 100644 --- a/examples/napi/__tests__/__snapshots__/values.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/values.spec.ts.md @@ -390,6 +390,8 @@ Generated by [AVA](https://avajs.dev). ␊ export declare function call2(callback: (arg0: number, arg1: number) => number, arg1: number, arg2: number): number␊ ␊ + export declare function callAsyncWithUnknownReturnValue(tsfn: ((err: Error | null, arg: number) => Ref)): Promise␊ + ␊ export declare function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise␊ ␊ export declare function callbackReturnPromiseAndSpawn(jsFunc: (arg0: string) => Promise): Promise␊ diff --git a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap index 208646c7..a2213b4b 100644 Binary files a/examples/napi/__tests__/__snapshots__/values.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/values.spec.ts.snap differ diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index 8415958d..cd106ae6 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -230,6 +230,7 @@ import { getClassFromArray, extendsJavascriptError, shutdownRuntime, + callAsyncWithUnknownReturnValue, } from '../index.cjs' import { test } from './test.framework.js' @@ -1390,6 +1391,22 @@ Napi4Test('threadsafe function return Promise and await in Rust', async (t) => { await new Promise((resolve) => setTimeout(resolve, 400)) }) +Napi4Test('call async with unknown return value', async (t) => { + await new Promise((resolve, reject) => { + return callAsyncWithUnknownReturnValue((err, value) => { + if (err) { + reject(err) + } else { + resolve(value) + t.is(value, 42) + return {} + } + }).then((result) => { + t.is(result, 110) + }) + }) +}) + Napi4Test('object only from js', (t) => { return new Promise((resolve, reject) => { receiveObjectOnlyFromJs({ diff --git a/examples/napi/example.wasi-browser.js b/examples/napi/example.wasi-browser.js index 0a3a3097..07e36e28 100644 --- a/examples/napi/example.wasi-browser.js +++ b/examples/napi/example.wasi-browser.js @@ -139,6 +139,7 @@ export const buildThreadsafeFunctionFromFunctionCalleeHandle = __napiModule.expo export const call0 = __napiModule.exports.call0 export const call1 = __napiModule.exports.call1 export const call2 = __napiModule.exports.call2 +export const callAsyncWithUnknownReturnValue = __napiModule.exports.callAsyncWithUnknownReturnValue export const callbackReturnPromise = __napiModule.exports.callbackReturnPromise export const callbackReturnPromiseAndSpawn = __napiModule.exports.callbackReturnPromiseAndSpawn export const callCatchOnPromise = __napiModule.exports.callCatchOnPromise diff --git a/examples/napi/example.wasi.cjs b/examples/napi/example.wasi.cjs index 054dd70c..955c24c9 100644 --- a/examples/napi/example.wasi.cjs +++ b/examples/napi/example.wasi.cjs @@ -163,6 +163,7 @@ module.exports.buildThreadsafeFunctionFromFunctionCalleeHandle = __napiModule.ex module.exports.call0 = __napiModule.exports.call0 module.exports.call1 = __napiModule.exports.call1 module.exports.call2 = __napiModule.exports.call2 +module.exports.callAsyncWithUnknownReturnValue = __napiModule.exports.callAsyncWithUnknownReturnValue module.exports.callbackReturnPromise = __napiModule.exports.callbackReturnPromise module.exports.callbackReturnPromiseAndSpawn = __napiModule.exports.callbackReturnPromiseAndSpawn module.exports.callCatchOnPromise = __napiModule.exports.callCatchOnPromise diff --git a/examples/napi/index.cjs b/examples/napi/index.cjs index 1add9d4a..c555aa57 100644 --- a/examples/napi/index.cjs +++ b/examples/napi/index.cjs @@ -453,6 +453,7 @@ module.exports.buildThreadsafeFunctionFromFunctionCalleeHandle = nativeBinding.b module.exports.call0 = nativeBinding.call0 module.exports.call1 = nativeBinding.call1 module.exports.call2 = nativeBinding.call2 +module.exports.callAsyncWithUnknownReturnValue = nativeBinding.callAsyncWithUnknownReturnValue module.exports.callbackReturnPromise = nativeBinding.callbackReturnPromise module.exports.callbackReturnPromiseAndSpawn = nativeBinding.callbackReturnPromiseAndSpawn module.exports.callCatchOnPromise = nativeBinding.callCatchOnPromise diff --git a/examples/napi/index.d.cts b/examples/napi/index.d.cts index af02258a..46738df0 100644 --- a/examples/napi/index.d.cts +++ b/examples/napi/index.d.cts @@ -352,6 +352,8 @@ export declare function call1(callback: (arg: number) => number, arg: number): n export declare function call2(callback: (arg0: number, arg1: number) => number, arg1: number, arg2: number): number +export declare function callAsyncWithUnknownReturnValue(tsfn: ((err: Error | null, arg: number) => Ref)): Promise + export declare function callbackReturnPromise(functionInput: () => T | Promise, callback: (err: Error | null, result: T) => void): T | Promise export declare function callbackReturnPromiseAndSpawn(jsFunc: (arg0: string) => Promise): Promise diff --git a/examples/napi/src/threadsafe_function.rs b/examples/napi/src/threadsafe_function.rs index 723eee89..7a71331f 100644 --- a/examples/napi/src/threadsafe_function.rs +++ b/examples/napi/src/threadsafe_function.rs @@ -3,6 +3,7 @@ use std::{sync::Arc, thread, time::Duration}; use napi::{ bindgen_prelude::*, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue}, + Ref, }; use crate::class::Animal; @@ -170,6 +171,27 @@ pub async fn tsfn_return_promise_timeout( } } +#[napi] +pub fn call_async_with_unknown_return_value( + env: &Env, + tsfn: ThreadsafeFunction>, +) -> Result> { + env.spawn_future_with_callback( + async move { + let return_value = tsfn.call_async(Ok(42)).await?; + Ok(return_value) + }, + |env, mut value| { + let return_value = value.get_value(&env)?; + value.unref(&env)?; + match return_value.get_type()? { + ValueType::Object => Ok(110), + _ => Ok(100), + } + }, + ) +} + #[napi] pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction>) -> napi::Result { tsfn.call_async(Ok(42)).await?.await diff --git a/memory-testing/src/lib.rs b/memory-testing/src/lib.rs index b02fbe35..cb09ed3a 100644 --- a/memory-testing/src/lib.rs +++ b/memory-testing/src/lib.rs @@ -42,7 +42,7 @@ pub struct Room { } #[napi] -pub fn test_async(env: &Env) -> napi::Result> { +pub fn test_async(env: &Env) -> napi::Result> { let data = serde_json::json!({ "findFirstBooking": { "id": "ckovh15xa104945sj64rdk8oas",