mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 21:01:52 +00:00
Start fixing bugs discovered by Node.js's Node-API tests (#14501)
Co-authored-by: Kai Tamkun <kai@tamkun.io> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> Co-authored-by: Ashcon Partovi <ashcon@partovi.net> Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com> Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com> Co-authored-by: 190n <190n@users.noreply.github.com>
This commit is contained in:
0
test/napi/node-napi-tests/test/node-api/.buildstamp
Normal file
0
test/napi/node-napi-tests/test/node-api/.buildstamp
Normal file
1
test/napi/node-napi-tests/test/node-api/.gitignore
vendored
Normal file
1
test/napi/node-napi-tests/test/node-api/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
#include <string.h>
|
||||
|
||||
static napi_value Method(napi_env env, napi_callback_info info) {
|
||||
napi_value world;
|
||||
const char* str = "world";
|
||||
size_t str_len = strlen(str);
|
||||
NODE_API_CALL(env, napi_create_string_utf8(env, str, str_len, &world));
|
||||
return world;
|
||||
}
|
||||
|
||||
NAPI_MODULE_INIT() {
|
||||
napi_property_descriptor desc = DECLARE_NODE_API_PROPERTY("hello", Method);
|
||||
NODE_API_CALL(env, napi_define_properties(env, exports, 1, &desc));
|
||||
return exports;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "binding",
|
||||
"sources": [ "binding.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const { Worker } = require('worker_threads');
|
||||
|
||||
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
|
||||
const binding = require(bindingPath);
|
||||
assert.strictEqual(binding.hello(), 'world');
|
||||
console.log('binding.hello() =', binding.hello());
|
||||
|
||||
// Test multiple loading of the same module.
|
||||
delete require.cache[bindingPath];
|
||||
const rebinding = require(bindingPath);
|
||||
assert.strictEqual(rebinding.hello(), 'world');
|
||||
assert.notStrictEqual(binding.hello, rebinding.hello);
|
||||
|
||||
// Test that workers can load addons declared using NAPI_MODULE_INIT().
|
||||
new Worker(`
|
||||
const { parentPort } = require('worker_threads');
|
||||
const msg = require(${JSON.stringify(bindingPath)}).hello();
|
||||
parentPort.postMessage(msg)`, { eval: true })
|
||||
.on('message', common.mustCall((msg) => assert.strictEqual(msg, 'world')));
|
||||
14
test/napi/node-napi-tests/test/node-api/node-api.status
Normal file
14
test/napi/node-napi-tests/test/node-api/node-api.status
Normal file
@@ -0,0 +1,14 @@
|
||||
prefix node-api
|
||||
|
||||
# To mark a test as flaky, list the test name in the appropriate section
|
||||
# below, without ".js", followed by ": PASS,FLAKY". Example:
|
||||
# sample-test : PASS,FLAKY
|
||||
|
||||
[true] # This section applies to all platforms
|
||||
|
||||
[$system==win32]
|
||||
|
||||
[$system==solaris] # Also applies to SmartOS
|
||||
# https://github.com/nodejs/node/issues/43457
|
||||
test_fatal/test_threads: PASS,FLAKY
|
||||
test_fatal/test_threads_report: PASS,FLAKY
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_async",
|
||||
"sources": [ "test_async.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const test_async = require(`./build/${common.buildType}/test_async`);
|
||||
|
||||
const events = [];
|
||||
let testId;
|
||||
const initAsyncId = async_hooks.executionAsyncId();
|
||||
|
||||
async_hooks.createHook({
|
||||
init(id, provider, triggerAsyncId, resource) {
|
||||
if (provider === 'TestResource') {
|
||||
testId = id;
|
||||
events.push({ type: 'init', id, provider, triggerAsyncId, resource });
|
||||
}
|
||||
},
|
||||
before(id) {
|
||||
if (testId === id) {
|
||||
events.push({ type: 'before', id });
|
||||
}
|
||||
},
|
||||
after(id) {
|
||||
if (testId === id) {
|
||||
events.push({ type: 'after', id });
|
||||
}
|
||||
},
|
||||
destroy(id) {
|
||||
if (testId === id) {
|
||||
events.push({ type: 'destroy', id });
|
||||
}
|
||||
},
|
||||
}).enable();
|
||||
|
||||
const resource = { foo: 'foo' };
|
||||
|
||||
events.push({ type: 'start' });
|
||||
test_async.Test(5, resource, common.mustCall(function(err, val) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(val, 10);
|
||||
events.push({ type: 'complete' });
|
||||
process.nextTick(common.mustCall());
|
||||
}));
|
||||
events.push({ type: 'scheduled' });
|
||||
|
||||
process.on('exit', () => {
|
||||
assert.deepStrictEqual(events, [
|
||||
{ type: 'start' },
|
||||
{ type: 'init',
|
||||
id: testId,
|
||||
provider: 'TestResource',
|
||||
triggerAsyncId: initAsyncId,
|
||||
resource },
|
||||
{ type: 'scheduled' },
|
||||
{ type: 'before', id: testId },
|
||||
{ type: 'complete' },
|
||||
{ type: 'after', id: testId },
|
||||
{ type: 'destroy', id: testId },
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const test_async = require(`./build/${common.buildType}/test_async`);
|
||||
const iterations = 500;
|
||||
|
||||
let x = 0;
|
||||
const workDone = common.mustCall((status) => {
|
||||
assert.strictEqual(status, 0);
|
||||
if (++x < iterations) {
|
||||
setImmediate(() => test_async.DoRepeatedWork(workDone));
|
||||
}
|
||||
}, iterations);
|
||||
test_async.DoRepeatedWork(workDone);
|
||||
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const test_async = require(`./build/${common.buildType}/test_async`);
|
||||
|
||||
process.on('uncaughtException', common.mustCall(function(err) {
|
||||
try {
|
||||
throw new Error('should not fail');
|
||||
} catch (err) {
|
||||
assert.strictEqual(err.message, 'should not fail');
|
||||
}
|
||||
assert.strictEqual(err.message, 'uncaught');
|
||||
}));
|
||||
|
||||
// Successful async execution and completion callback.
|
||||
test_async.Test(5, {}, common.mustCall(function() {
|
||||
throw new Error('uncaught');
|
||||
}));
|
||||
30
test/napi/node-napi-tests/test/node-api/test_async/test.js
Normal file
30
test/napi/node-napi-tests/test/node-api/test_async/test.js
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const test_async = require(`./build/${common.buildType}/test_async`);
|
||||
|
||||
const testException = 'test_async_cb_exception';
|
||||
|
||||
// Exception thrown from async completion callback.
|
||||
// (Tested in a spawned process because the exception is fatal.)
|
||||
if (process.argv[2] === 'child') {
|
||||
test_async.Test(1, {}, common.mustCall(function() {
|
||||
throw new Error(testException);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const p = child_process.spawnSync(
|
||||
process.execPath, [ __filename, 'child' ]);
|
||||
assert.ifError(p.error);
|
||||
assert.ok(p.stderr.toString().includes(testException));
|
||||
|
||||
// Successful async execution and completion callback.
|
||||
test_async.Test(5, {}, common.mustCall(function(err, val) {
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(val, 10);
|
||||
process.nextTick(common.mustCall());
|
||||
}));
|
||||
|
||||
// Async work item cancellation with callback.
|
||||
test_async.TestCancel(common.mustCall());
|
||||
216
test/napi/node-napi-tests/test/node-api/test_async/test_async.c
Normal file
216
test/napi/node-napi-tests/test/node-api/test_async/test_async.c
Normal file
@@ -0,0 +1,216 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <node_api.h>
|
||||
#include <uv.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
// this needs to be greater than the thread pool size
|
||||
#define MAX_CANCEL_THREADS 6
|
||||
|
||||
typedef struct {
|
||||
int32_t _input;
|
||||
int32_t _output;
|
||||
napi_ref _callback;
|
||||
napi_async_work _request;
|
||||
} carrier;
|
||||
|
||||
static carrier the_carrier;
|
||||
static carrier async_carrier[MAX_CANCEL_THREADS];
|
||||
|
||||
static void Execute(napi_env env, void* data) {
|
||||
uv_sleep(1000);
|
||||
carrier* c = (carrier*)(data);
|
||||
|
||||
assert(c == &the_carrier);
|
||||
|
||||
c->_output = c->_input * 2;
|
||||
}
|
||||
|
||||
static void Complete(napi_env env, napi_status status, void* data) {
|
||||
carrier* c = (carrier*)(data);
|
||||
|
||||
if (c != &the_carrier) {
|
||||
napi_throw_type_error(env, NULL, "Wrong data parameter to Complete.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != napi_ok) {
|
||||
napi_throw_type_error(env, NULL, "Execute callback failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
napi_value argv[2];
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_null(env, &argv[0]));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_int32(env, c->_output, &argv[1]));
|
||||
napi_value callback;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_get_reference_value(env, c->_callback, &callback));
|
||||
napi_value global;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &global));
|
||||
|
||||
napi_value result;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_call_function(env, global, callback, 2, argv, &result));
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
|
||||
}
|
||||
|
||||
static napi_value Test(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 3;
|
||||
napi_value argv[3];
|
||||
napi_value _this;
|
||||
napi_value resource_name;
|
||||
void* data;
|
||||
NODE_API_CALL(env,
|
||||
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
|
||||
NODE_API_ASSERT(env, argc >= 3, "Not enough arguments, expected 3.");
|
||||
|
||||
napi_valuetype t;
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[0], &t));
|
||||
NODE_API_ASSERT(env, t == napi_number,
|
||||
"Wrong first argument, integer expected.");
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[1], &t));
|
||||
NODE_API_ASSERT(env, t == napi_object,
|
||||
"Wrong second argument, object expected.");
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[2], &t));
|
||||
NODE_API_ASSERT(env, t == napi_function,
|
||||
"Wrong third argument, function expected.");
|
||||
|
||||
the_carrier._output = 0;
|
||||
|
||||
NODE_API_CALL(env,
|
||||
napi_get_value_int32(env, argv[0], &the_carrier._input));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_reference(env, argv[2], 1, &the_carrier._callback));
|
||||
|
||||
NODE_API_CALL(env, napi_create_string_utf8(
|
||||
env, "TestResource", NAPI_AUTO_LENGTH, &resource_name));
|
||||
NODE_API_CALL(env, napi_create_async_work(env, argv[1], resource_name,
|
||||
Execute, Complete, &the_carrier, &the_carrier._request));
|
||||
NODE_API_CALL(env,
|
||||
napi_queue_async_work(env, the_carrier._request));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void BusyCancelComplete(napi_env env, napi_status status, void* data) {
|
||||
carrier* c = (carrier*)(data);
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
|
||||
}
|
||||
|
||||
static void CancelComplete(napi_env env, napi_status status, void* data) {
|
||||
carrier* c = (carrier*)(data);
|
||||
|
||||
if (status == napi_cancelled) {
|
||||
// ok we got the status we expected so make the callback to
|
||||
// indicate the cancel succeeded.
|
||||
napi_value callback;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_get_reference_value(env, c->_callback, &callback));
|
||||
napi_value global;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &global));
|
||||
napi_value result;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_call_function(env, global, callback, 0, NULL, &result));
|
||||
}
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback));
|
||||
}
|
||||
|
||||
static void CancelExecute(napi_env env, void* data) {
|
||||
uv_sleep(1000);
|
||||
}
|
||||
|
||||
static napi_value TestCancel(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value argv[1];
|
||||
napi_value _this;
|
||||
napi_value resource_name;
|
||||
void* data;
|
||||
|
||||
NODE_API_CALL(env, napi_create_string_utf8(
|
||||
env, "TestResource", NAPI_AUTO_LENGTH, &resource_name));
|
||||
|
||||
// make sure the work we are going to cancel will not be
|
||||
// able to start by using all the threads in the pool
|
||||
for (int i = 1; i < MAX_CANCEL_THREADS; i++) {
|
||||
NODE_API_CALL(env, napi_create_async_work(env, NULL, resource_name,
|
||||
CancelExecute, BusyCancelComplete,
|
||||
&async_carrier[i], &async_carrier[i]._request));
|
||||
NODE_API_CALL(env, napi_queue_async_work(env, async_carrier[i]._request));
|
||||
}
|
||||
|
||||
// now queue the work we are going to cancel and then cancel it.
|
||||
// cancel will fail if the work has already started, but
|
||||
// we have prevented it from starting by consuming all of the
|
||||
// workers above.
|
||||
NODE_API_CALL(env,
|
||||
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
|
||||
NODE_API_CALL(env, napi_create_async_work(env, NULL, resource_name,
|
||||
CancelExecute, CancelComplete,
|
||||
&async_carrier[0], &async_carrier[0]._request));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_reference(env, argv[0], 1, &async_carrier[0]._callback));
|
||||
NODE_API_CALL(env, napi_queue_async_work(env, async_carrier[0]._request));
|
||||
NODE_API_CALL(env, napi_cancel_async_work(env, async_carrier[0]._request));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct {
|
||||
napi_ref ref;
|
||||
napi_async_work work;
|
||||
} repeated_work_info = { NULL, NULL };
|
||||
|
||||
static void RepeatedWorkerThread(napi_env env, void* data) {}
|
||||
|
||||
static void RepeatedWorkComplete(napi_env env, napi_status status, void* data) {
|
||||
napi_value cb, js_status;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_get_reference_value(env, repeated_work_info.ref, &cb));
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_delete_async_work(env, repeated_work_info.work));
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_delete_reference(env, repeated_work_info.ref));
|
||||
repeated_work_info.work = NULL;
|
||||
repeated_work_info.ref = NULL;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_create_uint32(env, (uint32_t)status, &js_status));
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_call_function(env, cb, cb, 1, &js_status, NULL));
|
||||
}
|
||||
|
||||
static napi_value DoRepeatedWork(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value cb, name;
|
||||
NODE_API_ASSERT(env, repeated_work_info.ref == NULL,
|
||||
"Reference left over from previous work");
|
||||
NODE_API_ASSERT(env, repeated_work_info.work == NULL,
|
||||
"Work pointer left over from previous work");
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL));
|
||||
NODE_API_CALL(env, napi_create_reference(env, cb, 1, &repeated_work_info.ref));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(env, "Repeated Work", NAPI_AUTO_LENGTH, &name));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_async_work(env, NULL, name, RepeatedWorkerThread,
|
||||
RepeatedWorkComplete, &repeated_work_info, &repeated_work_info.work));
|
||||
NODE_API_CALL(env, napi_queue_async_work(env, repeated_work_info.work));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("Test", Test),
|
||||
DECLARE_NODE_API_PROPERTY("TestCancel", TestCancel),
|
||||
DECLARE_NODE_API_PROPERTY("DoRepeatedWork", DoRepeatedWork),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "node_api.h"
|
||||
#include "assert.h"
|
||||
#include "uv.h"
|
||||
#include <stdlib.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static int cleanup_hook_count = 0;
|
||||
static void MustNotCall(napi_async_cleanup_hook_handle hook, void* arg) {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
struct AsyncData {
|
||||
uv_async_t async;
|
||||
napi_env env;
|
||||
napi_async_cleanup_hook_handle handle;
|
||||
};
|
||||
|
||||
static struct AsyncData* CreateAsyncData() {
|
||||
struct AsyncData* data = (struct AsyncData*) malloc(sizeof(struct AsyncData));
|
||||
data->handle = NULL;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void AfterCleanupHookTwo(uv_handle_t* handle) {
|
||||
cleanup_hook_count++;
|
||||
struct AsyncData* data = (struct AsyncData*) handle->data;
|
||||
napi_status status = napi_remove_async_cleanup_hook(data->handle);
|
||||
assert(status == napi_ok);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void AfterCleanupHookOne(uv_async_t* async) {
|
||||
cleanup_hook_count++;
|
||||
uv_close((uv_handle_t*) async, AfterCleanupHookTwo);
|
||||
}
|
||||
|
||||
static void AsyncCleanupHook(napi_async_cleanup_hook_handle handle, void* arg) {
|
||||
cleanup_hook_count++;
|
||||
struct AsyncData* data = (struct AsyncData*) arg;
|
||||
uv_loop_t* loop;
|
||||
napi_status status = napi_get_uv_event_loop(data->env, &loop);
|
||||
assert(status == napi_ok);
|
||||
int err = uv_async_init(loop, &data->async, AfterCleanupHookOne);
|
||||
assert(err == 0);
|
||||
|
||||
data->async.data = data;
|
||||
data->handle = handle;
|
||||
uv_async_send(&data->async);
|
||||
}
|
||||
|
||||
static void ObjectFinalizer(napi_env env, void* data, void* hint) {
|
||||
// AsyncCleanupHook and its subsequent callbacks are called twice.
|
||||
assert(cleanup_hook_count == 6);
|
||||
|
||||
napi_ref* ref = data;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, *ref));
|
||||
free(ref);
|
||||
}
|
||||
|
||||
static void CreateObjectWrap(napi_env env) {
|
||||
napi_value js_obj;
|
||||
napi_ref* ref = malloc(sizeof(*ref));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &js_obj));
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_wrap(env, js_obj, ref, ObjectFinalizer, NULL, ref));
|
||||
// create a strong reference so that the finalizer is called at shutdown.
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_reference_ref(env, *ref, NULL));
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
// Reinitialize the static variable to be compatible with musl libc.
|
||||
cleanup_hook_count = 0;
|
||||
// Create object wrap before cleanup hooks.
|
||||
CreateObjectWrap(env);
|
||||
|
||||
{
|
||||
struct AsyncData* data = CreateAsyncData();
|
||||
data->env = env;
|
||||
napi_add_async_cleanup_hook(env, AsyncCleanupHook, data, &data->handle);
|
||||
}
|
||||
|
||||
{
|
||||
struct AsyncData* data = CreateAsyncData();
|
||||
data->env = env;
|
||||
napi_add_async_cleanup_hook(env, AsyncCleanupHook, data, NULL);
|
||||
}
|
||||
|
||||
{
|
||||
napi_async_cleanup_hook_handle must_not_call_handle;
|
||||
napi_add_async_cleanup_hook(
|
||||
env, MustNotCall, NULL, &must_not_call_handle);
|
||||
napi_remove_async_cleanup_hook(must_not_call_handle);
|
||||
}
|
||||
|
||||
// Create object wrap after cleanup hooks.
|
||||
CreateObjectWrap(env);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const path = require('path');
|
||||
const { Worker } = require('worker_threads');
|
||||
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
|
||||
|
||||
const w = new Worker(`require(${JSON.stringify(binding)})`, { eval: true });
|
||||
w.on('exit', common.mustCall(() => require(binding)));
|
||||
@@ -0,0 +1,129 @@
|
||||
#include <node_api.h>
|
||||
#include <assert.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
#define MAX_ARGUMENTS 10
|
||||
#define RESERVED_ARGS 3
|
||||
|
||||
static napi_value MakeCallback(napi_env env, napi_callback_info info) {
|
||||
size_t argc = MAX_ARGUMENTS;
|
||||
size_t n;
|
||||
napi_value args[MAX_ARGUMENTS];
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
|
||||
NODE_API_ASSERT(env, argc > 0, "Wrong number of arguments");
|
||||
|
||||
napi_value async_context_wrap = args[0];
|
||||
napi_value recv = args[1];
|
||||
napi_value func = args[2];
|
||||
|
||||
napi_value argv[MAX_ARGUMENTS - RESERVED_ARGS];
|
||||
for (n = RESERVED_ARGS; n < argc; n += 1) {
|
||||
argv[n - RESERVED_ARGS] = args[n];
|
||||
}
|
||||
|
||||
napi_valuetype func_type;
|
||||
NODE_API_CALL(env, napi_typeof(env, func, &func_type));
|
||||
|
||||
napi_async_context context;
|
||||
NODE_API_CALL(env, napi_unwrap(env, async_context_wrap, (void**)&context));
|
||||
|
||||
napi_value result;
|
||||
if (func_type == napi_function) {
|
||||
NODE_API_CALL(env, napi_make_callback(
|
||||
env, context, recv, func, argc - RESERVED_ARGS, argv, &result));
|
||||
} else {
|
||||
NODE_API_ASSERT(env, false, "Unexpected argument type");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void AsyncDestroyCb(napi_env env, void* data, void* hint) {
|
||||
napi_status status = napi_async_destroy(env, (napi_async_context)data);
|
||||
// We cannot use NODE_API_ASSERT_RETURN_VOID because we need to have a JS
|
||||
// stack below in order to use exceptions.
|
||||
assert(status == napi_ok);
|
||||
}
|
||||
|
||||
#define CREATE_ASYNC_RESOURCE_ARGC 2
|
||||
|
||||
static napi_value CreateAsyncResource(napi_env env, napi_callback_info info) {
|
||||
napi_value async_context_wrap;
|
||||
NODE_API_CALL(env, napi_create_object(env, &async_context_wrap));
|
||||
|
||||
size_t argc = CREATE_ASYNC_RESOURCE_ARGC;
|
||||
napi_value args[CREATE_ASYNC_RESOURCE_ARGC];
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
|
||||
napi_value resource = args[0];
|
||||
napi_value js_destroy_on_finalizer = args[1];
|
||||
napi_valuetype resource_type;
|
||||
NODE_API_CALL(env, napi_typeof(env, resource, &resource_type));
|
||||
if (resource_type != napi_object) {
|
||||
resource = NULL;
|
||||
}
|
||||
|
||||
napi_value resource_name;
|
||||
NODE_API_CALL(env, napi_create_string_utf8(
|
||||
env, "test_async", NAPI_AUTO_LENGTH, &resource_name));
|
||||
|
||||
napi_async_context context;
|
||||
NODE_API_CALL(env, napi_async_init(env, resource, resource_name, &context));
|
||||
|
||||
bool destroy_on_finalizer = true;
|
||||
if (argc == 2) {
|
||||
NODE_API_CALL(env, napi_get_value_bool(env, js_destroy_on_finalizer, &destroy_on_finalizer));
|
||||
}
|
||||
if (resource_type == napi_object && destroy_on_finalizer) {
|
||||
NODE_API_CALL(env, napi_add_finalizer(
|
||||
env, resource, (void*)context, AsyncDestroyCb, NULL, NULL));
|
||||
}
|
||||
NODE_API_CALL(env, napi_wrap(env, async_context_wrap, context, NULL, NULL, NULL));
|
||||
return async_context_wrap;
|
||||
}
|
||||
|
||||
#define DESTROY_ASYNC_RESOURCE_ARGC 1
|
||||
|
||||
static napi_value DestroyAsyncResource(napi_env env, napi_callback_info info) {
|
||||
size_t argc = DESTROY_ASYNC_RESOURCE_ARGC;
|
||||
napi_value args[DESTROY_ASYNC_RESOURCE_ARGC];
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
|
||||
|
||||
napi_value async_context_wrap = args[0];
|
||||
|
||||
napi_async_context async_context;
|
||||
NODE_API_CALL(env,
|
||||
napi_remove_wrap(env, async_context_wrap, (void**)&async_context));
|
||||
NODE_API_CALL(env, napi_async_destroy(env, async_context));
|
||||
|
||||
return async_context_wrap;
|
||||
}
|
||||
|
||||
static
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_value fn;
|
||||
NODE_API_CALL(env, napi_create_function(
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn));
|
||||
NODE_API_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn));
|
||||
NODE_API_CALL(env, napi_create_function(
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
env, NULL, NAPI_AUTO_LENGTH, CreateAsyncResource, NULL, &fn));
|
||||
NODE_API_CALL(env, napi_set_named_property(
|
||||
env, exports, "createAsyncResource", fn));
|
||||
|
||||
NODE_API_CALL(env, napi_create_function(
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
env, NULL, NAPI_AUTO_LENGTH, DestroyAsyncResource, NULL, &fn));
|
||||
NODE_API_CALL(env, napi_set_named_property(
|
||||
env, exports, "destroyAsyncResource", fn));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
// Flags: --gc-interval=100 --gc-global
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const {
|
||||
createAsyncResource,
|
||||
destroyAsyncResource,
|
||||
makeCallback,
|
||||
} = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
// Test for https://github.com/nodejs/node/issues/27218:
|
||||
// napi_async_destroy() can be called during a regular garbage collection run.
|
||||
|
||||
const hook_result = {
|
||||
id: null,
|
||||
init_called: false,
|
||||
destroy_called: false,
|
||||
};
|
||||
|
||||
const test_hook = async_hooks.createHook({
|
||||
init: (id, type) => {
|
||||
if (type === 'test_async') {
|
||||
hook_result.id = id;
|
||||
hook_result.init_called = true;
|
||||
}
|
||||
},
|
||||
destroy: (id) => {
|
||||
if (id === hook_result.id) hook_result.destroy_called = true;
|
||||
},
|
||||
});
|
||||
|
||||
test_hook.enable();
|
||||
const asyncResource = createAsyncResource(
|
||||
{ foo: 'bar' },
|
||||
/* destroy_on_finalizer */false,
|
||||
);
|
||||
|
||||
// Trigger GC. This does *not* use global.gc(), because what we want to verify
|
||||
// is that `napi_async_destroy()` can be called when there is no JS context
|
||||
// on the stack at the time of GC.
|
||||
// Currently, using --gc-interval=100 + 1M elements seems to work fine for this.
|
||||
const arr = new Array(1024 * 1024);
|
||||
for (let i = 0; i < arr.length; i++)
|
||||
arr[i] = {};
|
||||
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
makeCallback(asyncResource, process, () => {
|
||||
const executionAsyncResource = async_hooks.executionAsyncResource();
|
||||
// Assuming the executionAsyncResource was created for the absence of the
|
||||
// initial `{ foo: 'bar' }`.
|
||||
// This is the worst path of `napi_async_context` related API of
|
||||
// recovering from the condition and not break the executionAsyncResource
|
||||
// shape, although the executionAsyncResource might not be correct.
|
||||
assert.strictEqual(typeof executionAsyncResource, 'object');
|
||||
assert.strictEqual(executionAsyncResource.foo, undefined);
|
||||
destroyAsyncResource(asyncResource);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
// Flags: --gc-interval=100 --gc-global
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { createAsyncResource } = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
// Test for https://github.com/nodejs/node/issues/27218:
|
||||
// napi_async_destroy() can be called during a regular garbage collection run.
|
||||
|
||||
const hook_result = {
|
||||
id: null,
|
||||
init_called: false,
|
||||
destroy_called: false,
|
||||
};
|
||||
|
||||
const test_hook = async_hooks.createHook({
|
||||
init: (id, type) => {
|
||||
if (type === 'test_async') {
|
||||
hook_result.id = id;
|
||||
hook_result.init_called = true;
|
||||
}
|
||||
},
|
||||
destroy: (id) => {
|
||||
if (id === hook_result.id) hook_result.destroy_called = true;
|
||||
},
|
||||
});
|
||||
|
||||
test_hook.enable();
|
||||
createAsyncResource({});
|
||||
|
||||
// Trigger GC. This does *not* use global.gc(), because what we want to verify
|
||||
// is that `napi_async_destroy()` can be called when there is no JS context
|
||||
// on the stack at the time of GC.
|
||||
// Currently, using --gc-interval=100 + 1M elements seems to work fine for this.
|
||||
const arr = new Array(1024 * 1024);
|
||||
for (let i = 0; i < arr.length; i++)
|
||||
arr[i] = {};
|
||||
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, true);
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
// Flags: --gc-interval=100 --gc-global
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const {
|
||||
makeCallback,
|
||||
createAsyncResource,
|
||||
destroyAsyncResource,
|
||||
} = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
const hook_result = {
|
||||
id: null,
|
||||
resource: null,
|
||||
init_called: false,
|
||||
destroy_called: false,
|
||||
};
|
||||
|
||||
const test_hook = async_hooks.createHook({
|
||||
init: (id, type, triggerAsyncId, resource) => {
|
||||
if (type === 'test_async') {
|
||||
hook_result.id = id;
|
||||
hook_result.init_called = true;
|
||||
hook_result.resource = resource;
|
||||
}
|
||||
},
|
||||
destroy: (id) => {
|
||||
if (id === hook_result.id) hook_result.destroy_called = true;
|
||||
},
|
||||
});
|
||||
|
||||
test_hook.enable();
|
||||
const resourceWrap = createAsyncResource(
|
||||
/**
|
||||
* set resource to NULL to generate a managed resource object
|
||||
*/
|
||||
undefined,
|
||||
);
|
||||
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
const recv = {};
|
||||
makeCallback(resourceWrap, recv, function callback() {
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
assert.strictEqual(
|
||||
hook_result.resource,
|
||||
async_hooks.executionAsyncResource(),
|
||||
);
|
||||
assert.strictEqual(this, recv);
|
||||
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, false);
|
||||
assert.notStrictEqual(
|
||||
hook_result.resource,
|
||||
async_hooks.executionAsyncResource(),
|
||||
);
|
||||
|
||||
destroyAsyncResource(resourceWrap);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_buffer",
|
||||
"defines": [
|
||||
'NAPI_EXPERIMENTAL'
|
||||
],
|
||||
"sources": [ "test_buffer.c" ]
|
||||
},
|
||||
{
|
||||
"target_name": "test_finalizer",
|
||||
"sources": [ "test_finalizer.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/test_buffer`);
|
||||
const assert = require('assert');
|
||||
|
||||
// Regression test for https://github.com/nodejs/node/issues/31134
|
||||
// Buffers with finalizers are allowed to remain alive until
|
||||
// Environment cleanup without crashing the process.
|
||||
// The test stores the buffer on `process` to make sure it lives as long as
|
||||
// the Context does.
|
||||
|
||||
process.externalBuffer = binding.newExternalBuffer();
|
||||
assert.strictEqual(process.externalBuffer.toString(), binding.theText);
|
||||
34
test/napi/node-napi-tests/test/node-api/test_buffer/test.js
Normal file
34
test/napi/node-napi-tests/test/node-api/test_buffer/test.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc --no-concurrent-array-buffer-sweeping
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/test_buffer`);
|
||||
const assert = require('assert');
|
||||
const tick = require('util').promisify(require('../../common/tick'));
|
||||
|
||||
(async function() {
|
||||
assert.strictEqual(binding.newBuffer().toString(), binding.theText);
|
||||
assert.strictEqual(binding.newExternalBuffer().toString(), binding.theText);
|
||||
console.log('gc1');
|
||||
global.gc();
|
||||
assert.strictEqual(binding.getDeleterCallCount(), 0);
|
||||
await tick(10);
|
||||
assert.strictEqual(binding.getDeleterCallCount(), 1);
|
||||
assert.strictEqual(binding.copyBuffer().toString(), binding.theText);
|
||||
|
||||
let buffer = binding.staticBuffer();
|
||||
assert.strictEqual(binding.bufferHasInstance(buffer), true);
|
||||
assert.strictEqual(binding.bufferInfo(buffer), true);
|
||||
buffer = null;
|
||||
global.gc();
|
||||
assert.strictEqual(binding.getDeleterCallCount(), 1);
|
||||
await tick(10);
|
||||
console.log('gc2');
|
||||
assert.strictEqual(binding.getDeleterCallCount(), 2);
|
||||
|
||||
// To test this doesn't crash
|
||||
binding.invalidObjectAsBuffer({});
|
||||
|
||||
const testBuffer = binding.bufferFromArrayBuffer();
|
||||
assert(testBuffer instanceof Buffer, 'Expected a Buffer');
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,189 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static const char theText[] =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
|
||||
static int deleterCallCount = 0;
|
||||
|
||||
static void deleteTheText(node_api_basic_env env,
|
||||
void* data,
|
||||
void* finalize_hint) {
|
||||
NODE_API_BASIC_ASSERT_RETURN_VOID(data != NULL && strcmp(data, theText) == 0,
|
||||
"invalid data");
|
||||
|
||||
(void)finalize_hint;
|
||||
free(data);
|
||||
deleterCallCount++;
|
||||
}
|
||||
|
||||
static void noopDeleter(node_api_basic_env env,
|
||||
void* data,
|
||||
void* finalize_hint) {
|
||||
NODE_API_BASIC_ASSERT_RETURN_VOID(data != NULL && strcmp(data, theText) == 0,
|
||||
"invalid data");
|
||||
(void)finalize_hint;
|
||||
deleterCallCount++;
|
||||
}
|
||||
|
||||
static napi_value newBuffer(napi_env env, napi_callback_info info) {
|
||||
napi_value theBuffer;
|
||||
char* theCopy;
|
||||
const unsigned int kBufferSize = sizeof(theText);
|
||||
|
||||
NODE_API_CALL(env,
|
||||
napi_create_buffer(
|
||||
env, sizeof(theText), (void**)(&theCopy), &theBuffer));
|
||||
NODE_API_ASSERT(env, theCopy, "Failed to copy static text for newBuffer");
|
||||
memcpy(theCopy, theText, kBufferSize);
|
||||
|
||||
return theBuffer;
|
||||
}
|
||||
|
||||
static napi_value newExternalBuffer(napi_env env, napi_callback_info info) {
|
||||
napi_value theBuffer;
|
||||
char* theCopy = strdup(theText);
|
||||
NODE_API_ASSERT(
|
||||
env, theCopy, "Failed to copy static text for newExternalBuffer");
|
||||
NODE_API_CALL(env,
|
||||
napi_create_external_buffer(env,
|
||||
sizeof(theText),
|
||||
theCopy,
|
||||
deleteTheText,
|
||||
NULL /* finalize_hint */,
|
||||
&theBuffer));
|
||||
|
||||
return theBuffer;
|
||||
}
|
||||
|
||||
static napi_value getDeleterCallCount(napi_env env, napi_callback_info info) {
|
||||
napi_value callCount;
|
||||
NODE_API_CALL(env, napi_create_int32(env, deleterCallCount, &callCount));
|
||||
return callCount;
|
||||
}
|
||||
|
||||
static napi_value copyBuffer(napi_env env, napi_callback_info info) {
|
||||
napi_value theBuffer;
|
||||
NODE_API_CALL(env, napi_create_buffer_copy(
|
||||
env, sizeof(theText), theText, NULL, &theBuffer));
|
||||
return theBuffer;
|
||||
}
|
||||
|
||||
static napi_value bufferHasInstance(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value args[1];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
|
||||
napi_value theBuffer = args[0];
|
||||
bool hasInstance;
|
||||
napi_valuetype theType;
|
||||
NODE_API_CALL(env, napi_typeof(env, theBuffer, &theType));
|
||||
NODE_API_ASSERT(env,
|
||||
theType == napi_object, "bufferHasInstance: instance is not an object");
|
||||
NODE_API_CALL(env, napi_is_buffer(env, theBuffer, &hasInstance));
|
||||
NODE_API_ASSERT(env, hasInstance, "bufferHasInstance: instance is not a buffer");
|
||||
napi_value returnValue;
|
||||
NODE_API_CALL(env, napi_get_boolean(env, hasInstance, &returnValue));
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
static napi_value bufferInfo(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value args[1];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
|
||||
napi_value theBuffer = args[0];
|
||||
char *bufferData;
|
||||
napi_value returnValue;
|
||||
size_t bufferLength;
|
||||
NODE_API_CALL(env,
|
||||
napi_get_buffer_info(
|
||||
env, theBuffer, (void**)(&bufferData), &bufferLength));
|
||||
NODE_API_CALL(env, napi_get_boolean(env,
|
||||
!strcmp(bufferData, theText) && bufferLength == sizeof(theText),
|
||||
&returnValue));
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
static napi_value staticBuffer(napi_env env, napi_callback_info info) {
|
||||
napi_value theBuffer;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_external_buffer(env,
|
||||
sizeof(theText),
|
||||
(void*)theText,
|
||||
noopDeleter,
|
||||
NULL /* finalize_hint */,
|
||||
&theBuffer));
|
||||
return theBuffer;
|
||||
}
|
||||
|
||||
static napi_value invalidObjectAsBuffer(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value args[1];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
|
||||
|
||||
napi_value notTheBuffer = args[0];
|
||||
napi_status status = napi_get_buffer_info(env, notTheBuffer, NULL, NULL);
|
||||
NODE_API_ASSERT(env,
|
||||
status == napi_invalid_arg,
|
||||
"napi_get_buffer_info: should fail with napi_invalid_arg "
|
||||
"when passed non buffer");
|
||||
|
||||
return notTheBuffer;
|
||||
}
|
||||
|
||||
static napi_value bufferFromArrayBuffer(napi_env env, napi_callback_info info) {
|
||||
napi_status status;
|
||||
napi_value arraybuffer;
|
||||
napi_value buffer;
|
||||
size_t byte_length = 1024;
|
||||
void* data = NULL;
|
||||
size_t buffer_length = 0;
|
||||
void* buffer_data = NULL;
|
||||
|
||||
status = napi_create_arraybuffer(env, byte_length, &data, &arraybuffer);
|
||||
NODE_API_ASSERT(env, status == napi_ok, "Failed to create arraybuffer");
|
||||
|
||||
status = node_api_create_buffer_from_arraybuffer(
|
||||
env, arraybuffer, 0, byte_length, &buffer);
|
||||
NODE_API_ASSERT(
|
||||
env, status == napi_ok, "Failed to create buffer from arraybuffer");
|
||||
|
||||
status = napi_get_buffer_info(env, buffer, &buffer_data, &buffer_length);
|
||||
NODE_API_ASSERT(env, status == napi_ok, "Failed to get buffer info");
|
||||
|
||||
NODE_API_ASSERT(env, buffer_length == byte_length, "Buffer length mismatch");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_value theValue;
|
||||
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(env, theText, sizeof(theText), &theValue));
|
||||
NODE_API_CALL(env,
|
||||
napi_set_named_property(env, exports, "theText", theValue));
|
||||
|
||||
napi_property_descriptor methods[] = {
|
||||
DECLARE_NODE_API_PROPERTY("newBuffer", newBuffer),
|
||||
DECLARE_NODE_API_PROPERTY("newExternalBuffer", newExternalBuffer),
|
||||
DECLARE_NODE_API_PROPERTY("getDeleterCallCount", getDeleterCallCount),
|
||||
DECLARE_NODE_API_PROPERTY("copyBuffer", copyBuffer),
|
||||
DECLARE_NODE_API_PROPERTY("bufferHasInstance", bufferHasInstance),
|
||||
DECLARE_NODE_API_PROPERTY("bufferInfo", bufferInfo),
|
||||
DECLARE_NODE_API_PROPERTY("staticBuffer", staticBuffer),
|
||||
DECLARE_NODE_API_PROPERTY("invalidObjectAsBuffer", invalidObjectAsBuffer),
|
||||
DECLARE_NODE_API_PROPERTY("bufferFromArrayBuffer", bufferFromArrayBuffer),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(methods) / sizeof(methods[0]), methods));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <node_api.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static const char theText[] =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
|
||||
static void malignDeleter(napi_env env, void* data, void* finalize_hint) {
|
||||
NODE_API_ASSERT_RETURN_VOID(
|
||||
env, data != NULL && strcmp(data, theText) == 0, "invalid data");
|
||||
napi_ref finalizer_ref = (napi_ref)finalize_hint;
|
||||
napi_value js_finalizer;
|
||||
napi_value recv;
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_get_reference_value(env, finalizer_ref, &js_finalizer));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &recv));
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_call_function(env, recv, js_finalizer, 0, NULL, NULL));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, finalizer_ref));
|
||||
}
|
||||
|
||||
static napi_value malignFinalizerBuffer(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value args[1];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
NODE_API_ASSERT(env, argc == 1, "Wrong number of arguments");
|
||||
napi_value finalizer = args[0];
|
||||
napi_valuetype finalizer_valuetype;
|
||||
NODE_API_CALL(env, napi_typeof(env, finalizer, &finalizer_valuetype));
|
||||
NODE_API_ASSERT(env,
|
||||
finalizer_valuetype == napi_function,
|
||||
"Wrong type of first argument");
|
||||
napi_ref finalizer_ref;
|
||||
NODE_API_CALL(env, napi_create_reference(env, finalizer, 1, &finalizer_ref));
|
||||
|
||||
napi_value theBuffer;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_external_buffer(env,
|
||||
sizeof(theText),
|
||||
(void*)theText,
|
||||
malignDeleter,
|
||||
finalizer_ref, // finalize_hint
|
||||
&theBuffer));
|
||||
return theBuffer;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor methods[] = {
|
||||
DECLARE_NODE_API_PROPERTY("malignFinalizerBuffer", malignFinalizerBuffer),
|
||||
};
|
||||
|
||||
NODE_API_CALL(
|
||||
env,
|
||||
napi_define_properties(
|
||||
env, exports, sizeof(methods) / sizeof(methods[0]), methods));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc --force-node-api-uncaught-exceptions-policy
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/test_finalizer`);
|
||||
const assert = require('assert');
|
||||
const tick = require('util').promisify(require('../../common/tick'));
|
||||
|
||||
process.on('uncaughtException', common.mustCall((err) => {
|
||||
assert.throws(() => { throw err; }, /finalizer error/);
|
||||
}));
|
||||
|
||||
(async function() {
|
||||
{
|
||||
binding.malignFinalizerBuffer(common.mustCall(() => {
|
||||
throw new Error('finalizer error');
|
||||
}));
|
||||
}
|
||||
global.gc({ type: 'minor' });
|
||||
await tick(common.platformTimeout(100));
|
||||
global.gc();
|
||||
await tick(common.platformTimeout(100));
|
||||
global.gc();
|
||||
await tick(common.platformTimeout(100));
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,119 @@
|
||||
#include <stdlib.h>
|
||||
#include "node_api.h"
|
||||
#include "uv.h"
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static napi_value RunInCallbackScope(napi_env env, napi_callback_info info) {
|
||||
size_t argc;
|
||||
napi_value args[3];
|
||||
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, NULL, NULL, NULL));
|
||||
NODE_API_ASSERT(env, argc == 3 , "Wrong number of arguments");
|
||||
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
|
||||
napi_valuetype valuetype;
|
||||
NODE_API_CALL(env, napi_typeof(env, args[0], &valuetype));
|
||||
NODE_API_ASSERT(env, valuetype == napi_object,
|
||||
"Wrong type of arguments. Expects an object as first argument.");
|
||||
|
||||
NODE_API_CALL(env, napi_typeof(env, args[1], &valuetype));
|
||||
NODE_API_ASSERT(env, valuetype == napi_string,
|
||||
"Wrong type of arguments. Expects a string as second argument.");
|
||||
|
||||
NODE_API_CALL(env, napi_typeof(env, args[2], &valuetype));
|
||||
NODE_API_ASSERT(env, valuetype == napi_function,
|
||||
"Wrong type of arguments. Expects a function as third argument.");
|
||||
|
||||
napi_async_context context;
|
||||
NODE_API_CALL(env, napi_async_init(env, args[0], args[1], &context));
|
||||
|
||||
napi_callback_scope scope = NULL;
|
||||
NODE_API_CALL(env,
|
||||
napi_open_callback_scope(env, args[0], context, &scope));
|
||||
|
||||
// If the function has an exception pending after the call that is ok
|
||||
// so we don't use NODE_API_CALL as we must close the callback scope
|
||||
// regardless.
|
||||
napi_value result = NULL;
|
||||
napi_status function_call_result =
|
||||
napi_call_function(env, args[0], args[2], 0, NULL, &result);
|
||||
if (function_call_result != napi_ok) {
|
||||
GET_AND_THROW_LAST_ERROR((env));
|
||||
}
|
||||
|
||||
NODE_API_CALL(env, napi_close_callback_scope(env, scope));
|
||||
NODE_API_CALL(env, napi_async_destroy(env, context));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static napi_env shared_env = NULL;
|
||||
static napi_deferred deferred = NULL;
|
||||
|
||||
static void Callback(uv_work_t* req, int ignored) {
|
||||
napi_env env = shared_env;
|
||||
|
||||
napi_handle_scope handle_scope = NULL;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_open_handle_scope(env, &handle_scope));
|
||||
|
||||
napi_value resource_name;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_string_utf8(
|
||||
env, "test", NAPI_AUTO_LENGTH, &resource_name));
|
||||
napi_async_context context;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_async_init(env, NULL, resource_name, &context));
|
||||
|
||||
napi_value resource_object;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &resource_object));
|
||||
|
||||
napi_value undefined_value;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined_value));
|
||||
|
||||
napi_callback_scope scope = NULL;
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_open_callback_scope(env, resource_object, context, &scope));
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_resolve_deferred(env, deferred, undefined_value));
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_close_callback_scope(env, scope));
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_close_handle_scope(env, handle_scope));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_async_destroy(env, context));
|
||||
free(req);
|
||||
}
|
||||
|
||||
static void NoopWork(uv_work_t* work) { (void) work; }
|
||||
|
||||
static napi_value TestResolveAsync(napi_env env, napi_callback_info info) {
|
||||
napi_value promise = NULL;
|
||||
if (deferred == NULL) {
|
||||
shared_env = env;
|
||||
NODE_API_CALL(env, napi_create_promise(env, &deferred, &promise));
|
||||
|
||||
uv_loop_t* loop = NULL;
|
||||
NODE_API_CALL(env, napi_get_uv_event_loop(env, &loop));
|
||||
|
||||
uv_work_t* req = malloc(sizeof(*req));
|
||||
uv_queue_work(loop,
|
||||
req,
|
||||
NoopWork,
|
||||
Callback);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor descriptors[] = {
|
||||
DECLARE_NODE_API_PROPERTY("runInCallbackScope", RunInCallbackScope),
|
||||
DECLARE_NODE_API_PROPERTY("testResolveAsync", TestResolveAsync)
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
|
||||
// The async_hook that we enable would register the process.emitWarning()
|
||||
// call from loading the N-API addon as asynchronous activity because
|
||||
// it contains a process.nextTick() call. Monkey patch it to be a no-op
|
||||
// before we load the addon in order to avoid this.
|
||||
process.emitWarning = () => {};
|
||||
|
||||
const { runInCallbackScope } = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
const expectedResource = {};
|
||||
const expectedResourceType = 'test-resource';
|
||||
let insideHook = false;
|
||||
let expectedId;
|
||||
async_hooks.createHook({
|
||||
init: common.mustCall((id, type, triggerAsyncId, resource) => {
|
||||
if (type !== expectedResourceType) {
|
||||
return;
|
||||
}
|
||||
assert.strictEqual(resource, expectedResource);
|
||||
expectedId = id;
|
||||
}),
|
||||
before: common.mustCall((id) => {
|
||||
assert.strictEqual(id, expectedId);
|
||||
insideHook = true;
|
||||
}),
|
||||
after: common.mustCall((id) => {
|
||||
assert.strictEqual(id, expectedId);
|
||||
insideHook = false;
|
||||
}),
|
||||
}).enable();
|
||||
|
||||
runInCallbackScope(expectedResource, expectedResourceType, () => {
|
||||
assert(insideHook);
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const { testResolveAsync } = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
testResolveAsync().then(common.mustCall());
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const { runInCallbackScope } = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
assert.strictEqual(runInCallbackScope({}, 'test-resource', () => 42), 42);
|
||||
|
||||
{
|
||||
process.once('uncaughtException', common.mustCall((err) => {
|
||||
assert.strictEqual(err.message, 'foo');
|
||||
}));
|
||||
|
||||
runInCallbackScope({}, 'test-resource', () => {
|
||||
throw new Error('foo');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
#include "node_api.h"
|
||||
#include "uv.h"
|
||||
|
||||
static int cleanup_hook_count = 0;
|
||||
static void cleanup(void* arg) {
|
||||
cleanup_hook_count++;
|
||||
printf("cleanup(%d)\n", *(int*)(arg));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static int secret = 42;
|
||||
static int wrong_secret = 17;
|
||||
|
||||
static void ObjectFinalizer(napi_env env, void* data, void* hint) {
|
||||
// cleanup is called once.
|
||||
assert(cleanup_hook_count == 1);
|
||||
|
||||
napi_ref* ref = data;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, *ref));
|
||||
free(ref);
|
||||
}
|
||||
|
||||
static void CreateObjectWrap(napi_env env) {
|
||||
napi_value js_obj;
|
||||
napi_ref* ref = malloc(sizeof(*ref));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &js_obj));
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_wrap(env, js_obj, ref, ObjectFinalizer, NULL, ref));
|
||||
// create a strong reference so that the finalizer is called at shutdown.
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_reference_ref(env, *ref, NULL));
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
// Create object wrap before cleanup hooks.
|
||||
CreateObjectWrap(env);
|
||||
|
||||
napi_add_env_cleanup_hook(env, cleanup, &wrong_secret);
|
||||
napi_add_env_cleanup_hook(env, cleanup, &secret);
|
||||
napi_remove_env_cleanup_hook(env, cleanup, &wrong_secret);
|
||||
|
||||
// Create object wrap after cleanup hooks.
|
||||
CreateObjectWrap(env);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
require(`./build/${common.buildType}/binding`);
|
||||
} else {
|
||||
const { stdout, status, signal } =
|
||||
child_process.spawnSync(process.execPath, [__filename, 'child']);
|
||||
assert.strictEqual(status, 0, `process exited with status(${status}) and signal(${signal})`);
|
||||
assert.strictEqual(stdout.toString().trim(), 'cleanup(42)');
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <stdlib.h>
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static void MyObject_fini(napi_env env, void* data, void* hint) {
|
||||
napi_ref* ref = data;
|
||||
napi_value global;
|
||||
napi_value cleanup;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &global));
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_get_named_property(env, global, "cleanup", &cleanup));
|
||||
napi_status status = napi_call_function(env, global, cleanup, 0, NULL, NULL);
|
||||
// We may not be allowed to call into JS, in which case a pending exception
|
||||
// will be returned.
|
||||
NODE_API_ASSERT_RETURN_VOID(env,
|
||||
status == napi_ok || status == napi_pending_exception,
|
||||
"Unexpected status for napi_call_function");
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, *ref));
|
||||
free(ref);
|
||||
}
|
||||
|
||||
static napi_value MyObject(napi_env env, napi_callback_info info) {
|
||||
napi_value js_this;
|
||||
napi_ref* ref = malloc(sizeof(*ref));
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, NULL, NULL, &js_this, NULL));
|
||||
NODE_API_CALL(env, napi_wrap(env, js_this, ref, MyObject_fini, NULL, ref));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NAPI_MODULE_INIT() {
|
||||
napi_value ctor;
|
||||
NODE_API_CALL(
|
||||
env, napi_define_class(
|
||||
env, "MyObject", NAPI_AUTO_LENGTH, MyObject, NULL, 0, NULL, &ctor));
|
||||
NODE_API_CALL(env, napi_set_named_property(env, exports, "MyObject", ctor));
|
||||
return exports;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
|
||||
process.env.NODE_TEST_KNOWN_GLOBALS = 0;
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
global.it = new binding.MyObject();
|
||||
|
||||
global.cleanup = () => {
|
||||
delete global.it;
|
||||
global.gc();
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_exception",
|
||||
"sources": [ "test_exception.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const test_exception = require(`./build/${common.buildType}/test_exception`);
|
||||
|
||||
// Make sure that exceptions that occur during finalization are propagated.
|
||||
function testFinalize(binding) {
|
||||
let x = test_exception[binding]();
|
||||
x = null;
|
||||
global.gc();
|
||||
process.on('uncaughtException', (err) => {
|
||||
assert.strictEqual(err.message, 'Error during Finalize');
|
||||
});
|
||||
|
||||
// To assuage the linter's concerns.
|
||||
(function() {})(x);
|
||||
}
|
||||
testFinalize('createExternalBuffer');
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static void finalizer(napi_env env, void *data, void *hint) {
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_throw_error(env, NULL, "Error during Finalize"));
|
||||
}
|
||||
|
||||
static char buffer_data[12];
|
||||
|
||||
static napi_value createExternalBuffer(napi_env env, napi_callback_info info) {
|
||||
napi_value buffer;
|
||||
NODE_API_CALL(env, napi_create_external_buffer(env, sizeof(buffer_data),
|
||||
buffer_data, finalizer, NULL, &buffer));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor descriptors[] = {
|
||||
DECLARE_NODE_API_PROPERTY("createExternalBuffer", createExternalBuffer),
|
||||
};
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_fatal",
|
||||
"sources": [ "test_fatal.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
19
test/napi/node-napi-tests/test/node-api/test_fatal/test.js
Normal file
19
test/napi/node-napi-tests/test/node-api/test_fatal/test.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const test_fatal = require(`./build/${common.buildType}/test_fatal`);
|
||||
|
||||
// Test in a child process because the test code will trigger a fatal error
|
||||
// that crashes the process.
|
||||
if (process.argv[2] === 'child') {
|
||||
test_fatal.Test();
|
||||
return;
|
||||
}
|
||||
|
||||
const p = child_process.spawnSync(
|
||||
process.execPath, [ __filename, 'child' ]);
|
||||
assert.ifError(p.error);
|
||||
assert.ok(p.stderr.toString().includes(
|
||||
'FATAL ERROR: test_fatal::Test fatal message'));
|
||||
assert.ok(p.status === 134 || p.signal === 'SIGABRT');
|
||||
19
test/napi/node-napi-tests/test/node-api/test_fatal/test2.js
Normal file
19
test/napi/node-napi-tests/test/node-api/test_fatal/test2.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const test_fatal = require(`./build/${common.buildType}/test_fatal`);
|
||||
|
||||
// Test in a child process because the test code will trigger a fatal error
|
||||
// that crashes the process.
|
||||
if (process.argv[2] === 'child') {
|
||||
test_fatal.TestStringLength();
|
||||
return;
|
||||
}
|
||||
|
||||
const p = child_process.spawnSync(
|
||||
process.execPath, [ '--napi-modules', __filename, 'child' ]);
|
||||
assert.ifError(p.error);
|
||||
assert.ok(p.stderr.toString().includes(
|
||||
'FATAL ERROR: test_fatal::Test fatal message'));
|
||||
assert.ok(p.status === 134 || p.signal === 'SIGABRT');
|
||||
@@ -0,0 +1,48 @@
|
||||
// For the purpose of this test we use libuv's threading library. When deciding
|
||||
// on a threading library for a new project it bears remembering that in the
|
||||
// future libuv may introduce API changes which may render it non-ABI-stable,
|
||||
// which, in turn, may affect the ABI stability of the project despite its use
|
||||
// of N-API.
|
||||
#include <uv.h>
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static uv_thread_t uv_thread;
|
||||
|
||||
static void work_thread(void* data) {
|
||||
napi_fatal_error("work_thread", NAPI_AUTO_LENGTH,
|
||||
"foobar", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
|
||||
static napi_value Test(napi_env env, napi_callback_info info) {
|
||||
napi_fatal_error("test_fatal::Test", NAPI_AUTO_LENGTH,
|
||||
"fatal message", NAPI_AUTO_LENGTH);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value TestThread(napi_env env, napi_callback_info info) {
|
||||
NODE_API_ASSERT(env,
|
||||
(uv_thread_create(&uv_thread, work_thread, NULL) == 0),
|
||||
"Thread creation");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value TestStringLength(napi_env env, napi_callback_info info) {
|
||||
napi_fatal_error("test_fatal::TestStringLength", 16, "fatal message", 13);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("Test", Test),
|
||||
DECLARE_NODE_API_PROPERTY("TestStringLength", TestStringLength),
|
||||
DECLARE_NODE_API_PROPERTY("TestThread", TestThread),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const test_fatal = require(`./build/${common.buildType}/test_fatal`);
|
||||
|
||||
// Test in a child process because the test code will trigger a fatal error
|
||||
// that crashes the process.
|
||||
if (process.argv[2] === 'child') {
|
||||
test_fatal.TestThread();
|
||||
while (true) {
|
||||
// Busy loop to allow the work thread to abort.
|
||||
}
|
||||
}
|
||||
|
||||
const p = child_process.spawnSync(
|
||||
process.execPath, [ __filename, 'child' ]);
|
||||
assert.ifError(p.error);
|
||||
assert.ok(p.stderr.toString().includes(
|
||||
'FATAL ERROR: work_thread foobar'));
|
||||
assert(common.nodeProcessAborted(p.status, p.signal));
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const helper = require('../../common/report.js');
|
||||
const tmpdir = require('../../common/tmpdir');
|
||||
|
||||
const assert = require('assert');
|
||||
const child_process = require('child_process');
|
||||
const test_fatal = require(`./build/${common.buildType}/test_fatal`);
|
||||
|
||||
if (common.buildType === 'Debug')
|
||||
common.skip('as this will currently fail with a Debug check ' +
|
||||
'in v8::Isolate::GetCurrent()');
|
||||
|
||||
// Test in a child process because the test code will trigger a fatal error
|
||||
// that crashes the process.
|
||||
if (process.argv[2] === 'child') {
|
||||
test_fatal.TestThread();
|
||||
// Busy loop to allow the work thread to abort.
|
||||
while (true);
|
||||
}
|
||||
|
||||
tmpdir.refresh();
|
||||
const p = child_process.spawnSync(
|
||||
process.execPath,
|
||||
[ '--report-on-fatalerror', __filename, 'child' ],
|
||||
{ cwd: tmpdir.path });
|
||||
assert.ifError(p.error);
|
||||
assert.ok(p.stderr.toString().includes(
|
||||
'FATAL ERROR: work_thread foobar'));
|
||||
assert.ok(p.status === 134 || p.signal === 'SIGABRT');
|
||||
|
||||
const reports = helper.findReports(p.pid, tmpdir.path);
|
||||
assert.strictEqual(reports.length, 1);
|
||||
|
||||
const report = reports[0];
|
||||
helper.validate(report);
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_fatal_exception",
|
||||
"sources": [ "test_fatal_exception.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const test_fatal = require(`./build/${common.buildType}/test_fatal_exception`);
|
||||
|
||||
process.on('uncaughtException', common.mustCall(function(err) {
|
||||
assert.strictEqual(err.message, 'fatal error');
|
||||
}));
|
||||
|
||||
const err = new Error('fatal error');
|
||||
test_fatal.Test(err);
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static napi_value Test(napi_env env, napi_callback_info info) {
|
||||
napi_value err;
|
||||
size_t argc = 1;
|
||||
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &err, NULL, NULL));
|
||||
|
||||
NODE_API_CALL(env, napi_fatal_exception(env, err));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("Test", Test),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_general",
|
||||
"sources": [ "test_general.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
46
test/napi/node-napi-tests/test/node-api/test_general/test.js
Normal file
46
test/napi/node-napi-tests/test/node-api/test_general/test.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const tmpdir = require('../../common/tmpdir');
|
||||
const child_process = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const filename = require.resolve(`./build/${common.buildType}/test_general`);
|
||||
const test_general = require(filename);
|
||||
const assert = require('assert');
|
||||
|
||||
tmpdir.refresh();
|
||||
|
||||
{
|
||||
// TODO(gabrielschulhof): This test may need updating if/when the filename
|
||||
// becomes a full-fledged URL.
|
||||
assert.strictEqual(test_general.filename, url.pathToFileURL(filename).href);
|
||||
}
|
||||
|
||||
{
|
||||
const urlTestDir = tmpdir.resolve('foo%#bar');
|
||||
const urlTestFile = path.join(urlTestDir, path.basename(filename));
|
||||
fs.mkdirSync(urlTestDir, { recursive: true });
|
||||
fs.copyFileSync(filename, urlTestFile);
|
||||
// Use a child process as indirection so that the built-in modules is not loaded
|
||||
// into this process and can be removed here.
|
||||
const reportedFilename = child_process.spawnSync(
|
||||
process.execPath,
|
||||
['-p', `require(${JSON.stringify(urlTestFile)}).filename`],
|
||||
{ encoding: 'utf8' }).stdout.trim();
|
||||
assert.doesNotMatch(reportedFilename, /foo%#bar/);
|
||||
assert.strictEqual(reportedFilename, url.pathToFileURL(urlTestFile).href);
|
||||
fs.rmSync(urlTestDir, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
maxRetries: 256,
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const [ major, minor, patch, release ] = test_general.testGetNodeVersion();
|
||||
assert.strictEqual(process.version.split('-')[0],
|
||||
`v${major}.${minor}.${patch}`);
|
||||
assert.strictEqual(release, process.release.name);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#define NAPI_VERSION 9
|
||||
// we define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED here to validate that it can
|
||||
// be used as a form of test itself. It is
|
||||
// not related to any of the other tests
|
||||
// defined in the file
|
||||
#define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
|
||||
#include <node_api.h>
|
||||
#include <stdlib.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static napi_value testGetNodeVersion(napi_env env, napi_callback_info info) {
|
||||
const napi_node_version* node_version;
|
||||
napi_value result, major, minor, patch, release;
|
||||
NODE_API_CALL(env, napi_get_node_version(env, &node_version));
|
||||
NODE_API_CALL(env, napi_create_uint32(env, node_version->major, &major));
|
||||
NODE_API_CALL(env, napi_create_uint32(env, node_version->minor, &minor));
|
||||
NODE_API_CALL(env, napi_create_uint32(env, node_version->patch, &patch));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(
|
||||
env, node_version->release, NAPI_AUTO_LENGTH, &release));
|
||||
NODE_API_CALL(env, napi_create_array_with_length(env, 4, &result));
|
||||
NODE_API_CALL(env, napi_set_element(env, result, 0, major));
|
||||
NODE_API_CALL(env, napi_set_element(env, result, 1, minor));
|
||||
NODE_API_CALL(env, napi_set_element(env, result, 2, patch));
|
||||
NODE_API_CALL(env, napi_set_element(env, result, 3, release));
|
||||
return result;
|
||||
}
|
||||
|
||||
static napi_value GetFilename(napi_env env, napi_callback_info info) {
|
||||
const char* filename;
|
||||
napi_value result;
|
||||
|
||||
NODE_API_CALL(env, node_api_get_module_file_name(env, &filename));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(env, filename, NAPI_AUTO_LENGTH, &result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor descriptors[] = {
|
||||
DECLARE_NODE_API_PROPERTY("testGetNodeVersion", testGetNodeVersion),
|
||||
DECLARE_NODE_API_GETTER("filename", GetFilename),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_init_order",
|
||||
"sources": [ "test_init_order.cc" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
// This test verifies that C++ static variable dynamic initialization is called
|
||||
// correctly and does not interfere with the module initialization.
|
||||
const common = require('../../common');
|
||||
const test_init_order = require(`./build/${common.buildType}/test_init_order`);
|
||||
const assert = require('assert');
|
||||
|
||||
assert.strictEqual(test_init_order.cppIntValue, 42);
|
||||
assert.strictEqual(test_init_order.cppStringValue, '123');
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <node_api.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
// This test verifies that use of the NAPI_MODULE in C++ code does not
|
||||
// interfere with the C++ dynamic static initializers.
|
||||
|
||||
namespace {
|
||||
|
||||
// This class uses dynamic static initializers for the test.
|
||||
// In production code developers must avoid dynamic static initializers because
|
||||
// they affect the start up time. They must prefer static initialization such as
|
||||
// use of constexpr functions or classes with constexpr constructors. E.g.
|
||||
// instead of using std::string, it is preferable to use const char[], or
|
||||
// constexpr std::string_view starting with C++17, or even constexpr
|
||||
// std::string starting with C++20.
|
||||
struct MyClass {
|
||||
static const std::unique_ptr<int> valueHolder;
|
||||
static const std::string testString;
|
||||
};
|
||||
|
||||
const std::unique_ptr<int> MyClass::valueHolder =
|
||||
std::unique_ptr<int>(new int(42));
|
||||
// NOLINTNEXTLINE(runtime/string)
|
||||
const std::string MyClass::testString = std::string("123");
|
||||
|
||||
} // namespace
|
||||
|
||||
EXTERN_C_START
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_value cppIntValue, cppStringValue;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_int32(env, *MyClass::valueHolder, &cppIntValue));
|
||||
NODE_API_CALL(
|
||||
env,
|
||||
napi_create_string_utf8(
|
||||
env, MyClass::testString.c_str(), NAPI_AUTO_LENGTH, &cppStringValue));
|
||||
|
||||
napi_property_descriptor descriptors[] = {
|
||||
DECLARE_NODE_API_PROPERTY_VALUE("cppIntValue", cppIntValue),
|
||||
DECLARE_NODE_API_PROPERTY_VALUE("cppStringValue", cppStringValue)};
|
||||
|
||||
NODE_API_CALL(env,
|
||||
napi_define_properties(
|
||||
env, exports, std::size(descriptors), descriptors));
|
||||
|
||||
return exports;
|
||||
}
|
||||
EXTERN_C_END
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <node_api.h>
|
||||
|
||||
static void addon_free(napi_env env, void* data, void* hint) {
|
||||
napi_ref* ref = data;
|
||||
napi_delete_reference(env, *ref);
|
||||
free(ref);
|
||||
fprintf(stderr, "addon_free");
|
||||
}
|
||||
|
||||
napi_value addon_new(napi_env env, napi_value exports, bool ref_first) {
|
||||
napi_ref* ref = malloc(sizeof(*ref));
|
||||
if (ref_first) {
|
||||
napi_create_reference(env, exports, 1, ref);
|
||||
napi_set_instance_data(env, ref, addon_free, NULL);
|
||||
} else {
|
||||
napi_set_instance_data(env, ref, addon_free, NULL);
|
||||
napi_create_reference(env, exports, 1, ref);
|
||||
}
|
||||
return exports;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_instance_data",
|
||||
"sources": [
|
||||
"test_instance_data.c"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target_name": "test_set_then_ref",
|
||||
"sources": [
|
||||
"addon.c",
|
||||
"test_set_then_ref.c",
|
||||
]
|
||||
},
|
||||
{
|
||||
"target_name": "test_ref_then_set",
|
||||
"sources": [
|
||||
"addon.c",
|
||||
"test_ref_then_set.c",
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
// Test API calls for instance data.
|
||||
|
||||
const common = require('../../common');
|
||||
|
||||
if (module !== require.main) {
|
||||
// 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 assert = require('assert');
|
||||
const requireAs = require('../../common/require-as');
|
||||
const runOptions = { stdio: ['inherit', 'pipe', 'inherit'] };
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
// 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');
|
||||
|
||||
function testProcessExit(addonName) {
|
||||
// Make sure that process exit is clean when the instance data has
|
||||
// references to JS objects.
|
||||
const path = require.resolve(`./build/${common.buildType}/${addonName}`);
|
||||
const child = spawnSync(process.execPath, ['-e', `require(${JSON.stringify(path)});`]);
|
||||
assert.strictEqual(child.signal, null);
|
||||
assert.strictEqual(child.status, 0);
|
||||
assert.strictEqual(child.stderr.toString(), 'addon_free');
|
||||
}
|
||||
|
||||
testProcessExit('test_ref_then_set');
|
||||
testProcessExit('test_set_then_ref');
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
#include <stdlib.h>
|
||||
#include <uv.h>
|
||||
#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;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
|
||||
optional_ref = &data->js_cb_ref;
|
||||
}
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_get_reference_value(env, *optional_ref, &js_cb));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
|
||||
NODE_API_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;
|
||||
|
||||
NODE_API_CALL_BASE(env, napi_get_instance_data(env, (void**)&data), false);
|
||||
NODE_API_ASSERT_BASE(
|
||||
env, data->js_cb_ref == NULL, "reference must be NULL", false);
|
||||
NODE_API_CALL_BASE(
|
||||
env, napi_get_cb_info(env, info, &argc, &js_cb, NULL, NULL), false);
|
||||
NODE_API_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;
|
||||
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(
|
||||
env, "AsyncIncrement", NAPI_AUTO_LENGTH, &resource_name));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_async_work(
|
||||
env, NULL, resource_name, AsyncWorkCbExecute, AsyncWorkCbComplete,
|
||||
NULL, &work));
|
||||
NODE_API_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)) {
|
||||
NODE_API_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;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
|
||||
NODE_API_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;
|
||||
}
|
||||
|
||||
// This 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;
|
||||
|
||||
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_ASSERT(env, data->js_cb_ref == NULL, "reference must be NULL");
|
||||
NODE_API_ASSERT(
|
||||
env, data->js_tsfn_finalizer_ref == NULL,
|
||||
"tsfn finalizer reference must be NULL");
|
||||
NODE_API_CALL(env, napi_create_reference(env, argv[0], 1, &data->js_cb_ref));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_reference(env, argv[1], 1, &data->js_tsfn_finalizer_ref));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(
|
||||
env, "TSFN instance data test", NAPI_AUTO_LENGTH, &resource_name));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_threadsafe_function(
|
||||
env, NULL, NULL, resource_name, 0, 1, NULL,
|
||||
FinalizeThreadsafeFunction, NULL, ThreadsafeFunctionCallJS,
|
||||
&data->tsfn));
|
||||
NODE_API_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) {
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_cb_ref));
|
||||
}
|
||||
if (data->js_tsfn_finalizer_ref) {
|
||||
NODE_API_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;
|
||||
|
||||
NODE_API_CALL(env, napi_set_instance_data(env, data, DeleteAddonData, NULL));
|
||||
|
||||
napi_property_descriptor props[] = {
|
||||
DECLARE_NODE_API_PROPERTY("asyncWorkCallback", AsyncWorkCallback),
|
||||
DECLARE_NODE_API_PROPERTY("testBufferFinalizer", TestBufferFinalizer),
|
||||
DECLARE_NODE_API_PROPERTY("testThreadsafeFunction", TestThreadsafeFunction),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env,
|
||||
napi_define_properties(
|
||||
env, exports, sizeof(props) / sizeof(*props), props));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <node_api.h>
|
||||
|
||||
napi_value addon_new(napi_env env, napi_value exports, bool ref_first);
|
||||
|
||||
// static napi_value
|
||||
NAPI_MODULE_INIT(/*napi_env env, napi_value exports */) {
|
||||
return addon_new(env, exports, true);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <node_api.h>
|
||||
|
||||
napi_value addon_new(napi_env env, napi_value exports, bool ref_first);
|
||||
|
||||
// static napi_value
|
||||
NAPI_MODULE_INIT(/*napi_env env, napi_value exports */) {
|
||||
return addon_new(env, exports, false);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <node_api.h>
|
||||
#include <assert.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
#define MAX_ARGUMENTS 10
|
||||
#define RESERVED_ARGS 3
|
||||
|
||||
static napi_value MakeCallback(napi_env env, napi_callback_info info) {
|
||||
size_t argc = MAX_ARGUMENTS;
|
||||
size_t n;
|
||||
napi_value args[MAX_ARGUMENTS];
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
|
||||
NODE_API_ASSERT(env, argc > 0, "Wrong number of arguments");
|
||||
|
||||
napi_value resource = args[0];
|
||||
napi_value recv = args[1];
|
||||
napi_value func = args[2];
|
||||
|
||||
napi_value argv[MAX_ARGUMENTS - RESERVED_ARGS];
|
||||
for (n = RESERVED_ARGS; n < argc; n += 1) {
|
||||
argv[n - RESERVED_ARGS] = args[n];
|
||||
}
|
||||
|
||||
napi_valuetype func_type;
|
||||
|
||||
NODE_API_CALL(env, napi_typeof(env, func, &func_type));
|
||||
|
||||
napi_value resource_name;
|
||||
NODE_API_CALL(env, napi_create_string_utf8(
|
||||
env, "test", NAPI_AUTO_LENGTH, &resource_name));
|
||||
|
||||
napi_async_context context;
|
||||
NODE_API_CALL(env, napi_async_init(env, resource, resource_name, &context));
|
||||
|
||||
napi_value result;
|
||||
if (func_type == napi_function) {
|
||||
NODE_API_CALL(env, napi_make_callback(
|
||||
env, context, recv, func, argc - RESERVED_ARGS, argv, &result));
|
||||
} else {
|
||||
NODE_API_ASSERT(env, false, "Unexpected argument type");
|
||||
}
|
||||
|
||||
NODE_API_CALL(env, napi_async_destroy(env, context));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_value fn;
|
||||
NODE_API_CALL(env, napi_create_function(
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn));
|
||||
NODE_API_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn));
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const binding = require(`./build/${common.buildType}/binding`);
|
||||
const makeCallback = binding.makeCallback;
|
||||
|
||||
// Check async hooks integration using async context.
|
||||
const hook_result = {
|
||||
id: null,
|
||||
init_called: false,
|
||||
before_called: false,
|
||||
after_called: false,
|
||||
destroy_called: false,
|
||||
};
|
||||
const test_hook = async_hooks.createHook({
|
||||
init: (id, type) => {
|
||||
if (type === 'test') {
|
||||
hook_result.id = id;
|
||||
hook_result.init_called = true;
|
||||
}
|
||||
},
|
||||
before: (id) => {
|
||||
if (id === hook_result.id) hook_result.before_called = true;
|
||||
},
|
||||
after: (id) => {
|
||||
if (id === hook_result.id) hook_result.after_called = true;
|
||||
},
|
||||
destroy: (id) => {
|
||||
if (id === hook_result.id) hook_result.destroy_called = true;
|
||||
},
|
||||
});
|
||||
|
||||
test_hook.enable();
|
||||
|
||||
/**
|
||||
* Resource should be able to be arbitrary objects without special internal
|
||||
* slots. Testing with plain object here.
|
||||
*/
|
||||
const resource = {};
|
||||
makeCallback(resource, process, function cb() {
|
||||
assert.strictEqual(this, process);
|
||||
assert.strictEqual(async_hooks.executionAsyncResource(), resource);
|
||||
});
|
||||
|
||||
assert.strictEqual(hook_result.init_called, true);
|
||||
assert.strictEqual(hook_result.before_called, true);
|
||||
assert.strictEqual(hook_result.after_called, true);
|
||||
setImmediate(() => {
|
||||
assert.strictEqual(hook_result.destroy_called, true);
|
||||
test_hook.disable();
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const vm = require('vm');
|
||||
const binding = require(`./build/${common.buildType}/binding`);
|
||||
const makeCallback = binding.makeCallback;
|
||||
|
||||
function myMultiArgFunc(arg1, arg2, arg3) {
|
||||
assert.strictEqual(arg1, 1);
|
||||
assert.strictEqual(arg2, 2);
|
||||
assert.strictEqual(arg3, 3);
|
||||
return 42;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource should be able to be arbitrary objects without special internal
|
||||
* slots. Testing with plain object here.
|
||||
*/
|
||||
const resource = {};
|
||||
assert.strictEqual(makeCallback(resource, process, common.mustCall(function() {
|
||||
assert.strictEqual(arguments.length, 0);
|
||||
assert.strictEqual(this, process);
|
||||
return 42;
|
||||
})), 42);
|
||||
|
||||
assert.strictEqual(makeCallback(resource, process, common.mustCall(function(x) {
|
||||
assert.strictEqual(arguments.length, 1);
|
||||
assert.strictEqual(this, process);
|
||||
assert.strictEqual(x, 1337);
|
||||
return 42;
|
||||
}), 1337), 42);
|
||||
|
||||
assert.strictEqual(makeCallback(resource, this,
|
||||
common.mustCall(myMultiArgFunc), 1, 2, 3), 42);
|
||||
|
||||
// TODO(node-api): napi_make_callback needs to support
|
||||
// strings passed for the func argument
|
||||
//
|
||||
// const recv = {
|
||||
// one: common.mustCall(function() {
|
||||
// assert.strictEqual(0, arguments.length);
|
||||
// assert.strictEqual(this, recv);
|
||||
// return 42;
|
||||
// }),
|
||||
// two: common.mustCall(function(x) {
|
||||
// assert.strictEqual(1, arguments.length);
|
||||
// assert.strictEqual(this, recv);
|
||||
// assert.strictEqual(x, 1337);
|
||||
// return 42;
|
||||
// }),
|
||||
// };
|
||||
//
|
||||
// assert.strictEqual(makeCallback(recv, 'one'), 42);
|
||||
// assert.strictEqual(makeCallback(recv, 'two', 1337), 42);
|
||||
//
|
||||
// // Check that callbacks on a receiver from a different context works.
|
||||
// const foreignObject = vm.runInNewContext('({ fortytwo() { return 42; } })');
|
||||
// assert.strictEqual(makeCallback(foreignObject, 'fortytwo'), 42);
|
||||
|
||||
|
||||
// Check that the callback is made in the context of the receiver.
|
||||
const target = vm.runInNewContext(`
|
||||
(function($Object) {
|
||||
if (Object === $Object)
|
||||
throw new Error('bad');
|
||||
return Object;
|
||||
})
|
||||
`);
|
||||
assert.notStrictEqual(makeCallback(resource, process, target, Object), Object);
|
||||
|
||||
// Runs in inner context.
|
||||
const forward = vm.runInNewContext(`
|
||||
(function(forward) {
|
||||
return forward(Object);
|
||||
})
|
||||
`);
|
||||
|
||||
// Runs in outer context.
|
||||
function endpoint($Object) {
|
||||
if (Object === $Object)
|
||||
throw new Error('bad');
|
||||
return Object;
|
||||
}
|
||||
|
||||
assert.strictEqual(makeCallback(resource, process, forward, endpoint), Object);
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static napi_value MakeCallback(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 2;
|
||||
napi_value args[2];
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
|
||||
|
||||
napi_value recv = args[0];
|
||||
napi_value func = args[1];
|
||||
|
||||
napi_status status = napi_make_callback(env, NULL /* async_context */,
|
||||
recv, func, 0 /* argc */, NULL /* argv */, NULL /* result */);
|
||||
|
||||
bool isExceptionPending;
|
||||
NODE_API_CALL(env, napi_is_exception_pending(env, &isExceptionPending));
|
||||
if (isExceptionPending && !(status == napi_pending_exception)) {
|
||||
// if there is an exception pending we don't expect any
|
||||
// other error
|
||||
napi_value pending_error;
|
||||
status = napi_get_and_clear_last_exception(env, &pending_error);
|
||||
NODE_API_CALL(env,
|
||||
napi_throw_error((env),
|
||||
NULL,
|
||||
"error when only pending exception expected"));
|
||||
}
|
||||
|
||||
return recv;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_value fn;
|
||||
NODE_API_CALL(env, napi_create_function(
|
||||
// NOLINTNEXTLINE (readability/null_usage)
|
||||
env, NULL, NAPI_AUTO_LENGTH, MakeCallback, NULL, &fn));
|
||||
NODE_API_CALL(env, napi_set_named_property(env, exports, "makeCallback", fn));
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const domain = require('domain');
|
||||
const binding = require(`./build/${common.buildType}/binding`);
|
||||
const makeCallback = binding.makeCallback;
|
||||
|
||||
// Make sure this is run in the future.
|
||||
const mustCallCheckDomains = common.mustCall(checkDomains);
|
||||
|
||||
|
||||
// Make sure that using MakeCallback allows the error to propagate.
|
||||
assert.throws(function() {
|
||||
makeCallback({}, function() {
|
||||
throw new Error('hi from domain error');
|
||||
});
|
||||
}, /^Error: hi from domain error$/);
|
||||
|
||||
|
||||
// Check the execution order of the nextTickQueue and MicrotaskQueue in
|
||||
// relation to running multiple MakeCallback's from bootstrap,
|
||||
// node::MakeCallback() and node::AsyncWrap::MakeCallback().
|
||||
// TODO(trevnorris): Is there a way to verify this is being run during
|
||||
// bootstrap?
|
||||
(function verifyExecutionOrder(arg) {
|
||||
const results = [];
|
||||
|
||||
// Processing of the MicrotaskQueue is manually handled by node. They are not
|
||||
// processed until after the nextTickQueue has been processed.
|
||||
Promise.resolve(1).then(common.mustCall(function() {
|
||||
results.push(7);
|
||||
}));
|
||||
|
||||
// The nextTick should run after all immediately invoked calls.
|
||||
process.nextTick(common.mustCall(function() {
|
||||
results.push(3);
|
||||
|
||||
// Run same test again but while processing the nextTickQueue to make sure
|
||||
// the following MakeCallback call breaks in the middle of processing the
|
||||
// queue and allows the script to run normally.
|
||||
process.nextTick(common.mustCall(function() {
|
||||
results.push(6);
|
||||
}));
|
||||
|
||||
makeCallback({}, common.mustCall(function() {
|
||||
results.push(4);
|
||||
}));
|
||||
|
||||
results.push(5);
|
||||
}));
|
||||
|
||||
results.push(0);
|
||||
|
||||
// MakeCallback is calling the function immediately, but should then detect
|
||||
// that a script is already in the middle of execution and return before
|
||||
// either the nextTickQueue or MicrotaskQueue are processed.
|
||||
makeCallback({}, common.mustCall(function() {
|
||||
results.push(1);
|
||||
}));
|
||||
|
||||
// This should run before either the nextTickQueue or MicrotaskQueue are
|
||||
// processed. Previously MakeCallback would not detect this circumstance
|
||||
// and process them immediately.
|
||||
results.push(2);
|
||||
|
||||
setImmediate(common.mustCall(function() {
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
assert.strictEqual(results[i], i,
|
||||
`verifyExecutionOrder(${arg}) results: ${results}`);
|
||||
}
|
||||
if (arg === 1) {
|
||||
// The tests are first run on bootstrap during LoadEnvironment() in
|
||||
// src/node.cc. Now run the tests through node::MakeCallback().
|
||||
setImmediate(function() {
|
||||
makeCallback({}, common.mustCall(function() {
|
||||
verifyExecutionOrder(2);
|
||||
}));
|
||||
});
|
||||
} else if (arg === 2) {
|
||||
// Make sure there are no conflicts using node::MakeCallback()
|
||||
// within timers.
|
||||
setTimeout(common.mustCall(function() {
|
||||
verifyExecutionOrder(3);
|
||||
}), 10);
|
||||
} else if (arg === 3) {
|
||||
mustCallCheckDomains();
|
||||
} else {
|
||||
throw new Error('UNREACHABLE');
|
||||
}
|
||||
}));
|
||||
}(1));
|
||||
|
||||
|
||||
function checkDomains() {
|
||||
// Check that domains are properly entered/exited when called in multiple
|
||||
// levels from both node::MakeCallback() and AsyncWrap::MakeCallback
|
||||
setImmediate(common.mustCall(function() {
|
||||
const d1 = domain.create();
|
||||
const d2 = domain.create();
|
||||
const d3 = domain.create();
|
||||
|
||||
makeCallback({ domain: d1 }, common.mustCall(function() {
|
||||
assert.strictEqual(d1, process.domain);
|
||||
makeCallback({ domain: d2 }, common.mustCall(function() {
|
||||
assert.strictEqual(d2, process.domain);
|
||||
makeCallback({ domain: d3 }, common.mustCall(function() {
|
||||
assert.strictEqual(d3, process.domain);
|
||||
}));
|
||||
assert.strictEqual(d2, process.domain);
|
||||
}));
|
||||
assert.strictEqual(d1, process.domain);
|
||||
}));
|
||||
}));
|
||||
|
||||
setTimeout(common.mustCall(function() {
|
||||
const d1 = domain.create();
|
||||
const d2 = domain.create();
|
||||
const d3 = domain.create();
|
||||
|
||||
makeCallback({ domain: d1 }, common.mustCall(function() {
|
||||
assert.strictEqual(d1, process.domain);
|
||||
makeCallback({ domain: d2 }, common.mustCall(function() {
|
||||
assert.strictEqual(d2, process.domain);
|
||||
makeCallback({ domain: d3 }, common.mustCall(function() {
|
||||
assert.strictEqual(d3, process.domain);
|
||||
}));
|
||||
assert.strictEqual(d2, process.domain);
|
||||
}));
|
||||
assert.strictEqual(d1, process.domain);
|
||||
}));
|
||||
}), 1);
|
||||
|
||||
function testTimer(id) {
|
||||
// Make sure nextTick, setImmediate and setTimeout can all recover properly
|
||||
// after a thrown makeCallback call.
|
||||
const d = domain.create();
|
||||
d.on('error', common.mustCall(function(e) {
|
||||
assert.strictEqual(e.message, `throw from domain ${id}`);
|
||||
}));
|
||||
makeCallback({ domain: d }, function() {
|
||||
throw new Error(`throw from domain ${id}`);
|
||||
});
|
||||
throw new Error('UNREACHABLE');
|
||||
}
|
||||
|
||||
process.nextTick(common.mustCall(testTimer), 3);
|
||||
setImmediate(common.mustCall(testTimer), 2);
|
||||
setTimeout(common.mustCall(testTimer), 1, 1);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'test_null_init',
|
||||
'sources': [ 'test_null_init.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
|
||||
assert.throws(
|
||||
() => require(`./build/${common.buildType}/test_null_init`),
|
||||
/Module has no declared entry point[.]/);
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <node_api.h>
|
||||
|
||||
// This test uses old module initialization style deprecated in current code.
|
||||
// The goal is to see that all previously compiled code continues to work the
|
||||
// same way as before.
|
||||
// The test has a copy of previous macro definitions which are removed from
|
||||
// the node_api.h file.
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(__cplusplus)
|
||||
#define NAPI_C_CTOR(fn) \
|
||||
static void NAPI_CDECL fn(void); \
|
||||
namespace { \
|
||||
struct fn##_ { \
|
||||
fn##_() { fn(); } \
|
||||
} fn##_v_; \
|
||||
} \
|
||||
static void NAPI_CDECL fn(void)
|
||||
#else // !defined(__cplusplus)
|
||||
#pragma section(".CRT$XCU", read)
|
||||
// The NAPI_C_CTOR macro defines a function fn that is called during CRT
|
||||
// initialization.
|
||||
// C does not support dynamic initialization of static variables and this code
|
||||
// simulates C++ behavior. Exporting the function pointer prevents it from being
|
||||
// optimized. See for details:
|
||||
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170
|
||||
#define NAPI_C_CTOR(fn) \
|
||||
static void NAPI_CDECL fn(void); \
|
||||
__declspec(dllexport, allocate(".CRT$XCU")) void(NAPI_CDECL * fn##_)(void) = \
|
||||
fn; \
|
||||
static void NAPI_CDECL fn(void)
|
||||
#endif // defined(__cplusplus)
|
||||
#else
|
||||
#define NAPI_C_CTOR(fn) \
|
||||
static void fn(void) __attribute__((constructor)); \
|
||||
static void fn(void)
|
||||
#endif
|
||||
|
||||
#define NAPI_MODULE_TEST(modname, regfunc) \
|
||||
EXTERN_C_START \
|
||||
static napi_module _module = { \
|
||||
NAPI_MODULE_VERSION, \
|
||||
0, \
|
||||
__FILE__, \
|
||||
regfunc, \
|
||||
#modname, \
|
||||
NULL, \
|
||||
{0}, \
|
||||
}; \
|
||||
NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \
|
||||
EXTERN_C_END
|
||||
|
||||
NAPI_MODULE_TEST(NODE_GYP_MODULE_NAME, NULL)
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_reference_all_types",
|
||||
"sources": [ "test_reference_by_node_api_version.c" ],
|
||||
"defines": [ "NAPI_EXPERIMENTAL" ],
|
||||
},
|
||||
{
|
||||
"target_name": "test_reference_obj_only",
|
||||
"sources": [ "test_reference_by_node_api_version.c" ],
|
||||
"defines": [ "NAPI_VERSION=8" ],
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
//
|
||||
// Testing API calls for Node-API references.
|
||||
// We compare their behavior between Node-API version 8 and later.
|
||||
// In version 8 references can be created only for object, function,
|
||||
// and symbol types, while in newer versions they can be created for
|
||||
// any value type.
|
||||
//
|
||||
const { buildType } = require('../../common');
|
||||
const { gcUntil } = require('../../common/gc');
|
||||
const assert = require('assert');
|
||||
const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`);
|
||||
const addon_new = require(`./build/${buildType}/test_reference_all_types`);
|
||||
|
||||
async function runTests(addon, isVersion8, isLocalSymbol) {
|
||||
let allEntries = [];
|
||||
|
||||
(() => {
|
||||
// Create values of all napi_valuetype types.
|
||||
const undefinedValue = undefined;
|
||||
const nullValue = null;
|
||||
const booleanValue = false;
|
||||
const numberValue = 42;
|
||||
const stringValue = 'test_string';
|
||||
const globalSymbolValue = Symbol.for('test_symbol_global');
|
||||
const localSymbolValue = Symbol('test_symbol_local');
|
||||
const symbolValue = isLocalSymbol ? localSymbolValue : globalSymbolValue;
|
||||
const objectValue = { x: 1, y: 2 };
|
||||
const functionValue = (x, y) => x + y;
|
||||
const externalValue = addon.createExternal();
|
||||
const bigintValue = 9007199254740991n;
|
||||
|
||||
// The position of entries in the allEntries array corresponds to the
|
||||
// napi_valuetype enum value. See the CreateRef function for the
|
||||
// implementation details.
|
||||
allEntries = [
|
||||
{ value: undefinedValue, canBeWeak: false, canBeRefV8: false },
|
||||
{ value: nullValue, canBeWeak: false, canBeRefV8: false },
|
||||
{ value: booleanValue, canBeWeak: false, canBeRefV8: false },
|
||||
{ value: numberValue, canBeWeak: false, canBeRefV8: false },
|
||||
{ value: stringValue, canBeWeak: false, canBeRefV8: false },
|
||||
{ value: symbolValue, canBeWeak: isLocalSymbol, canBeRefV8: true,
|
||||
isAlwaysStrong: !isLocalSymbol },
|
||||
{ value: objectValue, canBeWeak: true, canBeRefV8: true },
|
||||
{ value: functionValue, canBeWeak: true, canBeRefV8: true },
|
||||
{ value: externalValue, canBeWeak: true, canBeRefV8: true },
|
||||
{ value: bigintValue, canBeWeak: false, canBeRefV8: false },
|
||||
];
|
||||
|
||||
// Go over all values of different types, create strong ref values for
|
||||
// them, read the stored values, and check how the ref count works.
|
||||
for (const entry of allEntries) {
|
||||
if (!isVersion8 || entry.canBeRefV8) {
|
||||
const index = addon.createRef(entry.value);
|
||||
const refValue = addon.getRefValue(index);
|
||||
assert.strictEqual(entry.value, refValue);
|
||||
assert.strictEqual(addon.ref(index), 2);
|
||||
assert.strictEqual(addon.unref(index), 1);
|
||||
assert.strictEqual(addon.unref(index), 0);
|
||||
} else {
|
||||
assert.throws(() => { addon.createRef(entry.value); },
|
||||
{
|
||||
name: 'Error',
|
||||
message: 'Invalid argument',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// When the reference count is zero, then object types become weak pointers
|
||||
// and other types are released.
|
||||
// Here we know that the GC is not run yet because the values are
|
||||
// still in the allEntries array.
|
||||
allEntries.forEach((entry, index) => {
|
||||
if (!isVersion8 || entry.canBeRefV8) {
|
||||
if (entry.canBeWeak || entry.isAlwaysStrong) {
|
||||
assert.strictEqual(addon.getRefValue(index), entry.value);
|
||||
} else {
|
||||
assert.strictEqual(addon.getRefValue(index), undefined);
|
||||
}
|
||||
}
|
||||
// Set to undefined to allow GC collect the value.
|
||||
entry.value = undefined;
|
||||
});
|
||||
|
||||
// To check that GC pass is done.
|
||||
const objWithFinalizer = {};
|
||||
addon.addFinalizer(objWithFinalizer);
|
||||
})();
|
||||
|
||||
addon.initFinalizeCount();
|
||||
assert.strictEqual(addon.getFinalizeCount(), 0);
|
||||
await gcUntil('Wait until a finalizer is called',
|
||||
() => (addon.getFinalizeCount() === 1));
|
||||
|
||||
// Create and call finalizer again to make sure that we had another GC pass.
|
||||
(() => {
|
||||
const objWithFinalizer = {};
|
||||
addon.addFinalizer(objWithFinalizer);
|
||||
})();
|
||||
await gcUntil('Wait until a finalizer is called again',
|
||||
() => (addon.getFinalizeCount() === 2));
|
||||
|
||||
// After GC and finalizers run, all values that support weak reference
|
||||
// semantic must return undefined value.
|
||||
allEntries.forEach((entry, index) => {
|
||||
if (!isVersion8 || entry.canBeRefV8) {
|
||||
if (!entry.isAlwaysStrong) {
|
||||
assert.strictEqual(addon.getRefValue(index), undefined);
|
||||
} else {
|
||||
assert.notStrictEqual(addon.getRefValue(index), undefined);
|
||||
}
|
||||
addon.deleteRef(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function runAllTests() {
|
||||
await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ true);
|
||||
await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ false);
|
||||
await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ true);
|
||||
await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ false);
|
||||
}
|
||||
|
||||
runAllTests();
|
||||
@@ -0,0 +1,169 @@
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
#include "stdlib.h"
|
||||
|
||||
static uint32_t finalizeCount = 0;
|
||||
|
||||
static void FreeData(node_api_basic_env env, void* data, void* hint) {
|
||||
NODE_API_BASIC_ASSERT_RETURN_VOID(data != NULL, "Expects non-NULL data.");
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void Finalize(node_api_basic_env env, void* data, void* hint) {
|
||||
++finalizeCount;
|
||||
}
|
||||
|
||||
static napi_status GetArgValue(napi_env env,
|
||||
napi_callback_info info,
|
||||
napi_value* argValue) {
|
||||
size_t argc = 1;
|
||||
NODE_API_CHECK_STATUS(
|
||||
napi_get_cb_info(env, info, &argc, argValue, NULL, NULL));
|
||||
|
||||
NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg.");
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
static napi_status GetArgValueAsIndex(napi_env env,
|
||||
napi_callback_info info,
|
||||
uint32_t* index) {
|
||||
napi_value argValue;
|
||||
NODE_API_CHECK_STATUS(GetArgValue(env, info, &argValue));
|
||||
|
||||
napi_valuetype valueType;
|
||||
NODE_API_CHECK_STATUS(napi_typeof(env, argValue, &valueType));
|
||||
NODE_API_ASSERT_STATUS(
|
||||
env, valueType == napi_number, "Argument must be a number.");
|
||||
|
||||
return napi_get_value_uint32(env, argValue, index);
|
||||
}
|
||||
|
||||
static napi_status GetRef(napi_env env,
|
||||
napi_callback_info info,
|
||||
napi_ref* ref) {
|
||||
uint32_t index;
|
||||
NODE_API_CHECK_STATUS(GetArgValueAsIndex(env, info, &index));
|
||||
|
||||
napi_ref* refValues;
|
||||
NODE_API_CHECK_STATUS(napi_get_instance_data(env, (void**)&refValues));
|
||||
NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data.");
|
||||
|
||||
*ref = refValues[index];
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
static napi_value ToUInt32Value(napi_env env, uint32_t value) {
|
||||
napi_value result;
|
||||
NODE_API_CALL(env, napi_create_uint32(env, value, &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static napi_status InitRefArray(napi_env env) {
|
||||
// valueRefs array has one entry per napi_valuetype
|
||||
napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1));
|
||||
return napi_set_instance_data(env, valueRefs, (napi_finalize)&FreeData, NULL);
|
||||
}
|
||||
|
||||
static napi_value CreateExternal(napi_env env, napi_callback_info info) {
|
||||
napi_value result;
|
||||
int* data = (int*)malloc(sizeof(int));
|
||||
*data = 42;
|
||||
NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static napi_value CreateRef(napi_env env, napi_callback_info info) {
|
||||
napi_value argValue;
|
||||
NODE_API_CALL(env, GetArgValue(env, info, &argValue));
|
||||
|
||||
napi_valuetype valueType;
|
||||
NODE_API_CALL(env, napi_typeof(env, argValue, &valueType));
|
||||
uint32_t index = (uint32_t)valueType;
|
||||
|
||||
napi_ref* valueRefs;
|
||||
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_reference(env, argValue, 1, valueRefs + index));
|
||||
|
||||
return ToUInt32Value(env, index);
|
||||
}
|
||||
|
||||
static napi_value GetRefValue(napi_env env, napi_callback_info info) {
|
||||
napi_ref refValue;
|
||||
NODE_API_CALL(env, GetRef(env, info, &refValue));
|
||||
napi_value value;
|
||||
NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static napi_value Ref(napi_env env, napi_callback_info info) {
|
||||
napi_ref refValue;
|
||||
NODE_API_CALL(env, GetRef(env, info, &refValue));
|
||||
uint32_t refCount;
|
||||
NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount));
|
||||
return ToUInt32Value(env, refCount);
|
||||
}
|
||||
|
||||
static napi_value Unref(napi_env env, napi_callback_info info) {
|
||||
napi_ref refValue;
|
||||
NODE_API_CALL(env, GetRef(env, info, &refValue));
|
||||
uint32_t refCount;
|
||||
NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount));
|
||||
return ToUInt32Value(env, refCount);
|
||||
}
|
||||
|
||||
static napi_value DeleteRef(napi_env env, napi_callback_info info) {
|
||||
napi_ref refValue;
|
||||
NODE_API_CALL(env, GetRef(env, info, &refValue));
|
||||
NODE_API_CALL(env, napi_delete_reference(env, refValue));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value AddFinalizer(napi_env env, napi_callback_info info) {
|
||||
napi_value obj;
|
||||
NODE_API_CALL(env, GetArgValue(env, info, &obj));
|
||||
|
||||
napi_valuetype valueType;
|
||||
NODE_API_CALL(env, napi_typeof(env, obj, &valueType));
|
||||
NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object.");
|
||||
|
||||
NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) {
|
||||
return ToUInt32Value(env, finalizeCount);
|
||||
}
|
||||
|
||||
static napi_value InitFinalizeCount(napi_env env, napi_callback_info info) {
|
||||
finalizeCount = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EXTERN_C_START
|
||||
|
||||
NAPI_MODULE_INIT() {
|
||||
finalizeCount = 0;
|
||||
NODE_API_CALL(env, InitRefArray(env));
|
||||
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal),
|
||||
DECLARE_NODE_API_PROPERTY("createRef", CreateRef),
|
||||
DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue),
|
||||
DECLARE_NODE_API_PROPERTY("ref", Ref),
|
||||
DECLARE_NODE_API_PROPERTY("unref", Unref),
|
||||
DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef),
|
||||
DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer),
|
||||
DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount),
|
||||
DECLARE_NODE_API_PROPERTY("initFinalizeCount", InitFinalizeCount),
|
||||
};
|
||||
|
||||
NODE_API_CALL(
|
||||
env,
|
||||
napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
EXTERN_C_END
|
||||
@@ -0,0 +1,347 @@
|
||||
// For the purpose of this test we use libuv's threading library. When deciding
|
||||
// on a threading library for a new project it bears remembering that in the
|
||||
// future libuv may introduce API changes which may render it non-ABI-stable,
|
||||
// which, in turn, may affect the ABI stability of the project despite its use
|
||||
// of N-API.
|
||||
#include <uv.h>
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
#define ARRAY_LENGTH 10000
|
||||
#define MAX_QUEUE_SIZE 2
|
||||
|
||||
static uv_thread_t uv_threads[2];
|
||||
static napi_threadsafe_function ts_fn;
|
||||
|
||||
typedef struct {
|
||||
napi_threadsafe_function_call_mode block_on_full;
|
||||
napi_threadsafe_function_release_mode abort;
|
||||
bool start_secondary;
|
||||
napi_ref js_finalize_cb;
|
||||
uint32_t max_queue_size;
|
||||
} ts_fn_hint;
|
||||
|
||||
static ts_fn_hint ts_info;
|
||||
|
||||
// Thread data to transmit to JS
|
||||
static int ints[ARRAY_LENGTH];
|
||||
|
||||
static void secondary_thread(void* data) {
|
||||
napi_threadsafe_function ts_fn = data;
|
||||
|
||||
if (napi_release_threadsafe_function(ts_fn, napi_tsfn_release) != napi_ok) {
|
||||
napi_fatal_error("secondary_thread", NAPI_AUTO_LENGTH,
|
||||
"napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Source thread producing the data
|
||||
static void data_source_thread(void* data) {
|
||||
napi_threadsafe_function ts_fn = data;
|
||||
int index;
|
||||
void* hint;
|
||||
ts_fn_hint *ts_fn_info;
|
||||
napi_status status;
|
||||
bool queue_was_full = false;
|
||||
bool queue_was_closing = false;
|
||||
|
||||
if (napi_get_threadsafe_function_context(ts_fn, &hint) != napi_ok) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"napi_get_threadsafe_function_context failed", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
|
||||
ts_fn_info = (ts_fn_hint *)hint;
|
||||
|
||||
if (ts_fn_info != &ts_info) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"thread-safe function hint is not as expected", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
|
||||
if (ts_fn_info->start_secondary) {
|
||||
if (napi_acquire_threadsafe_function(ts_fn) != napi_ok) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"napi_acquire_threadsafe_function failed", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
|
||||
if (uv_thread_create(&uv_threads[1], secondary_thread, ts_fn) != 0) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"failed to start secondary thread", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
for (index = ARRAY_LENGTH - 1; index > -1 && !queue_was_closing; index--) {
|
||||
status = napi_call_threadsafe_function(ts_fn, &ints[index],
|
||||
ts_fn_info->block_on_full);
|
||||
if (ts_fn_info->max_queue_size == 0 && (index % 1000 == 0)) {
|
||||
// Let's make this thread really busy for 200 ms to give the main thread a
|
||||
// chance to abort.
|
||||
uint64_t start = uv_hrtime();
|
||||
for (; uv_hrtime() - start < 200000000;);
|
||||
}
|
||||
switch (status) {
|
||||
case napi_queue_full:
|
||||
queue_was_full = true;
|
||||
index++;
|
||||
// fall through
|
||||
|
||||
case napi_ok:
|
||||
continue;
|
||||
|
||||
case napi_closing:
|
||||
queue_was_closing = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"napi_call_threadsafe_function failed", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that the enqueuing of a value was refused at least once, if this is
|
||||
// a non-blocking test run.
|
||||
if (!ts_fn_info->block_on_full && !queue_was_full) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"queue was never full", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
|
||||
// Assert that the queue was marked as closing at least once, if this is an
|
||||
// aborting test run.
|
||||
if (ts_fn_info->abort == napi_tsfn_abort && !queue_was_closing) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"queue was never closing", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
|
||||
if (!queue_was_closing &&
|
||||
napi_release_threadsafe_function(ts_fn, napi_tsfn_release) != napi_ok) {
|
||||
napi_fatal_error("data_source_thread", NAPI_AUTO_LENGTH,
|
||||
"napi_release_threadsafe_function failed", NAPI_AUTO_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Getting the data into JS
|
||||
static void call_js(napi_env env, napi_value cb, void* hint, void* data) {
|
||||
if (!(env == NULL || cb == NULL)) {
|
||||
napi_value argv, undefined;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_int32(env, *(int*)data, &argv));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_call_function(env, undefined, cb, 1, &argv,
|
||||
NULL));
|
||||
}
|
||||
}
|
||||
|
||||
static napi_ref alt_ref;
|
||||
// Getting the data into JS with the alternative reference
|
||||
static void call_ref(napi_env env, napi_value _, void* hint, void* data) {
|
||||
if (!(env == NULL || alt_ref == NULL)) {
|
||||
napi_value fn, argv, undefined;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_reference_value(env, alt_ref, &fn));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_create_int32(env, *(int*)data, &argv));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_call_function(env, undefined, fn, 1, &argv,
|
||||
NULL));
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
static napi_value StopThread(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 2;
|
||||
napi_value argv[2];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
||||
napi_valuetype value_type;
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[0], &value_type));
|
||||
NODE_API_ASSERT(env, value_type == napi_function,
|
||||
"StopThread argument is a function");
|
||||
NODE_API_ASSERT(env, (ts_fn != NULL), "Existing threadsafe function");
|
||||
NODE_API_CALL(env,
|
||||
napi_create_reference(env, argv[0], 1, &(ts_info.js_finalize_cb)));
|
||||
bool abort;
|
||||
NODE_API_CALL(env, napi_get_value_bool(env, argv[1], &abort));
|
||||
NODE_API_CALL(env,
|
||||
napi_release_threadsafe_function(ts_fn,
|
||||
abort ? napi_tsfn_abort : napi_tsfn_release));
|
||||
ts_fn = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Join the thread and inform JS that we're done.
|
||||
static void join_the_threads(napi_env env, void *data, void *hint) {
|
||||
uv_thread_t *the_threads = data;
|
||||
ts_fn_hint *the_hint = hint;
|
||||
napi_value js_cb, undefined;
|
||||
|
||||
uv_thread_join(&the_threads[0]);
|
||||
if (the_hint->start_secondary) {
|
||||
uv_thread_join(&the_threads[1]);
|
||||
}
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_get_reference_value(env, the_hint->js_finalize_cb, &js_cb));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
|
||||
NODE_API_CALL_RETURN_VOID(env,
|
||||
napi_call_function(env, undefined, js_cb, 0, NULL, NULL));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env,
|
||||
the_hint->js_finalize_cb));
|
||||
if (alt_ref != NULL) {
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, alt_ref));
|
||||
alt_ref = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static napi_value StartThreadInternal(napi_env env,
|
||||
napi_callback_info info,
|
||||
napi_threadsafe_function_call_js cb,
|
||||
bool block_on_full,
|
||||
bool alt_ref_js_cb) {
|
||||
|
||||
size_t argc = 4;
|
||||
napi_value argv[4];
|
||||
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
||||
if (alt_ref_js_cb) {
|
||||
NODE_API_CALL(env, napi_create_reference(env, argv[0], 1, &alt_ref));
|
||||
argv[0] = NULL;
|
||||
}
|
||||
|
||||
ts_info.block_on_full =
|
||||
(block_on_full ? napi_tsfn_blocking : napi_tsfn_nonblocking);
|
||||
|
||||
NODE_API_ASSERT(env, (ts_fn == NULL), "Existing thread-safe function");
|
||||
napi_value async_name;
|
||||
NODE_API_CALL(env, napi_create_string_utf8(env,
|
||||
"N-API Thread-safe Function Test", NAPI_AUTO_LENGTH, &async_name));
|
||||
NODE_API_CALL(env,
|
||||
napi_get_value_uint32(env, argv[3], &ts_info.max_queue_size));
|
||||
NODE_API_CALL(env, napi_create_threadsafe_function(env,
|
||||
argv[0],
|
||||
NULL,
|
||||
async_name,
|
||||
ts_info.max_queue_size,
|
||||
2,
|
||||
uv_threads,
|
||||
join_the_threads,
|
||||
&ts_info,
|
||||
cb,
|
||||
&ts_fn));
|
||||
bool abort;
|
||||
NODE_API_CALL(env, napi_get_value_bool(env, argv[1], &abort));
|
||||
ts_info.abort = abort ? napi_tsfn_abort : napi_tsfn_release;
|
||||
NODE_API_CALL(env,
|
||||
napi_get_value_bool(env, argv[2], &(ts_info.start_secondary)));
|
||||
|
||||
NODE_API_ASSERT(env,
|
||||
(uv_thread_create(&uv_threads[0], data_source_thread, ts_fn) == 0),
|
||||
"Thread creation");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value Unref(napi_env env, napi_callback_info info) {
|
||||
NODE_API_ASSERT(env, ts_fn != NULL, "No existing thread-safe function");
|
||||
NODE_API_CALL(env, napi_unref_threadsafe_function(env, ts_fn));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value Release(napi_env env, napi_callback_info info) {
|
||||
NODE_API_ASSERT(env, ts_fn != NULL, "No existing thread-safe function");
|
||||
NODE_API_CALL(env, napi_release_threadsafe_function(ts_fn, napi_tsfn_release));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Startup
|
||||
static napi_value StartThread(napi_env env, napi_callback_info info) {
|
||||
return StartThreadInternal(env, info, call_js,
|
||||
/** block_on_full */true, /** alt_ref_js_cb */false);
|
||||
}
|
||||
|
||||
static napi_value StartThreadNonblocking(napi_env env,
|
||||
napi_callback_info info) {
|
||||
return StartThreadInternal(env, info, call_js,
|
||||
/** block_on_full */false, /** alt_ref_js_cb */false);
|
||||
}
|
||||
|
||||
static napi_value StartThreadNoNative(napi_env env, napi_callback_info info) {
|
||||
return StartThreadInternal(env, info, NULL,
|
||||
/** block_on_full */true, /** alt_ref_js_cb */false);
|
||||
}
|
||||
|
||||
static napi_value StartThreadNoJsFunc(napi_env env, napi_callback_info info) {
|
||||
return StartThreadInternal(env, info, call_ref,
|
||||
/** block_on_full */true, /** alt_ref_js_cb */true);
|
||||
}
|
||||
|
||||
// Testing calling into JavaScript
|
||||
static void ThreadSafeFunctionFinalize(napi_env env,
|
||||
void* finalize_data,
|
||||
void* finalize_hint) {
|
||||
napi_ref js_func_ref = (napi_ref) finalize_data;
|
||||
napi_value js_func;
|
||||
napi_value recv;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_reference_value(env, js_func_ref, &js_func));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &recv));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_call_function(env, recv, js_func, 0, NULL, NULL));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, js_func_ref));
|
||||
}
|
||||
|
||||
// Testing calling into JavaScript
|
||||
static napi_value CallIntoModule(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 4;
|
||||
napi_value argv[4];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
||||
|
||||
napi_ref finalize_func;
|
||||
NODE_API_CALL(env, napi_create_reference(env, argv[3], 1, &finalize_func));
|
||||
|
||||
napi_threadsafe_function tsfn;
|
||||
NODE_API_CALL(env, napi_create_threadsafe_function(env, argv[0], argv[1], argv[2], 0, 1, finalize_func, ThreadSafeFunctionFinalize, NULL, NULL, &tsfn));
|
||||
NODE_API_CALL(env, napi_call_threadsafe_function(tsfn, NULL, napi_tsfn_blocking));
|
||||
NODE_API_CALL(env, napi_release_threadsafe_function(tsfn, napi_tsfn_release));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Module init
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
size_t index;
|
||||
for (index = 0; index < ARRAY_LENGTH; index++) {
|
||||
ints[index] = index;
|
||||
}
|
||||
napi_value js_array_length, js_max_queue_size;
|
||||
napi_create_uint32(env, ARRAY_LENGTH, &js_array_length);
|
||||
napi_create_uint32(env, MAX_QUEUE_SIZE, &js_max_queue_size);
|
||||
|
||||
napi_property_descriptor properties[] = {
|
||||
{
|
||||
"ARRAY_LENGTH",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
js_array_length,
|
||||
napi_enumerable,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"MAX_QUEUE_SIZE",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
js_max_queue_size,
|
||||
napi_enumerable,
|
||||
NULL
|
||||
},
|
||||
DECLARE_NODE_API_PROPERTY("StartThread", StartThread),
|
||||
DECLARE_NODE_API_PROPERTY("StartThreadNoNative", StartThreadNoNative),
|
||||
DECLARE_NODE_API_PROPERTY("StartThreadNonblocking", StartThreadNonblocking),
|
||||
DECLARE_NODE_API_PROPERTY("StartThreadNoJsFunc", StartThreadNoJsFunc),
|
||||
DECLARE_NODE_API_PROPERTY("StopThread", StopThread),
|
||||
DECLARE_NODE_API_PROPERTY("Unref", Unref),
|
||||
DECLARE_NODE_API_PROPERTY("Release", Release),
|
||||
DECLARE_NODE_API_PROPERTY("CallIntoModule", CallIntoModule),
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(env, exports,
|
||||
sizeof(properties)/sizeof(properties[0]), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': ['binding.c']
|
||||
},
|
||||
{
|
||||
'target_name': 'test_uncaught_exception_v9',
|
||||
'defines': [
|
||||
'NAPI_VERSION=9'
|
||||
],
|
||||
'sources': ['test_uncaught_exception.c']
|
||||
},
|
||||
{
|
||||
'target_name': 'test_uncaught_exception',
|
||||
'defines': [
|
||||
'NAPI_EXPERIMENTAL'
|
||||
],
|
||||
'sources': ['test_uncaught_exception.c']
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const binding = require(`./build/${common.buildType}/binding`);
|
||||
const { fork } = require('child_process');
|
||||
const expectedArray = (function(arrayLength) {
|
||||
const result = [];
|
||||
for (let index = 0; index < arrayLength; index++) {
|
||||
result.push(arrayLength - 1 - index);
|
||||
}
|
||||
return result;
|
||||
})(binding.ARRAY_LENGTH);
|
||||
|
||||
// Handle the rapid teardown test case as the child process. We unref the
|
||||
// thread-safe function after we have received two values. This causes the
|
||||
// process to exit and the environment cleanup handler to be invoked.
|
||||
if (process.argv[2] === 'child') {
|
||||
let callCount = 0;
|
||||
binding.StartThread((value) => {
|
||||
callCount++;
|
||||
console.log(value);
|
||||
if (callCount === 2) {
|
||||
binding.Unref();
|
||||
}
|
||||
}, false /* abort */, true /* launchSecondary */, +process.argv[3]);
|
||||
|
||||
// Release the thread-safe function from the main thread so that it may be
|
||||
// torn down via the environment cleanup handler.
|
||||
binding.Release();
|
||||
return;
|
||||
}
|
||||
|
||||
function testWithJSMarshaller({
|
||||
threadStarter,
|
||||
quitAfter,
|
||||
abort,
|
||||
maxQueueSize,
|
||||
launchSecondary,
|
||||
}) {
|
||||
return new Promise((resolve) => {
|
||||
const array = [];
|
||||
binding[threadStarter](function testCallback(value) {
|
||||
array.push(value);
|
||||
if (array.length === quitAfter) {
|
||||
setImmediate(() => {
|
||||
binding.StopThread(common.mustCall(() => {
|
||||
resolve(array);
|
||||
}), !!abort);
|
||||
});
|
||||
}
|
||||
}, !!abort, !!launchSecondary, maxQueueSize);
|
||||
if (threadStarter === 'StartThreadNonblocking') {
|
||||
// Let's make this thread really busy for a short while to ensure that
|
||||
// the queue fills and the thread receives a napi_queue_full.
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testUnref(queueSize) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let output = '';
|
||||
const child = fork(__filename, ['child', queueSize], {
|
||||
stdio: [process.stdin, 'pipe', process.stderr, 'ipc'],
|
||||
});
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve(output.match(/\S+/g));
|
||||
} else {
|
||||
reject(new Error('Child process died with code ' + code));
|
||||
}
|
||||
});
|
||||
child.stdout.on('data', (data) => (output += data.toString()));
|
||||
})
|
||||
.then((result) => assert.strictEqual(result.indexOf(0), -1));
|
||||
}
|
||||
|
||||
new Promise(function testWithoutJSMarshaller(resolve) {
|
||||
let callCount = 0;
|
||||
binding.StartThreadNoNative(function testCallback() {
|
||||
callCount++;
|
||||
|
||||
// The default call-into-JS implementation passes no arguments.
|
||||
assert.strictEqual(arguments.length, 0);
|
||||
if (callCount === binding.ARRAY_LENGTH) {
|
||||
setImmediate(() => {
|
||||
binding.StopThread(common.mustCall(() => {
|
||||
resolve();
|
||||
}), false);
|
||||
});
|
||||
}
|
||||
}, false /* abort */, false /* launchSecondary */, binding.MAX_QUEUE_SIZE);
|
||||
})
|
||||
|
||||
// Start the thread in blocking mode, and assert that all values are passed.
|
||||
// Quit after it's done.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
quitAfter: binding.ARRAY_LENGTH,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in blocking mode, and assert that all values are passed.
|
||||
// Quit after it's done.
|
||||
// Doesn't pass the callback js function to napi_create_threadsafe_function.
|
||||
// Instead, use an alternative reference to get js function called.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThreadNoJsFunc',
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
quitAfter: binding.ARRAY_LENGTH,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in blocking mode with an infinite queue, and assert that all
|
||||
// values are passed. Quit after it's done.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
maxQueueSize: 0,
|
||||
quitAfter: binding.ARRAY_LENGTH,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in non-blocking mode, and assert that all values are passed.
|
||||
// Quit after it's done.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThreadNonblocking',
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
quitAfter: binding.ARRAY_LENGTH,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in blocking mode, and assert that all values are passed.
|
||||
// Quit early, but let the thread finish.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
quitAfter: 1,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in blocking mode with an infinite queue, and assert that all
|
||||
// values are passed. Quit early, but let the thread finish.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
maxQueueSize: 0,
|
||||
quitAfter: 1,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in non-blocking mode, and assert that all values are passed.
|
||||
// Quit early, but let the thread finish.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThreadNonblocking',
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
quitAfter: 1,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in blocking mode, and assert that all values are passed.
|
||||
// Quit early, but let the thread finish. Launch a secondary thread to test the
|
||||
// reference counter incrementing functionality.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
quitAfter: 1,
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
launchSecondary: true,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in non-blocking mode, and assert that all values are passed.
|
||||
// Quit early, but let the thread finish. Launch a secondary thread to test the
|
||||
// reference counter incrementing functionality.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThreadNonblocking',
|
||||
quitAfter: 1,
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
launchSecondary: true,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start the thread in blocking mode, and assert that it could not finish.
|
||||
// Quit early by aborting.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
quitAfter: 1,
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
abort: true,
|
||||
}))
|
||||
.then((result) => assert.strictEqual(result.indexOf(0), -1))
|
||||
|
||||
// Start the thread in blocking mode with an infinite queue, and assert that it
|
||||
// could not finish. Quit early by aborting.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThread',
|
||||
quitAfter: 1,
|
||||
maxQueueSize: 0,
|
||||
abort: true,
|
||||
}))
|
||||
.then((result) => assert.strictEqual(result.indexOf(0), -1))
|
||||
|
||||
// Start the thread in non-blocking mode, and assert that it could not finish.
|
||||
// Quit early and aborting.
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThreadNonblocking',
|
||||
quitAfter: 1,
|
||||
maxQueueSize: binding.MAX_QUEUE_SIZE,
|
||||
abort: true,
|
||||
}))
|
||||
.then((result) => assert.strictEqual(result.indexOf(0), -1))
|
||||
|
||||
// Make sure that threadsafe function isn't stalled when we hit
|
||||
// `kMaxIterationCount` in `src/node_api.cc`
|
||||
.then(() => testWithJSMarshaller({
|
||||
threadStarter: 'StartThreadNonblocking',
|
||||
maxQueueSize: binding.ARRAY_LENGTH >>> 1,
|
||||
quitAfter: binding.ARRAY_LENGTH,
|
||||
}))
|
||||
.then((result) => assert.deepStrictEqual(result, expectedArray))
|
||||
|
||||
// Start a child process to test rapid teardown
|
||||
.then(() => testUnref(binding.MAX_QUEUE_SIZE))
|
||||
|
||||
// Start a child process with an infinite queue to test rapid teardown
|
||||
.then(() => testUnref(0));
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
// Flags: --no-force-node-api-uncaught-exceptions-policy
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/test_uncaught_exception_v9`);
|
||||
|
||||
process.on(
|
||||
'uncaughtException',
|
||||
common.mustNotCall('uncaught callback errors should be suppressed ' +
|
||||
'with the option --no-force-node-api-uncaught-exceptions-policy'),
|
||||
);
|
||||
|
||||
binding.CallIntoModule(
|
||||
common.mustCall(() => {
|
||||
throw new Error('callback error');
|
||||
}),
|
||||
{},
|
||||
'resource_name',
|
||||
common.mustCall(function finalizer() {
|
||||
throw new Error('finalizer error');
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,62 @@
|
||||
#include <node_api.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
// Testing calling into JavaScript
|
||||
static void ThreadSafeFunctionFinalize(napi_env env,
|
||||
void* finalize_data,
|
||||
void* finalize_hint) {
|
||||
napi_ref js_func_ref = (napi_ref)finalize_data;
|
||||
napi_value js_func;
|
||||
napi_value recv;
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_get_reference_value(env, js_func_ref, &js_func));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_get_global(env, &recv));
|
||||
NODE_API_CALL_RETURN_VOID(
|
||||
env, napi_call_function(env, recv, js_func, 0, NULL, NULL));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, js_func_ref));
|
||||
}
|
||||
|
||||
// Testing calling into JavaScript
|
||||
static napi_value CallIntoModule(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 4;
|
||||
napi_value argv[4];
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
||||
|
||||
napi_ref finalize_func;
|
||||
NODE_API_CALL(env, napi_create_reference(env, argv[3], 1, &finalize_func));
|
||||
|
||||
napi_threadsafe_function tsfn;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_threadsafe_function(env,
|
||||
argv[0],
|
||||
argv[1],
|
||||
argv[2],
|
||||
0,
|
||||
1,
|
||||
finalize_func,
|
||||
ThreadSafeFunctionFinalize,
|
||||
NULL,
|
||||
NULL,
|
||||
&tsfn));
|
||||
NODE_API_CALL(env,
|
||||
napi_call_threadsafe_function(tsfn, NULL, napi_tsfn_blocking));
|
||||
NODE_API_CALL(env, napi_release_threadsafe_function(tsfn, napi_tsfn_release));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Module init
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("CallIntoModule", CallIntoModule),
|
||||
};
|
||||
|
||||
NODE_API_CALL(
|
||||
env,
|
||||
napi_define_properties(env,
|
||||
exports,
|
||||
sizeof(properties) / sizeof(properties[0]),
|
||||
properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/test_uncaught_exception`);
|
||||
const { testUncaughtException } = require('./uncaught_exception');
|
||||
|
||||
testUncaughtException(binding);
|
||||
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
// Flags: --force-node-api-uncaught-exceptions-policy
|
||||
|
||||
const common = require('../../common');
|
||||
const binding = require(`./build/${common.buildType}/test_uncaught_exception_v9`);
|
||||
const { testUncaughtException } = require('./uncaught_exception');
|
||||
|
||||
testUncaughtException(binding);
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
|
||||
function testUncaughtException(binding) {
|
||||
const callbackCheck = common.mustCall((err) => {
|
||||
assert.throws(() => { throw err; }, /callback error/);
|
||||
process.removeListener('uncaughtException', callbackCheck);
|
||||
process.on('uncaughtException', finalizerCheck);
|
||||
});
|
||||
const finalizerCheck = common.mustCall((err) => {
|
||||
assert.throws(() => { throw err; }, /finalizer error/);
|
||||
});
|
||||
process.on('uncaughtException', callbackCheck);
|
||||
|
||||
binding.CallIntoModule(
|
||||
common.mustCall(() => {
|
||||
throw new Error('callback error');
|
||||
}),
|
||||
{},
|
||||
'resource_name',
|
||||
common.mustCall(function finalizer() {
|
||||
throw new Error('finalizer error');
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testUncaughtException,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_uv_loop",
|
||||
"sources": [ "test_uv_loop.cc" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const { SetImmediate } = require(`./build/${common.buildType}/test_uv_loop`);
|
||||
|
||||
SetImmediate(common.mustCall());
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
#include <node_api.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
template <typename T>
|
||||
void* SetImmediate(napi_env env, T&& cb) {
|
||||
T* ptr = new T(std::move(cb));
|
||||
uv_loop_t* loop = nullptr;
|
||||
uv_check_t* check = new uv_check_t;
|
||||
check->data = ptr;
|
||||
NODE_API_ASSERT(env,
|
||||
napi_get_uv_event_loop(env, &loop) == napi_ok,
|
||||
"can get event loop");
|
||||
uv_check_init(loop, check);
|
||||
uv_check_start(check, [](uv_check_t* check) {
|
||||
std::unique_ptr<T> ptr {static_cast<T*>(check->data)};
|
||||
T cb = std::move(*ptr);
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(check), [](uv_handle_t* handle) {
|
||||
delete reinterpret_cast<uv_check_t*>(handle);
|
||||
});
|
||||
|
||||
assert(cb() != nullptr);
|
||||
});
|
||||
// Idle handle is needed only to stop the event loop from blocking in poll.
|
||||
uv_idle_t* idle = new uv_idle_t;
|
||||
uv_idle_init(loop, idle);
|
||||
uv_idle_start(idle, [](uv_idle_t* idle) {
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(idle), [](uv_handle_t* handle) {
|
||||
delete reinterpret_cast<uv_check_t*>(handle);
|
||||
});
|
||||
});
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static char dummy;
|
||||
|
||||
napi_value SetImmediateBinding(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value argv[1];
|
||||
napi_value _this;
|
||||
void* data;
|
||||
NODE_API_CALL(env,
|
||||
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
|
||||
NODE_API_ASSERT(env, argc >= 1, "Not enough arguments, expected 1.");
|
||||
|
||||
napi_valuetype t;
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[0], &t));
|
||||
NODE_API_ASSERT(env, t == napi_function,
|
||||
"Wrong first argument, function expected.");
|
||||
|
||||
napi_ref cbref;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_reference(env, argv[0], 1, &cbref));
|
||||
|
||||
SetImmediate(env, [=]() -> char* {
|
||||
napi_value undefined;
|
||||
napi_value callback;
|
||||
napi_handle_scope scope;
|
||||
NODE_API_CALL(env, napi_open_handle_scope(env, &scope));
|
||||
NODE_API_CALL(env, napi_get_undefined(env, &undefined));
|
||||
NODE_API_CALL(env, napi_get_reference_value(env, cbref, &callback));
|
||||
NODE_API_CALL(env, napi_delete_reference(env, cbref));
|
||||
NODE_API_CALL(env,
|
||||
napi_call_function(env, undefined, callback, 0, nullptr, nullptr));
|
||||
NODE_API_CALL(env, napi_close_handle_scope(env, scope));
|
||||
return &dummy;
|
||||
});
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("SetImmediate", SetImmediateBinding)
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_uv_threadpool_size",
|
||||
"sources": [ "test_uv_threadpool_size.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
if (process.config.variables.node_without_node_options) {
|
||||
common.skip('missing NODE_OPTIONS support');
|
||||
}
|
||||
|
||||
const uvThreadPoolPath = '../../fixtures/dotenv/uv-threadpool.env';
|
||||
|
||||
// Should update UV_THREADPOOL_SIZE
|
||||
const filePath = path.join(__dirname, `./build/${common.buildType}/test_uv_threadpool_size`);
|
||||
const code = `
|
||||
const { test } = require(${JSON.stringify(filePath)});
|
||||
const size = parseInt(process.env.UV_THREADPOOL_SIZE, 10);
|
||||
require('assert').strictEqual(size, 4);
|
||||
test(size);
|
||||
`.trim();
|
||||
const child = spawnSync(
|
||||
process.execPath,
|
||||
[ `--env-file=${uvThreadPoolPath}`, '--eval', code ],
|
||||
{ cwd: __dirname, encoding: 'utf-8' },
|
||||
);
|
||||
assert.strictEqual(child.stderr, '');
|
||||
assert.strictEqual(child.status, 0);
|
||||
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const { test } = require(`./build/${common.buildType}/test_uv_threadpool_size`);
|
||||
|
||||
const uvThreadpoolSize = parseInt(process.env.EXPECTED_UV_THREADPOOL_SIZE ||
|
||||
process.env.UV_THREADPOOL_SIZE, 10) || 4;
|
||||
test(uvThreadpoolSize);
|
||||
@@ -0,0 +1,189 @@
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include <node_api.h>
|
||||
#include <stdlib.h>
|
||||
#include <uv.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
typedef struct {
|
||||
uv_mutex_t mutex;
|
||||
uint32_t threadpool_size;
|
||||
uint32_t n_tasks_started;
|
||||
uint32_t n_tasks_exited;
|
||||
uint32_t n_tasks_finalized;
|
||||
bool observed_saturation;
|
||||
} async_shared_data;
|
||||
|
||||
typedef struct {
|
||||
uint32_t task_id;
|
||||
async_shared_data* shared_data;
|
||||
napi_async_work request;
|
||||
} async_carrier;
|
||||
|
||||
static inline bool all_tasks_started(async_shared_data* d) {
|
||||
assert(d->n_tasks_started <= d->threadpool_size + 1);
|
||||
return d->n_tasks_started == d->threadpool_size + 1;
|
||||
}
|
||||
|
||||
static inline bool all_tasks_exited(async_shared_data* d) {
|
||||
assert(d->n_tasks_exited <= d->n_tasks_started);
|
||||
return all_tasks_started(d) && d->n_tasks_exited == d->n_tasks_started;
|
||||
}
|
||||
|
||||
static inline bool all_tasks_finalized(async_shared_data* d) {
|
||||
assert(d->n_tasks_finalized <= d->n_tasks_exited);
|
||||
return all_tasks_exited(d) && d->n_tasks_finalized == d->n_tasks_exited;
|
||||
}
|
||||
|
||||
static inline bool still_saturating(async_shared_data* d) {
|
||||
return d->n_tasks_started < d->threadpool_size;
|
||||
}
|
||||
|
||||
static inline bool threadpool_saturated(async_shared_data* d) {
|
||||
return d->n_tasks_started == d->threadpool_size && d->n_tasks_exited == 0;
|
||||
}
|
||||
|
||||
static inline bool threadpool_desaturating(async_shared_data* d) {
|
||||
return d->n_tasks_started >= d->threadpool_size && d->n_tasks_exited != 0;
|
||||
}
|
||||
|
||||
static inline void print_info(const char* label, async_carrier* c) {
|
||||
async_shared_data* d = c->shared_data;
|
||||
printf("%s task_id=%u n_tasks_started=%u n_tasks_exited=%u "
|
||||
"n_tasks_finalized=%u observed_saturation=%d\n",
|
||||
label,
|
||||
c->task_id,
|
||||
d->n_tasks_started,
|
||||
d->n_tasks_exited,
|
||||
d->n_tasks_finalized,
|
||||
d->observed_saturation);
|
||||
}
|
||||
|
||||
static void Execute(napi_env env, void* data) {
|
||||
async_carrier* c = (async_carrier*)data;
|
||||
async_shared_data* d = c->shared_data;
|
||||
|
||||
// As long as fewer than threadpool_size async tasks have been started, more
|
||||
// should be started (eventually). Only once that happens should scheduled
|
||||
// async tasks remain queued.
|
||||
uv_mutex_lock(&d->mutex);
|
||||
bool should_be_concurrent = still_saturating(d);
|
||||
d->n_tasks_started++;
|
||||
assert(d->n_tasks_started <= d->threadpool_size + 1);
|
||||
|
||||
print_info("start", c);
|
||||
|
||||
if (should_be_concurrent) {
|
||||
// Wait for the thread pool to be saturated. This is not an elegant way of
|
||||
// doing so, but it really does not matter much here.
|
||||
while (still_saturating(d)) {
|
||||
print_info("waiting", c);
|
||||
uv_mutex_unlock(&d->mutex);
|
||||
uv_sleep(100);
|
||||
uv_mutex_lock(&d->mutex);
|
||||
}
|
||||
|
||||
// One async task will observe that the threadpool is saturated, that is,
|
||||
// that threadpool_size tasks have been started and none have exited yet.
|
||||
// That task will be the first to exit.
|
||||
if (!d->observed_saturation) {
|
||||
assert(threadpool_saturated(d));
|
||||
d->observed_saturation = true;
|
||||
} else {
|
||||
assert(threadpool_saturated(d) || threadpool_desaturating(d));
|
||||
}
|
||||
} else {
|
||||
// If this task is not among the first threadpool_size tasks, it should not
|
||||
// have been started unless other tasks have already finished.
|
||||
assert(threadpool_desaturating(d));
|
||||
}
|
||||
|
||||
print_info("exit", c);
|
||||
|
||||
// Allow other tasks to access the shared data. If the thread pool is actually
|
||||
// larger than threadpool_size, this allows an extraneous task to start, which
|
||||
// will lead to an assertion error.
|
||||
uv_mutex_unlock(&d->mutex);
|
||||
uv_sleep(1000);
|
||||
uv_mutex_lock(&d->mutex);
|
||||
|
||||
d->n_tasks_exited++;
|
||||
uv_mutex_unlock(&d->mutex);
|
||||
}
|
||||
|
||||
static void Complete(napi_env env, napi_status status, void* data) {
|
||||
async_carrier* c = (async_carrier*)data;
|
||||
async_shared_data* d = c->shared_data;
|
||||
|
||||
if (status != napi_ok) {
|
||||
napi_throw_type_error(env, NULL, "Execute callback failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_mutex_lock(&d->mutex);
|
||||
assert(threadpool_desaturating(d));
|
||||
d->n_tasks_finalized++;
|
||||
print_info("finalize", c);
|
||||
if (all_tasks_finalized(d)) {
|
||||
uv_mutex_unlock(&d->mutex);
|
||||
uv_mutex_destroy(&d->mutex);
|
||||
free(d);
|
||||
} else {
|
||||
uv_mutex_unlock(&d->mutex);
|
||||
}
|
||||
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->request));
|
||||
free(c);
|
||||
}
|
||||
|
||||
static napi_value Test(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value argv[1];
|
||||
napi_value this;
|
||||
void* data;
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, &this, &data));
|
||||
NODE_API_ASSERT(env, argc >= 1, "Not enough arguments, expected 1.");
|
||||
|
||||
async_shared_data* shared_data = calloc(1, sizeof(async_shared_data));
|
||||
assert(shared_data != NULL);
|
||||
int ret = uv_mutex_init(&shared_data->mutex);
|
||||
assert(ret == 0);
|
||||
|
||||
napi_valuetype t;
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[0], &t));
|
||||
NODE_API_ASSERT(
|
||||
env, t == napi_number, "Wrong first argument, integer expected.");
|
||||
NODE_API_CALL(
|
||||
env, napi_get_value_uint32(env, argv[0], &shared_data->threadpool_size));
|
||||
|
||||
napi_value resource_name;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_string_utf8(
|
||||
env, "TestResource", NAPI_AUTO_LENGTH, &resource_name));
|
||||
|
||||
for (uint32_t i = 0; i <= shared_data->threadpool_size; i++) {
|
||||
async_carrier* carrier = malloc(sizeof(async_carrier));
|
||||
assert(carrier != NULL);
|
||||
carrier->task_id = i;
|
||||
carrier->shared_data = shared_data;
|
||||
NODE_API_CALL(env,
|
||||
napi_create_async_work(env,
|
||||
NULL,
|
||||
resource_name,
|
||||
Execute,
|
||||
Complete,
|
||||
carrier,
|
||||
&carrier->request));
|
||||
NODE_API_CALL(env, napi_queue_async_work(env, carrier->request));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor desc = DECLARE_NODE_API_PROPERTY("test", Test);
|
||||
NODE_API_CALL(env, napi_define_properties(env, exports, 1, &desc));
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': [ 'test_worker_buffer_callback.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
const { Worker } = require('worker_threads');
|
||||
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
|
||||
const { getFreeCallCount } = require(binding);
|
||||
|
||||
// Test that buffers allocated with a free callback through our APIs are
|
||||
// released when a Worker owning it exits.
|
||||
|
||||
const w = new Worker(`require(${JSON.stringify(binding)})`, { eval: true });
|
||||
|
||||
assert.strictEqual(getFreeCallCount(), 0);
|
||||
w.on('exit', common.mustCall(() => {
|
||||
assert.strictEqual(getFreeCallCount(), 1);
|
||||
}));
|
||||
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const { MessageChannel } = require('worker_threads');
|
||||
const { buffer } = require(`./build/${common.buildType}/binding`);
|
||||
|
||||
// Test that buffers allocated with a free callback through our APIs are not
|
||||
// transferred.
|
||||
|
||||
const { port1 } = new MessageChannel();
|
||||
const origByteLength = buffer.byteLength;
|
||||
assert.throws(() => port1.postMessage(buffer, [buffer]), {
|
||||
code: 25,
|
||||
name: 'DataCloneError',
|
||||
});
|
||||
|
||||
assert.strictEqual(buffer.byteLength, origByteLength);
|
||||
assert.notStrictEqual(buffer.byteLength, 0);
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <node_api.h>
|
||||
#include <assert.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
uint32_t free_call_count = 0;
|
||||
|
||||
napi_value GetFreeCallCount(napi_env env, napi_callback_info info) {
|
||||
napi_value value;
|
||||
NODE_API_CALL(env, napi_create_uint32(env, free_call_count, &value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static void finalize_cb(napi_env env, void* finalize_data, void* hint) {
|
||||
free(finalize_data);
|
||||
free_call_count++;
|
||||
}
|
||||
|
||||
NAPI_MODULE_INIT() {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("getFreeCallCount", GetFreeCallCount)
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
// This is a slight variation on the non-N-API test: We create an ArrayBuffer
|
||||
// rather than a Node.js Buffer, since testing the latter would only test
|
||||
// the same code paths and not the ones specific to N-API.
|
||||
napi_value buffer;
|
||||
|
||||
char* data = malloc(sizeof(char));
|
||||
|
||||
NODE_API_CALL(env, napi_create_external_arraybuffer(
|
||||
env,
|
||||
data,
|
||||
sizeof(char),
|
||||
finalize_cb,
|
||||
NULL,
|
||||
&buffer));
|
||||
|
||||
NODE_API_CALL(env, napi_set_named_property(env, exports, "buffer", buffer));
|
||||
|
||||
return exports;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_worker_terminate",
|
||||
"sources": [ "test_worker_terminate.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
const assert = require('assert');
|
||||
const { Worker, isMainThread, workerData } = require('worker_threads');
|
||||
|
||||
if (isMainThread) {
|
||||
// Load the addon in the main thread first.
|
||||
// This checks that N-API addons can be loaded from multiple contexts
|
||||
// when they are not loaded through NAPI_MODULE().
|
||||
require(`./build/${common.buildType}/test_worker_terminate`);
|
||||
|
||||
const counter = new Int32Array(new SharedArrayBuffer(4));
|
||||
const worker = new Worker(__filename, { workerData: { counter } });
|
||||
worker.on('exit', common.mustCall(() => {
|
||||
assert.strictEqual(counter[0], 1);
|
||||
}));
|
||||
worker.on('error', common.mustNotCall());
|
||||
} else {
|
||||
const { Test } = require(`./build/${common.buildType}/test_worker_terminate`);
|
||||
|
||||
const { counter } = workerData;
|
||||
// Test() tries to call a function and asserts it fails because of a
|
||||
// pending termination exception.
|
||||
Test(() => {
|
||||
Atomics.add(counter, 0, 1);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <stdio.h>
|
||||
#include <node_api.h>
|
||||
#include <assert.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
napi_value Test(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value recv;
|
||||
napi_value argv[1];
|
||||
napi_status status;
|
||||
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, &recv, NULL));
|
||||
NODE_API_ASSERT(env, argc >= 1, "Not enough arguments, expected 1.");
|
||||
|
||||
napi_valuetype t;
|
||||
NODE_API_CALL(env, napi_typeof(env, argv[0], &t));
|
||||
NODE_API_ASSERT(env, t == napi_function,
|
||||
"Wrong first argument, function expected.");
|
||||
|
||||
status = napi_call_function(env, recv, argv[0], 0, NULL, NULL);
|
||||
assert(status == napi_pending_exception);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("Test", Test)
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
// Do not start using NAPI_MODULE_INIT() here, so that we can test
|
||||
// compatibility of Workers with NAPI_MODULE().
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "test_worker_terminate_finalization",
|
||||
"sources": [ "test_worker_terminate_finalization.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
const common = require('../../common');
|
||||
|
||||
// Refs: https://github.com/nodejs/node/issues/34731
|
||||
// Refs: https://github.com/nodejs/node/pull/35777
|
||||
// Refs: https://github.com/nodejs/node/issues/35778
|
||||
|
||||
const { Worker, isMainThread } = require('worker_threads');
|
||||
|
||||
if (isMainThread) {
|
||||
const worker = new Worker(__filename);
|
||||
worker.on('error', common.mustNotCall());
|
||||
} else {
|
||||
const { Test } =
|
||||
require(`./build/${common.buildType}/test_worker_terminate_finalization`);
|
||||
|
||||
// Spin up thread and call add-on create the right sequence
|
||||
// of rerences to hit the case reported in
|
||||
// https://github.com/nodejs/node-addon-api/issues/722
|
||||
// will crash if run under debug and its not possible to
|
||||
// create object in the specific finalizer
|
||||
Test(new Object());
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <stdio.h>
|
||||
#include <node_api.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
#define BUFFER_SIZE 4
|
||||
|
||||
int wrappedNativeData;
|
||||
napi_ref ref;
|
||||
void WrapFinalizer(napi_env env, void* data, void* hint) {
|
||||
uint32_t count;
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_reference_unref(env, ref, &count));
|
||||
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, ref));
|
||||
}
|
||||
|
||||
void BufferFinalizer(napi_env env, void* data, void* hint) {
|
||||
free(hint);
|
||||
}
|
||||
|
||||
napi_value Test(napi_env env, napi_callback_info info) {
|
||||
size_t argc = 1;
|
||||
napi_value argv[1];
|
||||
napi_value result;
|
||||
void* bufferData = malloc(BUFFER_SIZE);
|
||||
|
||||
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
||||
NODE_API_CALL(env,
|
||||
napi_create_external_buffer(
|
||||
env, BUFFER_SIZE, bufferData, BufferFinalizer, bufferData, &result));
|
||||
NODE_API_CALL(env, napi_create_reference(env, result, 1, &ref));
|
||||
NODE_API_CALL(env,
|
||||
napi_wrap(
|
||||
env, argv[0], (void*) &wrappedNativeData, WrapFinalizer, NULL, NULL));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_property_descriptor properties[] = {
|
||||
DECLARE_NODE_API_PROPERTY("Test", Test)
|
||||
};
|
||||
|
||||
NODE_API_CALL(env, napi_define_properties(
|
||||
env, exports, sizeof(properties) / sizeof(*properties), properties));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
// Do not start using NAPI_MODULE_INIT() here, so that we can test
|
||||
// compatibility of Workers with NAPI_MODULE().
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||
6
test/napi/node-napi-tests/test/node-api/testcfg.py
Normal file
6
test/napi/node-napi-tests/test/node-api/testcfg.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import sys, os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import testpy
|
||||
|
||||
def GetConfiguration(context, root):
|
||||
return testpy.AddonTestConfiguration(context, root, 'node-api')
|
||||
Reference in New Issue
Block a user