Skip to content

Commit f89a09b

Browse files
src: restore ability to run under NAPI_EXPERIMENTAL
Since we made the default for Node.js core finalizers synchronous for users running with `NAPI_EXPERIMENTAL` and introduced `env->CheckGCAccess()` in Node.js core, we must now defer all finalizers in node-addon-api, because our users likely make non-gc-safe Node-API calls from existing finalizers. To that end, * Use the NAPI_VERSION environment variable to detect whether `NAPI_EXPERIMENTAL` should be on, and add it to the defines if `NAPI_VERSION` is set to `NAPI_VERSION_EXPERIMENTAL`, i.e. 2147483647. * When building with `NAPI_EXPERIMENTAL`, * render all finalizers asynchronous, and * expect `napi_cannot_run_js` instead of `napi_exception_pending`.
1 parent f85e814 commit f89a09b

File tree

5 files changed

+125
-64
lines changed

5 files changed

+125
-64
lines changed

.github/workflows/ci.yml

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ jobs:
1313
timeout-minutes: 30
1414
strategy:
1515
matrix:
16+
api_version:
17+
- standard
18+
- experimental
1619
node-version: [ 18.x, 20.x, 21.x ]
1720
os:
1821
- macos-latest
@@ -43,6 +46,9 @@ jobs:
4346
npm install
4447
- name: npm test
4548
run: |
49+
if [ "${{ matrix.api_version }}" = "experimental" ]; then
50+
export NAPI_VERSION=2147483647
51+
fi
4652
if [ "${{ matrix.compiler }}" = "gcc" ]; then
4753
export CC="gcc" CXX="g++"
4854
fi

common.gypi

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
},
66
'conditions': [
77
['NAPI_VERSION!=""', { 'defines': ['NAPI_VERSION=<@(NAPI_VERSION)'] } ],
8+
['NAPI_VERSION==2147483647', { 'defines': ['NAPI_EXPERIMENTAL'] } ],
89
['disable_deprecated=="true"', {
910
'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED']
1011
}],

napi-inl.h

+116-62
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@
1818
#include <type_traits>
1919
#include <utility>
2020

21+
// TODO(gabrielschulhof): Remove this and remove the wrapping at the call sites
22+
// (i.e., at the call site, `TSFN_FINALIZER(x)` should be changed back to `x`)
23+
// after https://github.com/nodejs/node/pull/51801 has landed.
24+
#if (defined(NAPI_EXPERIMENTAL) && \
25+
defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER))
26+
#define TSFN_FINALIZER(fini) reinterpret_cast<node_api_nogc_finalize>(fini)
27+
#else
28+
#define TSFN_FINALIZER(fini) fini
29+
#endif
30+
2131
namespace Napi {
2232

2333
#ifdef NAPI_CPP_CUSTOM_NAMESPACE
@@ -31,6 +41,23 @@ namespace details {
3141
// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h.
3242
constexpr int napi_no_external_buffers_allowed = 22;
3343

44+
#if (defined(NAPI_EXPERIMENTAL) && \
45+
defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER))
46+
template <napi_finalize finalizer>
47+
inline void PostFinalizerWrapper(node_api_nogc_env nogc_env,
48+
void* data,
49+
void* hint) {
50+
napi_status status = node_api_post_finalizer(nogc_env, finalizer, data, hint);
51+
NAPI_FATAL_IF_FAILED(
52+
status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
53+
}
54+
#else
55+
template <napi_finalize finalizer>
56+
inline void PostFinalizerWrapper(napi_env env, void* data, void* hint) {
57+
finalizer(env, data, hint);
58+
}
59+
#endif
60+
3461
template <typename FreeType>
3562
inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) {
3663
delete static_cast<FreeType*>(data);
@@ -65,7 +92,8 @@ inline napi_status AttachData(napi_env env,
6592
}
6693
}
6794
#else // NAPI_VERSION >= 5
68-
status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr);
95+
status = napi_add_finalizer(
96+
env, obj, data, details::PostFinalizerWrapper<finalizer>, hint, nullptr);
6997
#endif
7098
return status;
7199
}
@@ -1774,7 +1802,8 @@ inline External<T> External<T>::New(napi_env env,
17741802
napi_status status =
17751803
napi_create_external(env,
17761804
data,
1777-
details::FinalizeData<T, Finalizer>::Wrapper,
1805+
details::PostFinalizerWrapper<
1806+
details::FinalizeData<T, Finalizer>::Wrapper>,
17781807
finalizeData,
17791808
&value);
17801809
if (status != napi_ok) {
@@ -1797,7 +1826,8 @@ inline External<T> External<T>::New(napi_env env,
17971826
napi_status status = napi_create_external(
17981827
env,
17991828
data,
1800-
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
1829+
details::PostFinalizerWrapper<
1830+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
18011831
finalizeData,
18021832
&value);
18031833
if (status != napi_ok) {
@@ -1910,7 +1940,8 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
19101940
env,
19111941
externalData,
19121942
byteLength,
1913-
details::FinalizeData<void, Finalizer>::Wrapper,
1943+
details::PostFinalizerWrapper<
1944+
details::FinalizeData<void, Finalizer>::Wrapper>,
19141945
finalizeData,
19151946
&value);
19161947
if (status != napi_ok) {
@@ -1935,7 +1966,8 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
19351966
env,
19361967
externalData,
19371968
byteLength,
1938-
details::FinalizeData<void, Finalizer, Hint>::WrapperWithHint,
1969+
details::PostFinalizerWrapper<
1970+
details::FinalizeData<void, Finalizer, Hint>::WrapperWithHint>,
19391971
finalizeData,
19401972
&value);
19411973
if (status != napi_ok) {
@@ -2652,13 +2684,14 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
26522684
details::FinalizeData<T, Finalizer>* finalizeData =
26532685
new details::FinalizeData<T, Finalizer>(
26542686
{std::move(finalizeCallback), nullptr});
2655-
napi_status status =
2656-
napi_create_external_buffer(env,
2657-
length * sizeof(T),
2658-
data,
2659-
details::FinalizeData<T, Finalizer>::Wrapper,
2660-
finalizeData,
2661-
&value);
2687+
napi_status status = napi_create_external_buffer(
2688+
env,
2689+
length * sizeof(T),
2690+
data,
2691+
details::PostFinalizerWrapper<
2692+
details::FinalizeData<T, Finalizer>::Wrapper>,
2693+
finalizeData,
2694+
&value);
26622695
if (status != napi_ok) {
26632696
delete finalizeData;
26642697
NAPI_THROW_IF_FAILED(env, status, Buffer());
@@ -2681,7 +2714,8 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
26812714
env,
26822715
length * sizeof(T),
26832716
data,
2684-
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
2717+
details::PostFinalizerWrapper<
2718+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
26852719
finalizeData,
26862720
&value);
26872721
if (status != napi_ok) {
@@ -2720,13 +2754,14 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
27202754
{std::move(finalizeCallback), nullptr});
27212755
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
27222756
napi_value value;
2723-
napi_status status =
2724-
napi_create_external_buffer(env,
2725-
length * sizeof(T),
2726-
data,
2727-
details::FinalizeData<T, Finalizer>::Wrapper,
2728-
finalizeData,
2729-
&value);
2757+
napi_status status = napi_create_external_buffer(
2758+
env,
2759+
length * sizeof(T),
2760+
data,
2761+
details::PostFinalizerWrapper<
2762+
details::FinalizeData<T, Finalizer>::Wrapper>,
2763+
finalizeData,
2764+
&value);
27302765
if (status == details::napi_no_external_buffers_allowed) {
27312766
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
27322767
// If we can't create an external buffer, we'll just copy the data.
@@ -2759,7 +2794,8 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
27592794
env,
27602795
length * sizeof(T),
27612796
data,
2762-
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
2797+
details::PostFinalizerWrapper<
2798+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
27632799
finalizeData,
27642800
&value);
27652801
if (status == details::napi_no_external_buffers_allowed) {
@@ -3054,7 +3090,12 @@ inline void Error::ThrowAsJavaScriptException() const {
30543090

30553091
status = napi_throw(_env, Value());
30563092

3057-
if (status == napi_pending_exception) {
3093+
#ifdef NAPI_EXPERIMENTAL
3094+
napi_status expected_failure_mode = napi_cannot_run_js;
3095+
#else
3096+
napi_status expected_failure_mode = napi_pending_exception;
3097+
#endif
3098+
if (status == expected_failure_mode) {
30583099
// The environment must be terminating as we checked earlier and there
30593100
// was no pending exception. In this case continuing will result
30603101
// in a fatal error and there is nothing the author has done incorrectly
@@ -4428,7 +4469,12 @@ inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
44284469
napi_status status;
44294470
napi_ref ref;
44304471
T* instance = static_cast<T*>(this);
4431-
status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref);
4472+
status = napi_wrap(env,
4473+
wrapper,
4474+
instance,
4475+
details::PostFinalizerWrapper<FinalizeCallback>,
4476+
nullptr,
4477+
&ref);
44324478
NAPI_THROW_IF_FAILED_VOID(env, status);
44334479

44344480
Reference<Object>* instanceRef = instance;
@@ -5321,19 +5367,21 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
53215367
auto* finalizeData = new details::
53225368
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
53235369
{data, finalizeCallback});
5324-
napi_status status = napi_create_threadsafe_function(
5325-
env,
5326-
nullptr,
5327-
nullptr,
5328-
String::From(env, resourceName),
5329-
maxQueueSize,
5330-
initialThreadCount,
5331-
finalizeData,
5370+
auto fini =
53325371
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5333-
FinalizeFinalizeWrapperWithDataAndContext,
5334-
context,
5335-
CallJsInternal,
5336-
&tsfn._tsfn);
5372+
FinalizeFinalizeWrapperWithDataAndContext;
5373+
napi_status status =
5374+
napi_create_threadsafe_function(env,
5375+
nullptr,
5376+
nullptr,
5377+
String::From(env, resourceName),
5378+
maxQueueSize,
5379+
initialThreadCount,
5380+
finalizeData,
5381+
TSFN_FINALIZER(fini),
5382+
context,
5383+
CallJsInternal,
5384+
&tsfn._tsfn);
53375385
if (status != napi_ok) {
53385386
delete finalizeData;
53395387
NAPI_THROW_IF_FAILED(
@@ -5365,19 +5413,21 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
53655413
auto* finalizeData = new details::
53665414
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
53675415
{data, finalizeCallback});
5368-
napi_status status = napi_create_threadsafe_function(
5369-
env,
5370-
nullptr,
5371-
resource,
5372-
String::From(env, resourceName),
5373-
maxQueueSize,
5374-
initialThreadCount,
5375-
finalizeData,
5416+
auto fini =
53765417
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5377-
FinalizeFinalizeWrapperWithDataAndContext,
5378-
context,
5379-
CallJsInternal,
5380-
&tsfn._tsfn);
5418+
FinalizeFinalizeWrapperWithDataAndContext;
5419+
napi_status status =
5420+
napi_create_threadsafe_function(env,
5421+
nullptr,
5422+
resource,
5423+
String::From(env, resourceName),
5424+
maxQueueSize,
5425+
initialThreadCount,
5426+
finalizeData,
5427+
TSFN_FINALIZER(fini),
5428+
context,
5429+
CallJsInternal,
5430+
&tsfn._tsfn);
53815431
if (status != napi_ok) {
53825432
delete finalizeData;
53835433
NAPI_THROW_IF_FAILED(
@@ -5481,19 +5531,21 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
54815531
auto* finalizeData = new details::
54825532
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
54835533
{data, finalizeCallback});
5484-
napi_status status = napi_create_threadsafe_function(
5485-
env,
5486-
callback,
5487-
nullptr,
5488-
String::From(env, resourceName),
5489-
maxQueueSize,
5490-
initialThreadCount,
5491-
finalizeData,
5534+
auto fini =
54925535
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5493-
FinalizeFinalizeWrapperWithDataAndContext,
5494-
context,
5495-
CallJsInternal,
5496-
&tsfn._tsfn);
5536+
FinalizeFinalizeWrapperWithDataAndContext;
5537+
napi_status status =
5538+
napi_create_threadsafe_function(env,
5539+
callback,
5540+
nullptr,
5541+
String::From(env, resourceName),
5542+
maxQueueSize,
5543+
initialThreadCount,
5544+
finalizeData,
5545+
TSFN_FINALIZER(fini),
5546+
context,
5547+
CallJsInternal,
5548+
&tsfn._tsfn);
54975549
if (status != napi_ok) {
54985550
delete finalizeData;
54995551
NAPI_THROW_IF_FAILED(
@@ -5527,6 +5579,9 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
55275579
auto* finalizeData = new details::
55285580
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
55295581
{data, finalizeCallback});
5582+
auto fini =
5583+
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5584+
FinalizeFinalizeWrapperWithDataAndContext;
55305585
napi_status status = napi_create_threadsafe_function(
55315586
env,
55325587
details::DefaultCallbackWrapper<
@@ -5538,8 +5593,7 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
55385593
maxQueueSize,
55395594
initialThreadCount,
55405595
finalizeData,
5541-
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5542-
FinalizeFinalizeWrapperWithDataAndContext,
5596+
TSFN_FINALIZER(fini),
55435597
context,
55445598
CallJsInternal,
55455599
&tsfn._tsfn);
@@ -6078,7 +6132,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env,
60786132
maxQueueSize,
60796133
initialThreadCount,
60806134
finalizeData,
6081-
wrapper,
6135+
TSFN_FINALIZER(wrapper),
60826136
context,
60836137
CallJS,
60846138
&tsfn._tsfn);

test/threadsafe_function/threadsafe_function_existing_tsfn.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ static Value TestCall(const CallbackInfo& info) {
8686
0,
8787
1,
8888
nullptr, /*finalize data*/
89-
FinalizeCB,
89+
TSFN_FINALIZER(FinalizeCB),
9090
testContext,
9191
hasData ? CallJSWithData : CallJSNoData,
9292
&testContext->tsfn);

test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ static Value TestCall(const CallbackInfo& info) {
8888
0,
8989
1,
9090
nullptr, /*finalize data*/
91-
FinalizeCB,
91+
TSFN_FINALIZER(FinalizeCB),
9292
testContext,
9393
hasData ? CallJSWithData : CallJSNoData,
9494
&testContext->tsfn);

0 commit comments

Comments
 (0)