summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schulhof <gabriel.schulhof@intel.com>2018-07-18 09:33:51 -0400
committerRod Vagg <rod@vagg.org>2019-02-28 23:40:10 +1100
commit367505940ab09369262295cf21d3ebe08b943ad5 (patch)
tree580c1857c5e60a692698882cbf81593e6f8e5e20 /test
parentc5a11dc58e48775dc3c510fed7fc858d54bcbd3a (diff)
downloadnode-new-367505940ab09369262295cf21d3ebe08b943ad5.tar.gz
n-api: guard against cond null dereference
A condition variable is only created by the thread-safe function if the queue size is set to something larger than zero. This adds null-checks around the condition variable and tests for the case where the queue size is zero. Fixes: https://github.com/nodejs/help/issues/1387 PR-URL: https://github.com/nodejs/node/pull/21871 Backport-PR-URL: https://github.com/nodejs/node/pull/25002 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Diffstat (limited to 'test')
-rw-r--r--test/addons-napi/test_threadsafe_function/binding.c39
-rw-r--r--test/addons-napi/test_threadsafe_function/test.js84
2 files changed, 97 insertions, 26 deletions
diff --git a/test/addons-napi/test_threadsafe_function/binding.c b/test/addons-napi/test_threadsafe_function/binding.c
index 551705b1f2..354012a288 100644
--- a/test/addons-napi/test_threadsafe_function/binding.c
+++ b/test/addons-napi/test_threadsafe_function/binding.c
@@ -9,6 +9,7 @@
#include "../common.h"
#define ARRAY_LENGTH 10
+#define MAX_QUEUE_SIZE 2
static uv_thread_t uv_threads[2];
static napi_threadsafe_function ts_fn;
@@ -18,6 +19,7 @@ typedef struct {
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;
@@ -71,6 +73,12 @@ static void data_source_thread(void* data) {
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) {
+ // 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;
@@ -167,8 +175,8 @@ static napi_value StartThreadInternal(napi_env env,
napi_callback_info info,
napi_threadsafe_function_call_js cb,
bool block_on_full) {
- size_t argc = 3;
- napi_value argv[3];
+ size_t argc = 4;
+ napi_value argv[4];
ts_info.block_on_full =
(block_on_full ? napi_tsfn_blocking : napi_tsfn_nonblocking);
@@ -178,8 +186,18 @@ static napi_value StartThreadInternal(napi_env env,
napi_value async_name;
NAPI_CALL(env, napi_create_string_utf8(env, "N-API Thread-safe Function Test",
NAPI_AUTO_LENGTH, &async_name));
- NAPI_CALL(env, napi_create_threadsafe_function(env, argv[0], NULL, async_name,
- 2, 2, uv_threads, join_the_threads, &ts_info, cb, &ts_fn));
+ NAPI_CALL(env, napi_get_value_uint32(env, argv[3], &ts_info.max_queue_size));
+ NAPI_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;
NAPI_CALL(env, napi_get_value_bool(env, argv[1], &abort));
ts_info.abort = abort ? napi_tsfn_abort : napi_tsfn_release;
@@ -224,8 +242,9 @@ static napi_value Init(napi_env env, napi_value exports) {
for (index = 0; index < ARRAY_LENGTH; index++) {
ints[index] = index;
}
- napi_value js_array_length;
+ 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[] = {
{
@@ -238,6 +257,16 @@ static napi_value Init(napi_env env, napi_value exports) {
napi_enumerable,
NULL
},
+ {
+ "MAX_QUEUE_SIZE",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ js_max_queue_size,
+ napi_enumerable,
+ NULL
+ },
DECLARE_NAPI_PROPERTY("StartThread", StartThread),
DECLARE_NAPI_PROPERTY("StartThreadNoNative", StartThreadNoNative),
DECLARE_NAPI_PROPERTY("StartThreadNonblocking", StartThreadNonblocking),
diff --git a/test/addons-napi/test_threadsafe_function/test.js b/test/addons-napi/test_threadsafe_function/test.js
index 8d8a6d9d8c..ac2549a8c0 100644
--- a/test/addons-napi/test_threadsafe_function/test.js
+++ b/test/addons-napi/test_threadsafe_function/test.js
@@ -25,7 +25,7 @@ if (process.argv[2] === 'child') {
if (callCount === 2) {
binding.Unref();
}
- }, false /* abort */, true /* launchSecondary */);
+ }, 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.
@@ -37,6 +37,7 @@ function testWithJSMarshaller({
threadStarter,
quitAfter,
abort,
+ maxQueueSize,
launchSecondary }) {
return new Promise((resolve) => {
const array = [];
@@ -49,7 +50,7 @@ function testWithJSMarshaller({
}), !!abort);
});
}
- }, !!abort, !!launchSecondary);
+ }, !!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.
@@ -59,6 +60,24 @@ function testWithJSMarshaller({
});
}
+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() {
@@ -73,13 +92,23 @@ new Promise(function testWithoutJSMarshaller(resolve) {
}), false);
});
}
- }, false /* abort */, false /* launchSecondary */);
+ }, 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 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))
@@ -88,6 +117,7 @@ new Promise(function testWithoutJSMarshaller(resolve) {
// Quit after it's done.
.then(() => testWithJSMarshaller({
threadStarter: 'StartThreadNonblocking',
+ maxQueueSize: binding.MAX_QUEUE_SIZE,
quitAfter: binding.ARRAY_LENGTH
}))
.then((result) => assert.deepStrictEqual(result, expectedArray))
@@ -96,6 +126,16 @@ new Promise(function testWithoutJSMarshaller(resolve) {
// 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))
@@ -104,6 +144,7 @@ new Promise(function testWithoutJSMarshaller(resolve) {
// Quit early, but let the thread finish.
.then(() => testWithJSMarshaller({
threadStarter: 'StartThreadNonblocking',
+ maxQueueSize: binding.MAX_QUEUE_SIZE,
quitAfter: 1
}))
.then((result) => assert.deepStrictEqual(result, expectedArray))
@@ -114,6 +155,7 @@ new Promise(function testWithoutJSMarshaller(resolve) {
.then(() => testWithJSMarshaller({
threadStarter: 'StartThread',
quitAfter: 1,
+ maxQueueSize: binding.MAX_QUEUE_SIZE,
launchSecondary: true
}))
.then((result) => assert.deepStrictEqual(result, expectedArray))
@@ -124,15 +166,27 @@ new Promise(function testWithoutJSMarshaller(resolve) {
.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 and aborting.
+// 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))
@@ -142,25 +196,13 @@ new Promise(function testWithoutJSMarshaller(resolve) {
.then(() => testWithJSMarshaller({
threadStarter: 'StartThreadNonblocking',
quitAfter: 1,
+ maxQueueSize: binding.MAX_QUEUE_SIZE,
abort: true
}))
.then((result) => assert.strictEqual(result.indexOf(0), -1))
// Start a child process to test rapid teardown
-.then(() => {
- return new Promise((resolve, reject) => {
- let output = '';
- const child = fork(__filename, ['child'], {
- 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));
+.then(() => testUnref(binding.MAX_QUEUE_SIZE))
+
+// Start a child process with an infinite queue to test rapid teardown
+.then(() => testUnref(0));