From 5030e81ce305cc6bc5a1e17dd0ef8a9c761f035b Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Sun, 14 Jul 2019 17:21:13 -0700 Subject: n-api: add APIs for per-instance state management Adds `napi_set_instance_data()` and `napi_get_instance_data()`, which allow native addons to store their data on and retrieve their data from `napi_env`. `napi_set_instance_data()` accepts a finalizer which is called when the `node::Environment()` is destroyed. This entails rendering the `napi_env` local to each add-on. Fixes: https://github.com/nodejs/abi-stable-node/issues/378 PR-URL: https://github.com/nodejs/node/pull/28682 Reviewed-By: Ben Noordhuis Reviewed-By: Michael Dawson --- test/js-native-api/test_instance_data/binding.gyp | 11 +++ test/js-native-api/test_instance_data/test.js | 40 +++++++++ .../test_instance_data/test_instance_data.c | 97 ++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 test/js-native-api/test_instance_data/binding.gyp create mode 100644 test/js-native-api/test_instance_data/test.js create mode 100644 test/js-native-api/test_instance_data/test_instance_data.c (limited to 'test/js-native-api') diff --git a/test/js-native-api/test_instance_data/binding.gyp b/test/js-native-api/test_instance_data/binding.gyp new file mode 100644 index 0000000000..5b2d4ff328 --- /dev/null +++ b/test/js-native-api/test_instance_data/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "test_instance_data", + "sources": [ + "../entry_point.c", + "test_instance_data.c" + ] + } + ] +} diff --git a/test/js-native-api/test_instance_data/test.js b/test/js-native-api/test_instance_data/test.js new file mode 100644 index 0000000000..986f644fd2 --- /dev/null +++ b/test/js-native-api/test_instance_data/test.js @@ -0,0 +1,40 @@ +'use strict'; +// Test API calls for instance data. + +const common = require('../../common'); +const assert = require('assert'); + +if (module.parent) { + // When required as a module, run the tests. + const test_instance_data = + require(`./build/${common.buildType}/test_instance_data`); + + // Print to stdout when the environment deletes the instance data. This output + // is checked by the parent process. + test_instance_data.setPrintOnDelete(); + + // Test that instance data can be accessed from a binding. + assert.strictEqual(test_instance_data.increment(), 42); + + // Test that the instance data can be accessed from a finalizer. + test_instance_data.objectWithFinalizer(common.mustCall()); + global.gc(); +} else { + // When launched as a script, run tests in either a child process or in a + // worker thread. + const requireAs = require('../../common/require-as'); + const runOptions = { stdio: ['inherit', 'pipe', 'inherit'] }; + + function checkOutput(child) { + assert.strictEqual(child.status, 0); + assert.strictEqual( + (child.stdout.toString().split(/\r\n?|\n/) || [])[0], + 'deleting addon data'); + } + + // Run tests in a child process. + checkOutput(requireAs(__filename, ['--expose-gc'], runOptions, 'child')); + + // Run tests in a worker thread in a child process. + checkOutput(requireAs(__filename, ['--expose-gc'], runOptions, 'worker')); +} diff --git a/test/js-native-api/test_instance_data/test_instance_data.c b/test/js-native-api/test_instance_data/test_instance_data.c new file mode 100644 index 0000000000..a64ebec0c1 --- /dev/null +++ b/test/js-native-api/test_instance_data/test_instance_data.c @@ -0,0 +1,97 @@ +#include +#include +#define NAPI_EXPERIMENTAL +#include +#include "../common.h" + +typedef struct { + size_t value; + bool print; + napi_ref js_cb_ref; +} AddonData; + +static napi_value Increment(napi_env env, napi_callback_info info) { + AddonData* data; + napi_value result; + + NAPI_CALL(env, napi_get_instance_data(env, (void**)&data)); + NAPI_CALL(env, napi_create_uint32(env, ++data->value, &result)); + + return result; +} + +static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { + AddonData* data = raw_data; + if (data->print) { + printf("deleting addon data\n"); + } + if (data->js_cb_ref != NULL) { + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); + } + free(data); +} + +static napi_value SetPrintOnDelete(napi_env env, napi_callback_info info) { + AddonData* data; + + NAPI_CALL(env, napi_get_instance_data(env, (void**)&data)); + data->print = true; + + return NULL; +} + +static void TestFinalizer(napi_env env, void* raw_data, void* hint) { + (void) raw_data; + (void) hint; + + AddonData* data; + NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + napi_value js_cb, undefined; + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, data->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, data->js_cb_ref)); + data->js_cb_ref = NULL; +} + +static napi_value ObjectWithFinalizer(napi_env env, napi_callback_info info) { + AddonData* data; + napi_value result, js_cb; + size_t argc = 1; + + NAPI_CALL(env, napi_get_instance_data(env, (void**)&data)); + NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL)); + NAPI_CALL(env, napi_create_object(env, &result)); + NAPI_CALL(env, + napi_add_finalizer(env, result, NULL, TestFinalizer, NULL, NULL)); + NAPI_CALL(env, napi_create_reference(env, js_cb, 1, &data->js_cb_ref)); + + return result; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + AddonData* data = malloc(sizeof(*data)); + data->value = 41; + data->print = false; + data->js_cb_ref = NULL; + + NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); + + napi_property_descriptor props[] = { + DECLARE_NAPI_PROPERTY("increment", Increment), + DECLARE_NAPI_PROPERTY("setPrintOnDelete", SetPrintOnDelete), + DECLARE_NAPI_PROPERTY("objectWithFinalizer", ObjectWithFinalizer), + }; + + NAPI_CALL(env, napi_define_properties(env, + exports, + sizeof(props) / sizeof(*props), + props)); + + return exports; +} +EXTERN_C_END -- cgit v1.2.3