diff options
author | Gabriel Schulhof <gabriel.schulhof@intel.com> | 2019-07-14 17:21:13 -0700 |
---|---|---|
committer | Gabriel Schulhof <gabriel.schulhof@intel.com> | 2019-07-25 16:53:07 -0700 |
commit | 5030e81ce305cc6bc5a1e17dd0ef8a9c761f035b (patch) | |
tree | 5678055a6286b9e8a452b83b05398b5fad51ee5b /test/node-api/test_instance_data | |
parent | e9ea8eaf7a77cf9b6f164cef92283c5eb99688cc (diff) | |
download | android-node-v8-5030e81ce305cc6bc5a1e17dd0ef8a9c761f035b.tar.gz android-node-v8-5030e81ce305cc6bc5a1e17dd0ef8a9c761f035b.tar.bz2 android-node-v8-5030e81ce305cc6bc5a1e17dd0ef8a9c761f035b.zip |
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 <info@bnoordhuis.nl>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Diffstat (limited to 'test/node-api/test_instance_data')
-rw-r--r-- | test/node-api/test_instance_data/binding.gyp | 10 | ||||
-rw-r--r-- | test/node-api/test_instance_data/test.js | 35 | ||||
-rw-r--r-- | test/node-api/test_instance_data/test_instance_data.c | 236 |
3 files changed, 281 insertions, 0 deletions
diff --git a/test/node-api/test_instance_data/binding.gyp b/test/node-api/test_instance_data/binding.gyp new file mode 100644 index 0000000000..0d55905e9e --- /dev/null +++ b/test/node-api/test_instance_data/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "test_instance_data", + "sources": [ + "test_instance_data.c" + ] + } + ] +} diff --git a/test/node-api/test_instance_data/test.js b/test/node-api/test_instance_data/test.js new file mode 100644 index 0000000000..969c164afd --- /dev/null +++ b/test/node-api/test_instance_data/test.js @@ -0,0 +1,35 @@ +'use strict'; +// Test API calls for instance data. + +const common = require('../../common'); + +if (module.parent) { + // When required as a module, run the tests. + const test_instance_data = + require(`./build/${common.buildType}/test_instance_data`); + + // Test that instance data can be used in an async work callback. + new Promise((resolve) => test_instance_data.asyncWorkCallback(resolve)) + + // Test that the buffer finalizer can access the instance data. + .then(() => new Promise((resolve) => { + test_instance_data.testBufferFinalizer(resolve); + global.gc(); + })) + + // Test that the thread-safe function can access the instance data. + .then(() => new Promise((resolve) => + test_instance_data.testThreadsafeFunction(common.mustCall(), + common.mustCall(resolve)))); +} 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'] }; + + // Run tests in a child process. + requireAs(__filename, ['--expose-gc'], runOptions, 'child'); + + // Run tests in a worker thread in a child process. + requireAs(__filename, ['--expose-gc'], runOptions, 'worker'); +} diff --git a/test/node-api/test_instance_data/test_instance_data.c b/test/node-api/test_instance_data/test_instance_data.c new file mode 100644 index 0000000000..1a814e91c0 --- /dev/null +++ b/test/node-api/test_instance_data/test_instance_data.c @@ -0,0 +1,236 @@ +#include <stdlib.h> +#include <uv.h> +#define NAPI_EXPERIMENTAL +#include <node_api.h> +#include "../../js-native-api/common.h" + +typedef struct { + napi_ref js_cb_ref; + napi_ref js_tsfn_finalizer_ref; + napi_threadsafe_function tsfn; + uv_thread_t thread; +} AddonData; + +static void AsyncWorkCbExecute(napi_env env, void* data) { + (void) env; + (void) data; +} + +static void call_cb_and_delete_ref(napi_env env, napi_ref* optional_ref) { + napi_value js_cb, undefined; + + if (optional_ref == NULL) { + AddonData* data; + NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + optional_ref = &data->js_cb_ref; + } + + NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, + *optional_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, *optional_ref)); + + *optional_ref = NULL; +} + +static void AsyncWorkCbComplete(napi_env env, + napi_status status, + void* data) { + (void) status; + (void) data; + call_cb_and_delete_ref(env, NULL); +} + +static bool establish_callback_ref(napi_env env, napi_callback_info info) { + AddonData* data; + size_t argc = 1; + napi_value js_cb; + + NAPI_CALL_BASE(env, napi_get_instance_data(env, (void**)&data), false); + NAPI_ASSERT_BASE(env, + data->js_cb_ref == NULL, + "reference must be NULL", + false); + NAPI_CALL_BASE(env, + napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL), + false); + NAPI_CALL_BASE(env, + napi_create_reference(env, js_cb, 1, &data->js_cb_ref), + false); + + return true; +} + +static napi_value AsyncWorkCallback(napi_env env, napi_callback_info info) { + if (establish_callback_ref(env, info)) { + napi_value resource_name; + napi_async_work work; + + NAPI_CALL(env, napi_create_string_utf8(env, + "AsyncIncrement", + NAPI_AUTO_LENGTH, + &resource_name)); + NAPI_CALL(env, napi_create_async_work(env, + NULL, + resource_name, + AsyncWorkCbExecute, + AsyncWorkCbComplete, + NULL, + &work)); + NAPI_CALL(env, napi_queue_async_work(env, work)); + } + + return NULL; +} + +static void TestBufferFinalizerCallback(napi_env env, void* data, void* hint) { + (void) data; + (void) hint; + call_cb_and_delete_ref(env, NULL); +} + +static napi_value TestBufferFinalizer(napi_env env, napi_callback_info info) { + napi_value buffer = NULL; + if (establish_callback_ref(env, info)) { + NAPI_CALL(env, napi_create_external_buffer(env, + sizeof(napi_callback), + TestBufferFinalizer, + TestBufferFinalizerCallback, + NULL, + &buffer)); + } + return buffer; +} + +static void ThreadsafeFunctionCallJS(napi_env env, + napi_value tsfn_cb, + void* context, + void* data) { + (void) tsfn_cb; + (void) context; + (void) data; + call_cb_and_delete_ref(env, NULL); +} + +static void ThreadsafeFunctionTestThread(void* raw_data) { + AddonData* data = raw_data; + napi_status status; + + // No need to call `napi_acquire_threadsafe_function()` because the main + // thread has set the refcount to 1 and there is only this one secondary + // thread. + status = napi_call_threadsafe_function(data->tsfn, + ThreadsafeFunctionCallJS, + napi_tsfn_nonblocking); + if (status != napi_ok) { + napi_fatal_error("ThreadSafeFunctionTestThread", + NAPI_AUTO_LENGTH, + "Failed to call TSFN", + NAPI_AUTO_LENGTH); + } + + status = napi_release_threadsafe_function(data->tsfn, napi_tsfn_release); + if (status != napi_ok) { + napi_fatal_error("ThreadSafeFunctionTestThread", + NAPI_AUTO_LENGTH, + "Failed to release TSFN", + NAPI_AUTO_LENGTH); + } + +} + +static void FinalizeThreadsafeFunction(napi_env env, void* raw, void* hint) { + AddonData* data; + NAPI_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); + NAPI_ASSERT_RETURN_VOID(env, + uv_thread_join(&data->thread) == 0, + "Failed to join the thread"); + call_cb_and_delete_ref(env, &data->js_tsfn_finalizer_ref); + data->tsfn = NULL; +} + +// Ths function accepts two arguments: the JS callback, and the finalize +// callback. The latter moves the test forward. +static napi_value +TestThreadsafeFunction(napi_env env, napi_callback_info info) { + AddonData* data; + size_t argc = 2; + napi_value argv[2], resource_name; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL)); + NAPI_CALL(env, napi_get_instance_data(env, (void**)&data)); + NAPI_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL"); + NAPI_ASSERT(env, + data->js_tsfn_finalizer_ref == NULL, + "tsfn finalizer reference must be NULL"); + NAPI_CALL(env, napi_create_reference(env, argv[0], 1, &data->js_cb_ref)); + NAPI_CALL(env, napi_create_reference(env, + argv[1], + 1, + &data->js_tsfn_finalizer_ref)); + NAPI_CALL(env, napi_create_string_utf8(env, + "TSFN instance data test", + NAPI_AUTO_LENGTH, + &resource_name)); + NAPI_CALL(env, napi_create_threadsafe_function(env, + NULL, + NULL, + resource_name, + 0, + 1, + NULL, + FinalizeThreadsafeFunction, + NULL, + ThreadsafeFunctionCallJS, + &data->tsfn)); + NAPI_ASSERT(env, + uv_thread_create(&data->thread, + ThreadsafeFunctionTestThread, + data) == 0, + "uv_thread_create failed"); + + return NULL; +} + +static void DeleteAddonData(napi_env env, void* raw_data, void* hint) { + AddonData* data = raw_data; + if (data->js_cb_ref) { + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref)); + } + if (data->js_tsfn_finalizer_ref) { + NAPI_CALL_RETURN_VOID(env, + napi_delete_reference(env, + data->js_tsfn_finalizer_ref)); + } + free(data); +} + +static napi_value Init(napi_env env, napi_value exports) { + AddonData* data = malloc(sizeof(*data)); + data->js_cb_ref = NULL; + data->js_tsfn_finalizer_ref = NULL; + + NAPI_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL)); + + napi_property_descriptor props[] = { + DECLARE_NAPI_PROPERTY("asyncWorkCallback", AsyncWorkCallback), + DECLARE_NAPI_PROPERTY("testBufferFinalizer", TestBufferFinalizer), + DECLARE_NAPI_PROPERTY("testThreadsafeFunction", TestThreadsafeFunction), + }; + + NAPI_CALL(env, napi_define_properties(env, + exports, + sizeof(props) / sizeof(*props), + props)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) |