node/test/js-native-api/test_general/test_general.c
Gabriel Schulhof 53ca0b9ae1 src: render N-API weak callbacks as cleanup hooks
Since worker threads are complete Node.js environments, including the
ability to load native addons, and since those native addons can
allocate resources to be freed when objects go out of scope, and since,
upon worker thread exit, the engine does not invoke the weak callbacks
responsible for freeing resources which still have references, this
modification introduces tracking for weak references such that a list
of outstanding weak references is maintained. This list is traversed
during environment teardown. The callbacks for the remaining weak
references are called.

This change is also relevant for Node.js embedder scenarios, because in
those cases the process also outlives the `node::Environment` and
therefore weak callbacks should also be rendered as environment cleanup
hooks to ensure proper cleanup after native addons. This changes
introduces the means by which this can be accomplished.

A benchmark is included which measures the time it takes to execute the
weak reference callback for a given number of weak references.

Re: https://github.com/tc39/proposal-weakrefs/issues/125#issuecomment-535832130
PR-URL: https://github.com/nodejs/node/pull/28428
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
2019-10-13 00:07:43 -07:00

314 lines
9.8 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <js_native_api.h>
#include "../common.h"
static napi_value testStrictEquals(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
bool bool_result;
napi_value result;
NAPI_CALL(env, napi_strict_equals(env, args[0], args[1], &bool_result));
NAPI_CALL(env, napi_get_boolean(env, bool_result, &result));
return result;
}
static napi_value testGetPrototype(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
napi_value result;
NAPI_CALL(env, napi_get_prototype(env, args[0], &result));
return result;
}
static napi_value testGetVersion(napi_env env, napi_callback_info info) {
uint32_t version;
napi_value result;
NAPI_CALL(env, napi_get_version(env, &version));
NAPI_CALL(env, napi_create_uint32(env, version, &result));
return result;
}
static napi_value doInstanceOf(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
bool instanceof;
NAPI_CALL(env, napi_instanceof(env, args[0], args[1], &instanceof));
napi_value result;
NAPI_CALL(env, napi_get_boolean(env, instanceof, &result));
return result;
}
static napi_value getNull(napi_env env, napi_callback_info info) {
napi_value result;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}
static napi_value getUndefined(napi_env env, napi_callback_info info) {
napi_value result;
NAPI_CALL(env, napi_get_undefined(env, &result));
return result;
}
static napi_value createNapiError(napi_env env, napi_callback_info info) {
napi_value value;
NAPI_CALL(env, napi_create_string_utf8(env, "xyz", 3, &value));
double double_value;
napi_status status = napi_get_value_double(env, value, &double_value);
NAPI_ASSERT(env, status != napi_ok, "Failed to produce error condition");
const napi_extended_error_info *error_info = 0;
NAPI_CALL(env, napi_get_last_error_info(env, &error_info));
NAPI_ASSERT(env, error_info->error_code == status,
"Last error info code should match last status");
NAPI_ASSERT(env, error_info->error_message,
"Last error info message should not be null");
return NULL;
}
static napi_value testNapiErrorCleanup(napi_env env, napi_callback_info info) {
const napi_extended_error_info *error_info = 0;
NAPI_CALL(env, napi_get_last_error_info(env, &error_info));
napi_value result;
bool is_ok = error_info->error_code == napi_ok;
NAPI_CALL(env, napi_get_boolean(env, is_ok, &result));
return result;
}
static napi_value testNapiTypeof(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
napi_valuetype argument_type;
NAPI_CALL(env, napi_typeof(env, args[0], &argument_type));
napi_value result = NULL;
if (argument_type == napi_number) {
NAPI_CALL(env, napi_create_string_utf8(
env, "number", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_string) {
NAPI_CALL(env, napi_create_string_utf8(
env, "string", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_function) {
NAPI_CALL(env, napi_create_string_utf8(
env, "function", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_object) {
NAPI_CALL(env, napi_create_string_utf8(
env, "object", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_boolean) {
NAPI_CALL(env, napi_create_string_utf8(
env, "boolean", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_undefined) {
NAPI_CALL(env, napi_create_string_utf8(
env, "undefined", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_symbol) {
NAPI_CALL(env, napi_create_string_utf8(
env, "symbol", NAPI_AUTO_LENGTH, &result));
} else if (argument_type == napi_null) {
NAPI_CALL(env, napi_create_string_utf8(
env, "null", NAPI_AUTO_LENGTH, &result));
}
return result;
}
static bool deref_item_called = false;
static void deref_item(napi_env env, void* data, void* hint) {
(void) hint;
NAPI_ASSERT_RETURN_VOID(env, data == &deref_item_called,
"Finalize callback was called with the correct pointer");
deref_item_called = true;
}
static napi_value deref_item_was_called(napi_env env, napi_callback_info info) {
napi_value it_was_called;
NAPI_CALL(env, napi_get_boolean(env, deref_item_called, &it_was_called));
return it_was_called;
}
static napi_value wrap_first_arg(napi_env env,
napi_callback_info info,
napi_finalize finalizer,
void* data) {
size_t argc = 1;
napi_value to_wrap;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &to_wrap, NULL, NULL));
NAPI_CALL(env, napi_wrap(env, to_wrap, data, finalizer, NULL, NULL));
return to_wrap;
}
static napi_value wrap(napi_env env, napi_callback_info info) {
deref_item_called = false;
return wrap_first_arg(env, info, deref_item, &deref_item_called);
}
static napi_value unwrap(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value wrapped;
void* data;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &wrapped, NULL, NULL));
NAPI_CALL(env, napi_unwrap(env, wrapped, &data));
return NULL;
}
static napi_value remove_wrap(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value wrapped;
void* data;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &wrapped, NULL, NULL));
NAPI_CALL(env, napi_remove_wrap(env, wrapped, &data));
return NULL;
}
static bool finalize_called = false;
static void test_finalize(napi_env env, void* data, void* hint) {
finalize_called = true;
}
static napi_value test_finalize_wrap(napi_env env, napi_callback_info info) {
return wrap_first_arg(env, info, test_finalize, NULL);
}
static napi_value finalize_was_called(napi_env env, napi_callback_info info) {
napi_value it_was_called;
NAPI_CALL(env, napi_get_boolean(env, finalize_called, &it_was_called));
return it_was_called;
}
static napi_value testAdjustExternalMemory(napi_env env, napi_callback_info info) {
napi_value result;
int64_t adjustedValue;
NAPI_CALL(env, napi_adjust_external_memory(env, 1, &adjustedValue));
NAPI_CALL(env, napi_create_double(env, (double)adjustedValue, &result));
return result;
}
static napi_value testNapiRun(napi_env env, napi_callback_info info) {
napi_value script, result;
size_t argc = 1;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &script, NULL, NULL));
NAPI_CALL(env, napi_run_script(env, script, &result));
return result;
}
static void finalizer_only_callback(napi_env env, void* data, void* hint) {
napi_ref js_cb_ref = data;
napi_value js_cb, undefined;
NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, js_cb_ref, &js_cb));
NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
NAPI_CALL_RETURN_VOID(env,
napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, js_cb_ref));
}
static napi_value add_finalizer_only(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2];
napi_ref js_cb_ref;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
NAPI_CALL(env, napi_create_reference(env, argv[1], 1, &js_cb_ref));
NAPI_CALL(env,
napi_add_finalizer(env,
argv[0],
js_cb_ref,
finalizer_only_callback,
NULL,
NULL));
return NULL;
}
static const char* env_cleanup_finalizer_messages[] = {
"simple wrap",
"wrap, removeWrap",
"first wrap",
"second wrap"
};
static void cleanup_env_finalizer(napi_env env, void* data, void* hint) {
(void) env;
(void) hint;
printf("finalize at env cleanup for %s\n",
env_cleanup_finalizer_messages[(uintptr_t)data]);
}
static napi_value env_cleanup_wrap(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2];
uint32_t value;
uintptr_t ptr_value;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
NAPI_CALL(env, napi_get_value_uint32(env, argv[1], &value));
ptr_value = value;
return wrap_first_arg(env, info, cleanup_env_finalizer, (void*)ptr_value);
}
EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
DECLARE_NAPI_PROPERTY("testStrictEquals", testStrictEquals),
DECLARE_NAPI_PROPERTY("testGetPrototype", testGetPrototype),
DECLARE_NAPI_PROPERTY("testGetVersion", testGetVersion),
DECLARE_NAPI_PROPERTY("testNapiRun", testNapiRun),
DECLARE_NAPI_PROPERTY("doInstanceOf", doInstanceOf),
DECLARE_NAPI_PROPERTY("getUndefined", getUndefined),
DECLARE_NAPI_PROPERTY("getNull", getNull),
DECLARE_NAPI_PROPERTY("createNapiError", createNapiError),
DECLARE_NAPI_PROPERTY("testNapiErrorCleanup", testNapiErrorCleanup),
DECLARE_NAPI_PROPERTY("testNapiTypeof", testNapiTypeof),
DECLARE_NAPI_PROPERTY("wrap", wrap),
DECLARE_NAPI_PROPERTY("envCleanupWrap", env_cleanup_wrap),
DECLARE_NAPI_PROPERTY("unwrap", unwrap),
DECLARE_NAPI_PROPERTY("removeWrap", remove_wrap),
DECLARE_NAPI_PROPERTY("addFinalizerOnly", add_finalizer_only),
DECLARE_NAPI_PROPERTY("testFinalizeWrap", test_finalize_wrap),
DECLARE_NAPI_PROPERTY("finalizeWasCalled", finalize_was_called),
DECLARE_NAPI_PROPERTY("derefItemWasCalled", deref_item_was_called),
DECLARE_NAPI_PROPERTY("testAdjustExternalMemory", testAdjustExternalMemory)
};
NAPI_CALL(env, napi_define_properties(
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
return exports;
}
EXTERN_C_END