-
Notifications
You must be signed in to change notification settings - Fork 5k
napi: run napi_add_finalizer and napi_create_external finalizers at env teardown #32912
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
robobun
wants to merge
8
commits into
main
Choose a base branch
from
claude/f8929714/napi-teardown-finalizers
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
8 commits
Select commit
Hold shift + click to select a range
742e6a1
napi: run napi_add_finalizer and napi_create_external finalizers at e…
robobun b76d8d2
napi: drain finalizers registered during env teardown; fix Windows CR…
robobun 757357a
napi test: print finalizer output to stdout; make failed assertions s…
robobun a831e10
ci: retrigger
robobun b3b69e3
napi test: don't inherit JSC exception-scope validation from the CI a…
robobun 9d3d449
napi: never return a tombstoned finalizer entry from addFinalizer
robobun 8f32b30
napi: remove BoundFinalizer::call, its only caller was the rewritten …
robobun 6ed3422
napi test: shorten comments to the 3-line limit and drop per-test tim…
robobun 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
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
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,172 @@ | ||
| // Every finalizer registration API must run its finalizer at env teardown, | ||
| // exactly once, including finalizers registered by another teardown finalizer. | ||
| // Each prints "finalize: <name>" to stdout (a child's stderr is unreliable on CI). | ||
|
|
||
| #include <node_api.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| static int finalize_count = 0; | ||
|
|
||
| static void finalize(napi_env env, void* data, void* hint) { | ||
| (void)env; | ||
| (void)data; | ||
| printf("finalize: %s\n", (const char*)hint); | ||
| fflush(stdout); | ||
| free(hint); | ||
| finalize_count++; | ||
| } | ||
|
|
||
| // Copies the string in `arg` into a malloc'd buffer that finalize() frees. | ||
| static char* dup_name_arg(napi_env env, napi_value arg) { | ||
| size_t len = 0; | ||
| napi_get_value_string_utf8(env, arg, NULL, 0, &len); | ||
| char* name = (char*)malloc(len + 1); | ||
| napi_get_value_string_utf8(env, arg, name, len + 1, &len); | ||
| return name; | ||
| } | ||
|
|
||
| // wrap(obj, name) | ||
| static napi_value wrap(napi_env env, napi_callback_info info) { | ||
| size_t argc = 2; | ||
| napi_value argv[2]; | ||
| napi_get_cb_info(env, info, &argc, argv, NULL, NULL); | ||
| napi_wrap(env, argv[0], NULL, finalize, dup_name_arg(env, argv[1]), NULL); | ||
| return argv[0]; | ||
| } | ||
|
|
||
| // addFinalizer(obj, name, wantRef): wantRef=true exercises the napi_ref-returning overload. | ||
| static napi_value add_finalizer(napi_env env, napi_callback_info info) { | ||
| size_t argc = 3; | ||
| napi_value argv[3]; | ||
| napi_get_cb_info(env, info, &argc, argv, NULL, NULL); | ||
| bool want_ref = false; | ||
| napi_get_value_bool(env, argv[2], &want_ref); | ||
| // Leaked on purpose: this test never releases the ref, matching addons | ||
| // that hold a weak ref for the object's whole lifetime. | ||
| napi_ref* ref = want_ref ? (napi_ref*)malloc(sizeof(napi_ref)) : NULL; | ||
| napi_add_finalizer(env, argv[0], NULL, finalize, dup_name_arg(env, argv[1]), ref); | ||
| return argv[0]; | ||
| } | ||
|
|
||
| // createExternal(name) -> external value | ||
| static napi_value create_external(napi_env env, napi_callback_info info) { | ||
| size_t argc = 1; | ||
| napi_value argv[1]; | ||
| napi_get_cb_info(env, info, &argc, argv, NULL, NULL); | ||
| napi_value external; | ||
| napi_create_external(env, NULL, finalize, dup_name_arg(env, argv[0]), &external); | ||
| return external; | ||
| } | ||
|
|
||
| // finalizeCount() -> number of finalizers that have already run (and flushed). | ||
| static napi_value get_finalize_count(napi_env env, napi_callback_info info) { | ||
| (void)info; | ||
| napi_value out; | ||
| napi_create_int32(env, finalize_count, &out); | ||
| return out; | ||
| } | ||
|
|
||
| typedef struct { | ||
| char* outer_name; | ||
| char* nested_external_name; | ||
| char* nested_add_finalizer_name; | ||
| } NestingContext; | ||
|
|
||
| // Runs as a teardown finalizer and registers two new finalizers while the env | ||
| // is already draining its finalizer list. Bun accepts these calls here (Node | ||
| // rejects them with napi_pending_exception), so it must drain them safely. | ||
| static void nesting_finalize(napi_env env, void* data, void* hint) { | ||
| (void)hint; | ||
| NestingContext* ctx = (NestingContext*)data; | ||
| printf("finalize: %s\n", ctx->outer_name); | ||
| fflush(stdout); | ||
| finalize_count++; | ||
| napi_value external; | ||
| napi_create_external(env, NULL, finalize, ctx->nested_external_name, &external); | ||
| napi_add_finalizer(env, external, NULL, finalize, ctx->nested_add_finalizer_name, NULL); | ||
| free(ctx->outer_name); | ||
| free(ctx); | ||
| } | ||
|
|
||
| // wrapNesting(obj, outerName, nestedExternalName, nestedAddFinalizerName) | ||
| static napi_value wrap_nesting(napi_env env, napi_callback_info info) { | ||
| size_t argc = 4; | ||
| napi_value argv[4]; | ||
| napi_get_cb_info(env, info, &argc, argv, NULL, NULL); | ||
| NestingContext* ctx = (NestingContext*)malloc(sizeof(NestingContext)); | ||
| ctx->outer_name = dup_name_arg(env, argv[1]); | ||
| ctx->nested_external_name = dup_name_arg(env, argv[2]); | ||
| ctx->nested_add_finalizer_name = dup_name_arg(env, argv[3]); | ||
| napi_wrap(env, argv[0], ctx, nesting_finalize, NULL, NULL); | ||
| return argv[0]; | ||
| } | ||
|
|
||
| static napi_ref saved_ref = NULL; | ||
|
|
||
| // addFinalizerSaveRef(obj, name): napi_add_finalizer keeping the returned ref | ||
| // so a later teardown finalizer can napi_delete_reference it. | ||
| static napi_value add_finalizer_save_ref(napi_env env, napi_callback_info info) { | ||
| size_t argc = 2; | ||
| napi_value argv[2]; | ||
| napi_get_cb_info(env, info, &argc, argv, NULL, NULL); | ||
| napi_add_finalizer(env, argv[0], NULL, finalize, dup_name_arg(env, argv[1]), &saved_ref); | ||
| return argv[0]; | ||
| } | ||
|
|
||
| typedef struct { | ||
| char* outer_name; | ||
| char* recycled_name; | ||
| } RecycleContext; | ||
|
|
||
| // Runs as a teardown finalizer: deletes the ref from addFinalizerSaveRef, then | ||
| // registers a new finalizer. The allocator commonly hands back the just-freed | ||
| // NapiRef address; the new finalizer must still run. | ||
| static void recycle_finalize(napi_env env, void* data, void* hint) { | ||
| (void)hint; | ||
| RecycleContext* ctx = (RecycleContext*)data; | ||
| printf("finalize: %s\n", ctx->outer_name); | ||
| fflush(stdout); | ||
| finalize_count++; | ||
| void* old_ref = (void*)saved_ref; | ||
| napi_delete_reference(env, saved_ref); | ||
| napi_value obj; | ||
| napi_create_object(env, &obj); | ||
| napi_ref new_ref = NULL; | ||
| napi_add_finalizer(env, obj, NULL, finalize, ctx->recycled_name, &new_ref); | ||
| // Diagnostic only (not asserted): says whether the address-reuse collision | ||
| // this exercise targets actually happened on this platform and allocator. | ||
| printf("recycled-address: %s\n", (void*)new_ref == old_ref ? "yes" : "no"); | ||
| fflush(stdout); | ||
| free(ctx->outer_name); | ||
| free(ctx); | ||
| } | ||
|
|
||
| // wrapRecycling(obj, outerName, recycledName) | ||
| static napi_value wrap_recycling(napi_env env, napi_callback_info info) { | ||
| size_t argc = 3; | ||
| napi_value argv[3]; | ||
| napi_get_cb_info(env, info, &argc, argv, NULL, NULL); | ||
| RecycleContext* ctx = (RecycleContext*)malloc(sizeof(RecycleContext)); | ||
| ctx->outer_name = dup_name_arg(env, argv[1]); | ||
| ctx->recycled_name = dup_name_arg(env, argv[2]); | ||
| napi_wrap(env, argv[0], ctx, recycle_finalize, NULL, NULL); | ||
| return argv[0]; | ||
| } | ||
|
|
||
| static napi_value init(napi_env env, napi_value exports) { | ||
| napi_property_descriptor properties[] = { | ||
| { "wrap", 0, wrap, 0, 0, 0, napi_default, 0 }, | ||
| { "addFinalizer", 0, add_finalizer, 0, 0, 0, napi_default, 0 }, | ||
| { "createExternal", 0, create_external, 0, 0, 0, napi_default, 0 }, | ||
| { "finalizeCount", 0, get_finalize_count, 0, 0, 0, napi_default, 0 }, | ||
| { "wrapNesting", 0, wrap_nesting, 0, 0, 0, napi_default, 0 }, | ||
| { "addFinalizerSaveRef", 0, add_finalizer_save_ref, 0, 0, 0, napi_default, 0 }, | ||
| { "wrapRecycling", 0, wrap_recycling, 0, 0, 0, napi_default, 0 }, | ||
| }; | ||
| napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties); | ||
| return exports; | ||
| } | ||
|
|
||
| NAPI_MODULE(NODE_GYP_MODULE_NAME, init) |
Oops, something went wrong.
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.