-
-
Notifications
You must be signed in to change notification settings - Fork 11
port test_finalizer #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
bavulapati
wants to merge
2
commits into
nodejs:main
Choose a base branch
from
bavulapati:feat/port-test-finalizer
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # The reference suite already defines a CMake target named "test_finalizer" | ||
| # (tests/js-native-api/test_reference/test_finalizer.c), and target names must | ||
| # be globally unique. Use a distinct target name here but keep the output binary | ||
| # named test_finalizer.node so loadAddon('test_finalizer') resolves in this dir. | ||
| add_node_api_cts_experimental_addon(test_finalizer_standalone test_finalizer.c) | ||
| set_target_properties(test_finalizer_standalone PROPERTIES OUTPUT_NAME test_finalizer) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| 'use strict'; | ||
|
|
||
| // node_api_post_finalizer and node_api_basic_env are both experimental. | ||
| if (!experimentalFeatures.postFinalizer) { | ||
| skipTest(); | ||
| } | ||
|
|
||
| const test_finalizer = loadAddon('test_finalizer'); | ||
|
|
||
| { | ||
| (() => { | ||
| const obj = {}; | ||
| test_finalizer.addFinalizer(obj); | ||
| })(); | ||
|
|
||
| await gcUntil( | ||
| 'pure finalizer', | ||
| () => test_finalizer.getFinalizerCallCount() === 1, | ||
| ); | ||
| assert.strictEqual(test_finalizer.getFinalizerCallCount(), 1); | ||
| } | ||
|
|
||
| // A finalizer that calls into JS runs later via node_api_post_finalizer, so | ||
| // it is observed asynchronously. | ||
| { | ||
| let js_is_called = false; | ||
| (() => { | ||
| const obj = {}; | ||
| test_finalizer.addFinalizerWithJS(obj, () => { | ||
| js_is_called = true; | ||
| }); | ||
| })(); | ||
|
|
||
| await gcUntil( | ||
| 'JS-calling finalizer', | ||
| () => test_finalizer.getFinalizerCallCount() === 2, | ||
| ); | ||
| assert.strictEqual(js_is_called, true); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| 'use strict'; | ||
|
|
||
| // A fatal finalizer aborts the process, so it runs in a spawned child | ||
| // (testFatalFinalize_child.mjs) rather than tearing down the test runner. | ||
| // Needs the postFinalizer API and the ability to spawn. | ||
| if (!experimentalFeatures.postFinalizer || !runtimeFeatures.spawn) { | ||
| skipTest(); | ||
| } | ||
|
|
||
| const result = await spawnTest('testFatalFinalize_child.mjs'); | ||
|
|
||
| // `aborted` covers however the runtime surfaces the abort (a fatal signal on | ||
| // POSIX, one of a few exit codes on Windows); the stderr match is the real | ||
| // specificity guard. | ||
| assert.ok(result.aborted, `Expected child to abort, got status=${result.status}`); | ||
| assert.match( | ||
| result.stderr, | ||
| /Finalizer is calling a function that may affect GC state/, | ||
| ); |
13 changes: 13 additions & 0 deletions
13
tests/js-native-api/test_finalizer/testFatalFinalize_child.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // Child of testFatalFinalize.js: a finalizer that illegally calls into JS from | ||
| // a basic finalizer context, which makes Node-API abort the process. | ||
| const test_finalizer = loadAddon('test_finalizer'); | ||
|
|
||
| (() => { | ||
| const obj = {}; | ||
| test_finalizer.addFinalizerFailOnJS(obj); | ||
| })(); | ||
|
|
||
| // The abort happens inside the finalizer, so this never returns; the counter | ||
| // guard only matters if the illegal call is somehow accepted. | ||
| let gcCount = 10; | ||
| await gcUntil('fatal finalize', () => --gcCount <= 0); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| #include <js_native_api.h> | ||
| #include <stdint.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include "../common.h" | ||
| #include "../entry_point.h" | ||
|
|
||
| typedef struct { | ||
| int32_t finalize_count; | ||
| napi_ref js_func; | ||
| } FinalizerData; | ||
|
|
||
| static void finalizerOnlyCallback(node_api_basic_env env, | ||
| void* finalize_data, | ||
| void* finalize_hint) { | ||
| FinalizerData* data = (FinalizerData*)finalize_data; | ||
| int32_t count = ++data->finalize_count; | ||
|
|
||
| // It is safe to access instance data | ||
| NODE_API_BASIC_CALL_RETURN_VOID(env, | ||
| napi_get_instance_data(env, (void**)&data)); | ||
| NODE_API_BASIC_ASSERT_RETURN_VOID(count = data->finalize_count, | ||
| "Expected to be the same FinalizerData"); | ||
| } | ||
|
|
||
| static void finalizerCallingJSCallback(napi_env env, | ||
| void* finalize_data, | ||
| void* finalize_hint) { | ||
| napi_value js_func, undefined; | ||
| FinalizerData* data = (FinalizerData*)finalize_data; | ||
| NODE_API_CALL_RETURN_VOID( | ||
| env, napi_get_reference_value(env, data->js_func, &js_func)); | ||
| NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); | ||
| NODE_API_CALL_RETURN_VOID( | ||
| env, napi_call_function(env, undefined, js_func, 0, NULL, NULL)); | ||
| NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_func)); | ||
| data->js_func = NULL; | ||
| ++data->finalize_count; | ||
| } | ||
|
|
||
| // Schedule async finalizer to run JavaScript-touching code. | ||
| static void finalizerWithJSCallback(node_api_basic_env env, | ||
| void* finalize_data, | ||
| void* finalize_hint) { | ||
| NODE_API_BASIC_CALL_RETURN_VOID( | ||
| env, | ||
| node_api_post_finalizer( | ||
| env, finalizerCallingJSCallback, finalize_data, finalize_hint)); | ||
| } | ||
|
|
||
| static void finalizerWithFailedJSCallback(node_api_basic_env basic_env, | ||
| void* finalize_data, | ||
| void* finalize_hint) { | ||
| // Intentionally cast to a napi_env to test the fatal failure. | ||
| napi_env env = (napi_env)basic_env; | ||
| napi_value obj; | ||
| FinalizerData* data = (FinalizerData*)finalize_data; | ||
| ++data->finalize_count; | ||
| NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &obj)); | ||
| } | ||
|
|
||
| static napi_value addFinalizer(napi_env env, napi_callback_info info) { | ||
| size_t argc = 1; | ||
| napi_value argv[1] = {0}; | ||
| FinalizerData* data; | ||
|
|
||
| NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); | ||
| NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); | ||
| NODE_API_CALL(env, | ||
| napi_add_finalizer( | ||
| env, argv[0], data, finalizerOnlyCallback, NULL, NULL)); | ||
| return NULL; | ||
| } | ||
|
|
||
| // This finalizer is going to call JavaScript from finalizer and succeed. | ||
| static napi_value addFinalizerWithJS(napi_env env, napi_callback_info info) { | ||
| size_t argc = 2; | ||
| napi_value argv[2] = {0}; | ||
| napi_valuetype arg_type; | ||
| FinalizerData* data; | ||
|
|
||
| NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); | ||
| NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); | ||
| NODE_API_CALL(env, napi_typeof(env, argv[1], &arg_type)); | ||
| NODE_API_ASSERT( | ||
| env, arg_type == napi_function, "Expected function as the second arg"); | ||
| NODE_API_CALL(env, napi_create_reference(env, argv[1], 1, &data->js_func)); | ||
| NODE_API_CALL(env, | ||
| napi_add_finalizer( | ||
| env, argv[0], data, finalizerWithJSCallback, NULL, NULL)); | ||
| return NULL; | ||
| } | ||
|
|
||
| // This finalizer is going to call JavaScript from finalizer and fail. | ||
| static napi_value addFinalizerFailOnJS(napi_env env, napi_callback_info info) { | ||
| size_t argc = 1; | ||
| napi_value argv[1] = {0}; | ||
| FinalizerData* data; | ||
|
|
||
| NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); | ||
| NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); | ||
| NODE_API_CALL( | ||
| env, | ||
| napi_add_finalizer( | ||
| env, argv[0], data, finalizerWithFailedJSCallback, NULL, NULL)); | ||
| return NULL; | ||
| } | ||
|
|
||
| static napi_value getFinalizerCallCount(napi_env env, napi_callback_info info) { | ||
| size_t argc = 1; | ||
| napi_value argv[1]; | ||
| FinalizerData* data; | ||
| napi_value result; | ||
|
|
||
| NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); | ||
| NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data)); | ||
| NODE_API_CALL(env, napi_create_int32(env, data->finalize_count, &result)); | ||
| return result; | ||
| } | ||
|
|
||
| static void finalizeData(napi_env env, void* data, void* hint) { | ||
| free(data); | ||
| } | ||
|
|
||
| EXTERN_C_START | ||
| napi_value Init(napi_env env, napi_value exports) { | ||
| FinalizerData* data = (FinalizerData*)malloc(sizeof(FinalizerData)); | ||
| NODE_API_ASSERT(env, data != NULL, "Failed to allocate memory"); | ||
| memset(data, 0, sizeof(FinalizerData)); | ||
| NODE_API_CALL(env, napi_set_instance_data(env, data, finalizeData, NULL)); | ||
| napi_property_descriptor descriptors[] = { | ||
| DECLARE_NODE_API_PROPERTY("addFinalizer", addFinalizer), | ||
| DECLARE_NODE_API_PROPERTY("addFinalizerWithJS", addFinalizerWithJS), | ||
| DECLARE_NODE_API_PROPERTY("addFinalizerFailOnJS", addFinalizerFailOnJS), | ||
| DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", | ||
| getFinalizerCallCount)}; | ||
|
|
||
| NODE_API_CALL( | ||
| env, | ||
| napi_define_properties(env, | ||
| exports, | ||
| sizeof(descriptors) / sizeof(*descriptors), | ||
| descriptors)); | ||
|
|
||
| return exports; | ||
| } | ||
| EXTERN_C_END |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.