Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
db5ee23
test: update OpenSSL 3.0.0-alpha15 error messages
danbev Apr 28, 2021
b51b4fe
test: skip tests for openssl-3.0.0-alpha15
danbev Apr 28, 2021
f31a611
deps: V8: cherry-pick 530080c44af2
Apr 30, 2021
e64ebac
test: fix flaky inspector-cli tests when breakpionts are restored
Trott Apr 27, 2021
727c2bc
test: move test-net-connect-econnrefused from pummel to sequential
Trott Apr 29, 2021
38644d6
doc: use sentence case in headers in src/crypto/README.md
Trott May 3, 2021
9e10e1a
doc: corrected workload name as per the latest VS Installer
MrJithil May 1, 2021
e0118f3
doc: remove extraneous dash from flag prefix
rhcarvalho May 4, 2021
03b4a3a
build: compile with -std=gnu++14
RaisinTen May 2, 2021
1514885
tools: make GH Actions workflows work if default branch is not master
aduh95 May 3, 2021
89e1dac
test: run message and pseudo-tty tests in parallel
richardlau May 1, 2021
e9d4c85
doc: use AIX instead of Aix in fs.md
Trott May 4, 2021
e59131d
doc: fix broken AHAFS link in fs doc
Trott May 4, 2021
c0f0c9a
typings: add JSDoc typings for readline
VoltrexKeyva Apr 15, 2021
ae9128e
doc: clarify DiffieHellmanGroup class docs
Linkgoron Apr 22, 2021
14a2a00
node-api: faster threadsafe_function
indutny May 1, 2021
6622650
test: increase coverage for Blob
Lxxyx May 3, 2021
5b44107
test: increase coverage for AbortController
Lxxyx May 3, 2021
f2f768f
test: complete coverage of querystring
pd4d10 May 3, 2021
4c70e42
doc: use `HEAD` instead of `master` for links
aduh95 May 3, 2021
1d9fd49
doc: fix code example in ecdh.setPublicKey()
JBaczuk May 4, 2021
cf4dc80
lib: make `IterableWeakMap` safe to iterate
aduh95 May 3, 2021
352a600
debugger: rename inspector-cli test module to debugger
Trott May 4, 2021
070ee4b
Revert "worker: remove `ERR_CLOSED_MESSAGE_PORT`"
juanarbol May 3, 2021
bfec80f
src: make workers messaging more resilient
juanarbol May 3, 2021
d9867b9
debugger: wrap lines longer than 80 chars
Trott May 4, 2021
e79f540
debugger: avoid non-ASCII char in code file
Trott May 4, 2021
11dd9a6
debugger: disable only the lint rules required by current file state
Trott May 4, 2021
47080bc
fs: use `assert` in `fsCall` argument checking
pd4d10 May 3, 2021
95db7d5
test: improve fs coverage
pd4d10 May 3, 2021
827ae05
deps: V8: backport c0fceaa0669b
Apr 21, 2021
1a104ba
deps: V8: cherry-pick 272445f10927
Apr 22, 2021
043b1aa
deps: V8: cherry-pick 5f4413194480
Apr 27, 2021
a84e9b3
deps: V8: cherry-pick 4c074516397b
Apr 29, 2021
ab29872
deps: V8: cherry-pick fa4cb172cde2
May 3, 2021
36b9485
async_hooks: use new v8::Context PromiseHook API
Qard Dec 4, 2020
16a6c8d
debugger: refactor to use internal modules
aduh95 May 5, 2021
56bf6c1
doc: typo stats() should be stat(); clarity
700software May 4, 2021
178fe21
test: increase coverage for Histogram
Lxxyx May 5, 2021
ec2dbfb
deps: patch V8 to 9.0.257.25
targos May 5, 2021
48b557e
buffer: remove unreachable code
pd4d10 May 4, 2021
10aaf30
build: add missing torque output sources
richardlau May 6, 2021
8f78c66
test: call functions internally
VoltrexKeyva May 5, 2021
90b640e
meta: add v8 team
gengjiawen May 6, 2021
dcae032
buffer: remove TODOs in `atob` / `btoa`
XadillaX May 5, 2021
16b2fb4
test: increase coverage for repl
Lxxyx May 5, 2021
4001dd2
dgram: extract cluster lazy loading method to make it testable
pd4d10 May 6, 2021
24472d9
test,repl: fix tests when inspector is disabled
aduh95 May 6, 2021
d3de0ef
doc: fix fs.openSync() signature
lpinca May 8, 2021
3bb12db
build: remove dependency on `distutils.spawn`
richardlau May 5, 2021
e876118
fs: fix error when writing buffers > INT32_MAX
zbjornson May 5, 2021
4c49027
module: add support for `URL` to `import.meta.resolve`
aduh95 May 7, 2021
b0df28d
build: add workaround for V8 builds
richardlau May 11, 2021
be4b3a4
path: inline conditions
VoltrexKeyva May 9, 2021
5c71f49
deps: upgrade npm to 7.12.1
ruyadorno May 10, 2021
c182198
process: add `'worker'` event
jasnell May 12, 2021
ec8ab22
tools: add `Worker` to type-parser
jasnell May 12, 2021
3d8b8e1
http: refactor to remove redundant argument of _deferToConnect
pd4d10 May 8, 2021
3d0fad3
test: improve coverage of lib/os.js
pd4d10 May 12, 2021
4296591
build,src,test,doc: enable FIPS for OpenSSL 3.0
danbev Apr 26, 2021
36bb724
debugger: refactor `inspect_repl` to use primordials
aduh95 Apr 27, 2021
5a0b521
test: improve coverage of lib/_http_client.js
pd4d10 May 8, 2021
6620a31
doc: add ESM code examples in url.md
aduh95 May 12, 2021
c4b161c
doc: change color of doctag on night mode
Ayase-252 May 12, 2021
1765e32
doc: update contact info
gabrielschulhof May 14, 2021
608d0e1
deps: upgrade npm to 7.13.0
ruyadorno May 13, 2021
e2b8454
repl: fix Ctrl+C on top level await
aduh95 May 12, 2021
6b409cf
src: remove extra semi after member fn
codebytere May 14, 2021
a028805
tools: update ESLint to 7.26.0
cjihrig May 8, 2021
36bb8da
crypto: forbid NODE-ED25519 and NODE-ED448 "raw" key export
panva May 13, 2021
fbf02e3
util: add util.types.isKeyObject and util.types.isCryptoKey
panva May 10, 2021
524a9d6
src: update cares_wrap OpenBSD defines
addaleax May 13, 2021
c0deeea
lib: support setting process.env.TZ on windows
jasnell May 11, 2021
5c38a55
doc: clarify synchronous blocking of Worker stdio
jasnell May 12, 2021
3a5856c
fs: fix async iterator partial writes
Linkgoron May 9, 2021
347b9f2
errors: remove input from ERR_INVALID_URL message
moander May 9, 2021
0100a3b
doc: add documentation for fs.WriteStream.close()
ihiteshsharma May 9, 2021
5b25fbe
test: set common.bits to 64 for riscv64
andreas-schwab May 9, 2021
8590c15
doc: update abort signal in fs promise api example
MoritzKn May 13, 2021
5b40e2f
events: use nullish coalencing operator
VoltrexKeyva Apr 21, 2021
8eea317
test: stream.finished detects a destroyed IncomingMessage
Linkgoron May 12, 2021
ff1b432
test: refactor `test-readline-interface` to be shorter
juanarbol May 14, 2021
a0dc194
doc: document buffer.kStringMaxLength
tniessen May 14, 2021
718ad10
src: fix fatal errors when a current isolate not exist
legendecas May 10, 2021
267a84f
tools: remove redundant v8 config
gengjiawen May 6, 2021
6e69f84
2021-05-19, Version 16.2.0 (Current)
targos May 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
node-api: faster threadsafe_function
Invoke threadsafe_function during the same tick and avoid marshalling
costs between threads and/or churning event loop if either:

1. There's a queued call already
2. `Push()` is called while the main thread was running
   threadsafe_function

PR-URL: #38506
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
indutny authored and targos committed May 17, 2021
commit 14a2a00cda419b0e9d3ea430cb926d7e506b034b
88 changes: 55 additions & 33 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "tracing/traced_value.h"
#include "util-inl.h"

#include <atomic>
#include <memory>

struct node_napi_env__ : public napi_env__ {
Expand Down Expand Up @@ -137,6 +138,7 @@ class ThreadSafeFunction : public node::AsyncResource {
*v8::String::Utf8Value(env_->isolate, name)),
thread_count(thread_count_),
is_closing(false),
dispatch_state(kDispatchIdle),
context(context_),
max_queue_size(max_queue_size_),
env(env_),
Expand Down Expand Up @@ -176,10 +178,8 @@ class ThreadSafeFunction : public node::AsyncResource {
return napi_closing;
}
} else {
if (uv_async_send(&async) != 0) {
return napi_generic_failure;
}
queue.push(data);
Send();
return napi_ok;
}
}
Expand Down Expand Up @@ -211,9 +211,7 @@ class ThreadSafeFunction : public node::AsyncResource {
if (is_closing && max_queue_size > 0) {
cond->Signal(lock);
}
if (uv_async_send(&async) != 0) {
return napi_generic_failure;
}
Send();
}
}

Expand All @@ -238,7 +236,6 @@ class ThreadSafeFunction : public node::AsyncResource {
cond = std::make_unique<node::ConditionVariable>();
}
if (max_queue_size == 0 || cond) {
CHECK_EQ(0, uv_idle_init(loop, &idle));
return napi_ok;
}

Expand All @@ -263,21 +260,46 @@ class ThreadSafeFunction : public node::AsyncResource {

napi_status Unref() {
uv_unref(reinterpret_cast<uv_handle_t*>(&async));
uv_unref(reinterpret_cast<uv_handle_t*>(&idle));

return napi_ok;
}

napi_status Ref() {
uv_ref(reinterpret_cast<uv_handle_t*>(&async));
uv_ref(reinterpret_cast<uv_handle_t*>(&idle));

return napi_ok;
}

void DispatchOne() {
inline void* Context() {
return context;
}

protected:
void Dispatch() {
bool has_more = true;

// Limit maximum synchronous iteration count to prevent event loop
// starvation. See `src/node_messaging.cc` for an inspiration.
unsigned int iterations_left = kMaxIterationCount;
while (has_more && --iterations_left != 0) {
dispatch_state = kDispatchRunning;
has_more = DispatchOne();

// Send() was called while we were executing the JS function
if (dispatch_state.exchange(kDispatchIdle) != kDispatchRunning) {
has_more = true;
}
}

if (has_more) {
Send();
}
}

bool DispatchOne() {
void* data = nullptr;
bool popped_value = false;
bool has_more = false;

{
node::Mutex::ScopedLock lock(this->mutex);
Expand All @@ -302,9 +324,9 @@ class ThreadSafeFunction : public node::AsyncResource {
cond->Signal(lock);
}
CloseHandlesAndMaybeDelete();
} else {
CHECK_EQ(0, uv_idle_stop(&idle));
}
} else {
has_more = true;
}
}
}
Expand All @@ -322,6 +344,8 @@ class ThreadSafeFunction : public node::AsyncResource {
call_js_cb(env, js_callback, context, data);
});
}

return has_more;
}

void Finalize() {
Expand All @@ -335,10 +359,6 @@ class ThreadSafeFunction : public node::AsyncResource {
EmptyQueueAndDelete();
}

inline void* Context() {
return context;
}

void CloseHandlesAndMaybeDelete(bool set_closing = false) {
v8::HandleScope scope(env->isolate);
if (set_closing) {
Expand All @@ -358,18 +378,20 @@ class ThreadSafeFunction : public node::AsyncResource {
ThreadSafeFunction* ts_fn =
node::ContainerOf(&ThreadSafeFunction::async,
reinterpret_cast<uv_async_t*>(handle));
v8::HandleScope scope(ts_fn->env->isolate);
ts_fn->env->node_env()->CloseHandle(
reinterpret_cast<uv_handle_t*>(&ts_fn->idle),
[](uv_handle_t* handle) -> void {
ThreadSafeFunction* ts_fn =
node::ContainerOf(&ThreadSafeFunction::idle,
reinterpret_cast<uv_idle_t*>(handle));
ts_fn->Finalize();
});
ts_fn->Finalize();
});
}

void Send() {
// Ask currently running Dispatch() to make one more iteration
unsigned char current_state = dispatch_state.fetch_or(kDispatchPending);
if ((current_state & kDispatchRunning) == kDispatchRunning) {
return;
}

CHECK_EQ(0, uv_async_send(&async));
}

// Default way of calling into JavaScript. Used when ThreadSafeFunction is
// without a call_js_cb_.
static void CallJs(napi_env env, napi_value cb, void* context, void* data) {
Expand All @@ -393,16 +415,10 @@ class ThreadSafeFunction : public node::AsyncResource {
}
}

static void IdleCb(uv_idle_t* idle) {
ThreadSafeFunction* ts_fn =
node::ContainerOf(&ThreadSafeFunction::idle, idle);
ts_fn->DispatchOne();
}

static void AsyncCb(uv_async_t* async) {
ThreadSafeFunction* ts_fn =
node::ContainerOf(&ThreadSafeFunction::async, async);
CHECK_EQ(0, uv_idle_start(&ts_fn->idle, IdleCb));
ts_fn->Dispatch();
}

static void Cleanup(void* data) {
Expand All @@ -411,14 +427,20 @@ class ThreadSafeFunction : public node::AsyncResource {
}

private:
static const unsigned char kDispatchIdle = 0;
static const unsigned char kDispatchRunning = 1 << 0;
static const unsigned char kDispatchPending = 1 << 1;

static const unsigned int kMaxIterationCount = 1000;

// These are variables protected by the mutex.
node::Mutex mutex;
std::unique_ptr<node::ConditionVariable> cond;
std::queue<void*> queue;
uv_async_t async;
uv_idle_t idle;
size_t thread_count;
bool is_closing;
std::atomic_uchar dispatch_state;

// These are variables set once, upon creation, and then never again, which
// means we don't need the mutex to read them.
Expand Down
4 changes: 2 additions & 2 deletions test/node-api/test_threadsafe_function/binding.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <node_api.h>
#include "../../js-native-api/common.h"

#define ARRAY_LENGTH 10
#define ARRAY_LENGTH 10000
#define MAX_QUEUE_SIZE 2

static uv_thread_t uv_threads[2];
Expand Down Expand Up @@ -72,7 +72,7 @@ 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) {
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();
Expand Down
9 changes: 9 additions & 0 deletions test/node-api/test_threadsafe_function/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ new Promise(function testWithoutJSMarshaller(resolve) {
}))
.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))

Expand Down