Skip to content

Commit 40bcb09

Browse files
src: restore ability to run under NAPI_EXPERIMENTAL (#1409)
* 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`. * propagate correlation between version and flag to test addon build * remove duplicate yaml key
1 parent 108e78b commit 40bcb09

File tree

5 files changed

+123
-64
lines changed

5 files changed

+123
-64
lines changed

.github/workflows/ci-win.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ jobs:
1212
test:
1313
timeout-minutes: 30
1414
strategy:
15+
fail-fast: false
1516
matrix:
16-
node-version: [ 18.x, 20.x, 21.x, 22.x ]
17+
node-version:
18+
- 18.x
19+
- 20.x
20+
- 22.x
1721
architecture: [x64, x86]
1822
os:
1923
- windows-2019
2024
- windows-2022
21-
fail-fast: false # Don't cancel other jobs when one job fails
2225
runs-on: ${{ matrix.os }}
2326
steps:
2427
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

.github/workflows/ci.yml

+11-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,15 @@ jobs:
1212
test:
1313
timeout-minutes: 30
1414
strategy:
15+
fail-fast: false
1516
matrix:
16-
node-version: [ 18.x, 20.x, 21.x, 22.x ]
17+
api_version:
18+
- standard
19+
- experimental
20+
node-version:
21+
- 18.x
22+
- 20.x
23+
- 22.x
1724
os:
1825
- macos-latest
1926
- ubuntu-latest
@@ -43,6 +50,9 @@ jobs:
4350
npm install
4451
- name: npm test
4552
run: |
53+
if [ "${{ matrix.api_version }}" = "experimental" ]; then
54+
export NAPI_VERSION=2147483647
55+
fi
4656
if [ "${{ matrix.compiler }}" = "gcc" ]; then
4757
export CC="gcc" CXX="g++"
4858
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

+105-61
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ namespace details {
3131
// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h.
3232
constexpr int napi_no_external_buffers_allowed = 22;
3333

34+
#if (defined(NAPI_EXPERIMENTAL) && \
35+
defined(NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER))
36+
template <napi_finalize finalizer>
37+
inline void PostFinalizerWrapper(node_api_nogc_env nogc_env,
38+
void* data,
39+
void* hint) {
40+
napi_status status = node_api_post_finalizer(nogc_env, finalizer, data, hint);
41+
NAPI_FATAL_IF_FAILED(
42+
status, "PostFinalizerWrapper", "node_api_post_finalizer failed");
43+
}
44+
#else
45+
template <napi_finalize finalizer>
46+
inline void PostFinalizerWrapper(napi_env env, void* data, void* hint) {
47+
finalizer(env, data, hint);
48+
}
49+
#endif
50+
3451
template <typename FreeType>
3552
inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) {
3653
delete static_cast<FreeType*>(data);
@@ -65,7 +82,8 @@ inline napi_status AttachData(napi_env env,
6582
}
6683
}
6784
#else // NAPI_VERSION >= 5
68-
status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr);
85+
status = napi_add_finalizer(
86+
env, obj, data, details::PostFinalizerWrapper<finalizer>, hint, nullptr);
6987
#endif
7088
return status;
7189
}
@@ -1777,7 +1795,8 @@ inline External<T> External<T>::New(napi_env env,
17771795
napi_status status =
17781796
napi_create_external(env,
17791797
data,
1780-
details::FinalizeData<T, Finalizer>::Wrapper,
1798+
details::PostFinalizerWrapper<
1799+
details::FinalizeData<T, Finalizer>::Wrapper>,
17811800
finalizeData,
17821801
&value);
17831802
if (status != napi_ok) {
@@ -1800,7 +1819,8 @@ inline External<T> External<T>::New(napi_env env,
18001819
napi_status status = napi_create_external(
18011820
env,
18021821
data,
1803-
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
1822+
details::PostFinalizerWrapper<
1823+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
18041824
finalizeData,
18051825
&value);
18061826
if (status != napi_ok) {
@@ -1913,7 +1933,8 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
19131933
env,
19141934
externalData,
19151935
byteLength,
1916-
details::FinalizeData<void, Finalizer>::Wrapper,
1936+
details::PostFinalizerWrapper<
1937+
details::FinalizeData<void, Finalizer>::Wrapper>,
19171938
finalizeData,
19181939
&value);
19191940
if (status != napi_ok) {
@@ -1938,7 +1959,8 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env,
19381959
env,
19391960
externalData,
19401961
byteLength,
1941-
details::FinalizeData<void, Finalizer, Hint>::WrapperWithHint,
1962+
details::PostFinalizerWrapper<
1963+
details::FinalizeData<void, Finalizer, Hint>::WrapperWithHint>,
19421964
finalizeData,
19431965
&value);
19441966
if (status != napi_ok) {
@@ -2655,13 +2677,14 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
26552677
details::FinalizeData<T, Finalizer>* finalizeData =
26562678
new details::FinalizeData<T, Finalizer>(
26572679
{std::move(finalizeCallback), nullptr});
2658-
napi_status status =
2659-
napi_create_external_buffer(env,
2660-
length * sizeof(T),
2661-
data,
2662-
details::FinalizeData<T, Finalizer>::Wrapper,
2663-
finalizeData,
2664-
&value);
2680+
napi_status status = napi_create_external_buffer(
2681+
env,
2682+
length * sizeof(T),
2683+
data,
2684+
details::PostFinalizerWrapper<
2685+
details::FinalizeData<T, Finalizer>::Wrapper>,
2686+
finalizeData,
2687+
&value);
26652688
if (status != napi_ok) {
26662689
delete finalizeData;
26672690
NAPI_THROW_IF_FAILED(env, status, Buffer());
@@ -2684,7 +2707,8 @@ inline Buffer<T> Buffer<T>::New(napi_env env,
26842707
env,
26852708
length * sizeof(T),
26862709
data,
2687-
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
2710+
details::PostFinalizerWrapper<
2711+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
26882712
finalizeData,
26892713
&value);
26902714
if (status != napi_ok) {
@@ -2723,13 +2747,14 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
27232747
{std::move(finalizeCallback), nullptr});
27242748
#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
27252749
napi_value value;
2726-
napi_status status =
2727-
napi_create_external_buffer(env,
2728-
length * sizeof(T),
2729-
data,
2730-
details::FinalizeData<T, Finalizer>::Wrapper,
2731-
finalizeData,
2732-
&value);
2750+
napi_status status = napi_create_external_buffer(
2751+
env,
2752+
length * sizeof(T),
2753+
data,
2754+
details::PostFinalizerWrapper<
2755+
details::FinalizeData<T, Finalizer>::Wrapper>,
2756+
finalizeData,
2757+
&value);
27332758
if (status == details::napi_no_external_buffers_allowed) {
27342759
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
27352760
// If we can't create an external buffer, we'll just copy the data.
@@ -2762,7 +2787,8 @@ inline Buffer<T> Buffer<T>::NewOrCopy(napi_env env,
27622787
env,
27632788
length * sizeof(T),
27642789
data,
2765-
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint,
2790+
details::PostFinalizerWrapper<
2791+
details::FinalizeData<T, Finalizer, Hint>::WrapperWithHint>,
27662792
finalizeData,
27672793
&value);
27682794
if (status == details::napi_no_external_buffers_allowed) {
@@ -3057,7 +3083,12 @@ inline void Error::ThrowAsJavaScriptException() const {
30573083

30583084
status = napi_throw(_env, Value());
30593085

3060-
if (status == napi_pending_exception) {
3086+
#ifdef NAPI_EXPERIMENTAL
3087+
napi_status expected_failure_mode = napi_cannot_run_js;
3088+
#else
3089+
napi_status expected_failure_mode = napi_pending_exception;
3090+
#endif
3091+
if (status == expected_failure_mode) {
30613092
// The environment must be terminating as we checked earlier and there
30623093
// was no pending exception. In this case continuing will result
30633094
// in a fatal error and there is nothing the author has done incorrectly
@@ -4431,7 +4462,12 @@ inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
44314462
napi_status status;
44324463
napi_ref ref;
44334464
T* instance = static_cast<T*>(this);
4434-
status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref);
4465+
status = napi_wrap(env,
4466+
wrapper,
4467+
instance,
4468+
details::PostFinalizerWrapper<FinalizeCallback>,
4469+
nullptr,
4470+
&ref);
44354471
NAPI_THROW_IF_FAILED_VOID(env, status);
44364472

44374473
Reference<Object>* instanceRef = instance;
@@ -5327,19 +5363,21 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
53275363
auto* finalizeData = new details::
53285364
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
53295365
{data, finalizeCallback});
5330-
napi_status status = napi_create_threadsafe_function(
5331-
env,
5332-
nullptr,
5333-
nullptr,
5334-
String::From(env, resourceName),
5335-
maxQueueSize,
5336-
initialThreadCount,
5337-
finalizeData,
5366+
auto fini =
53385367
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5339-
FinalizeFinalizeWrapperWithDataAndContext,
5340-
context,
5341-
CallJsInternal,
5342-
&tsfn._tsfn);
5368+
FinalizeFinalizeWrapperWithDataAndContext;
5369+
napi_status status =
5370+
napi_create_threadsafe_function(env,
5371+
nullptr,
5372+
nullptr,
5373+
String::From(env, resourceName),
5374+
maxQueueSize,
5375+
initialThreadCount,
5376+
finalizeData,
5377+
fini,
5378+
context,
5379+
CallJsInternal,
5380+
&tsfn._tsfn);
53435381
if (status != napi_ok) {
53445382
delete finalizeData;
53455383
NAPI_THROW_IF_FAILED(
@@ -5371,19 +5409,21 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
53715409
auto* finalizeData = new details::
53725410
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
53735411
{data, finalizeCallback});
5374-
napi_status status = napi_create_threadsafe_function(
5375-
env,
5376-
nullptr,
5377-
resource,
5378-
String::From(env, resourceName),
5379-
maxQueueSize,
5380-
initialThreadCount,
5381-
finalizeData,
5412+
auto fini =
53825413
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5383-
FinalizeFinalizeWrapperWithDataAndContext,
5384-
context,
5385-
CallJsInternal,
5386-
&tsfn._tsfn);
5414+
FinalizeFinalizeWrapperWithDataAndContext;
5415+
napi_status status =
5416+
napi_create_threadsafe_function(env,
5417+
nullptr,
5418+
resource,
5419+
String::From(env, resourceName),
5420+
maxQueueSize,
5421+
initialThreadCount,
5422+
finalizeData,
5423+
fini,
5424+
context,
5425+
CallJsInternal,
5426+
&tsfn._tsfn);
53875427
if (status != napi_ok) {
53885428
delete finalizeData;
53895429
NAPI_THROW_IF_FAILED(
@@ -5487,19 +5527,21 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
54875527
auto* finalizeData = new details::
54885528
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
54895529
{data, finalizeCallback});
5490-
napi_status status = napi_create_threadsafe_function(
5491-
env,
5492-
callback,
5493-
nullptr,
5494-
String::From(env, resourceName),
5495-
maxQueueSize,
5496-
initialThreadCount,
5497-
finalizeData,
5530+
auto fini =
54985531
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5499-
FinalizeFinalizeWrapperWithDataAndContext,
5500-
context,
5501-
CallJsInternal,
5502-
&tsfn._tsfn);
5532+
FinalizeFinalizeWrapperWithDataAndContext;
5533+
napi_status status =
5534+
napi_create_threadsafe_function(env,
5535+
callback,
5536+
nullptr,
5537+
String::From(env, resourceName),
5538+
maxQueueSize,
5539+
initialThreadCount,
5540+
finalizeData,
5541+
fini,
5542+
context,
5543+
CallJsInternal,
5544+
&tsfn._tsfn);
55035545
if (status != napi_ok) {
55045546
delete finalizeData;
55055547
NAPI_THROW_IF_FAILED(
@@ -5533,6 +5575,9 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
55335575
auto* finalizeData = new details::
55345576
ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>(
55355577
{data, finalizeCallback});
5578+
auto fini =
5579+
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5580+
FinalizeFinalizeWrapperWithDataAndContext;
55365581
napi_status status = napi_create_threadsafe_function(
55375582
env,
55385583
details::DefaultCallbackWrapper<
@@ -5544,8 +5589,7 @@ TypedThreadSafeFunction<ContextType, DataType, CallJs>::New(
55445589
maxQueueSize,
55455590
initialThreadCount,
55465591
finalizeData,
5547-
details::ThreadSafeFinalize<ContextType, Finalizer, FinalizerDataType>::
5548-
FinalizeFinalizeWrapperWithDataAndContext,
5592+
fini,
55495593
context,
55505594
CallJsInternal,
55515595
&tsfn._tsfn);

test/addon_build/tpl/binding.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
},
1010
'conditions': [
1111
['NAPI_VERSION!=""', { 'defines': ['NAPI_VERSION=<@(NAPI_VERSION)'] } ],
12+
['NAPI_VERSION==2147483647', { 'defines': ['NAPI_EXPERIMENTAL'] } ],
1213
['disable_deprecated=="true"', {
1314
'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED']
1415
}],

0 commit comments

Comments
 (0)