summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--benchmark/worker/atomics-wait.js15
-rw-r--r--doc/api/cli.md30
-rw-r--r--doc/node.14
-rw-r--r--src/node.cc45
-rw-r--r--src/node_options.cc4
-rw-r--r--src/node_options.h1
-rw-r--r--test/parallel/test-trace-atomics-wait.js79
7 files changed, 178 insertions, 0 deletions
diff --git a/benchmark/worker/atomics-wait.js b/benchmark/worker/atomics-wait.js
new file mode 100644
index 0000000000..a771b18137
--- /dev/null
+++ b/benchmark/worker/atomics-wait.js
@@ -0,0 +1,15 @@
+'use strict';
+/* global SharedArrayBuffer */
+
+const common = require('../common.js');
+const bench = common.createBenchmark(main, {
+ n: [1e7]
+});
+
+function main({ n }) {
+ const i32arr = new Int32Array(new SharedArrayBuffer(4));
+ bench.start();
+ for (let i = 0; i < n; i++)
+ Atomics.wait(i32arr, 0, 1); // Will return immediately.
+ bench.end(n);
+}
diff --git a/doc/api/cli.md b/doc/api/cli.md
index e6310b279f..82e2f674a8 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -816,6 +816,33 @@ added: v12.0.0
Set default [`tls.DEFAULT_MIN_VERSION`][] to 'TLSv1.3'. Use to disable support
for TLSv1.2, which is not as secure as TLSv1.3.
+### `--trace-atomics-wait`
+<!-- YAML
+added: REPLACEME
+-->
+
+Print short summaries of calls to [`Atomics.wait()`][] to stderr.
+The output could look like this:
+
+```text
+(node:15701) [Thread 0] Atomics.wait(<address> + 0, 1, inf) started
+(node:15701) [Thread 0] Atomics.wait(<address> + 0, 1, inf) did not wait because the values mismatched
+(node:15701) [Thread 0] Atomics.wait(<address> + 0, 0, 10) started
+(node:15701) [Thread 0] Atomics.wait(<address> + 0, 0, 10) timed out
+(node:15701) [Thread 0] Atomics.wait(<address> + 4, 0, inf) started
+(node:15701) [Thread 1] Atomics.wait(<address> + 4, -1, inf) started
+(node:15701) [Thread 0] Atomics.wait(<address> + 4, 0, inf) was woken up by another thread
+(node:15701) [Thread 1] Atomics.wait(<address> + 4, -1, inf) was woken up by another thread
+```
+
+The fields here correspond to:
+
+* The thread id as given by [`worker_threads.threadId`][]
+* The base address of the `SharedArrayBuffer` in question, as well as the
+ byte offset corresponding to the index passed to `Atomics.wait()`
+* The expected value that was passed to `Atomics.wait()`
+* The timeout passed to `Atomics.wait`
+
### `--trace-deprecation`
<!-- YAML
added: v0.8.0
@@ -1205,6 +1232,7 @@ Node.js options that are allowed are:
* `--tls-min-v1.1`
* `--tls-min-v1.2`
* `--tls-min-v1.3`
+* `--trace-atomics-wait`
* `--trace-deprecation`
* `--trace-event-categories`
* `--trace-event-file-pattern`
@@ -1474,12 +1502,14 @@ $ node --max-old-space-size=1536 index.js
```
[`--openssl-config`]: #cli_openssl_config_file
+[`Atomics.wait()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
[`Buffer`]: buffer.html#buffer_class_buffer
[`SlowBuffer`]: buffer.html#buffer_class_slowbuffer
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
[`tls.DEFAULT_MAX_VERSION`]: tls.html#tls_tls_default_max_version
[`tls.DEFAULT_MIN_VERSION`]: tls.html#tls_tls_default_min_version
[`unhandledRejection`]: process.html#process_event_unhandledrejection
+[`worker_threads.threadId`]: worker_threads.html##worker_threads_worker_threadid
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
[REPL]: repl.html
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
diff --git a/doc/node.1 b/doc/node.1
index ec2642170a..278061424c 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -363,6 +363,10 @@ but the option is supported for compatibility with older Node.js versions.
Set default minVersion to 'TLSv1.3'. Use to disable support for TLSv1.2 in
favour of TLSv1.3, which is more secure.
.
+.It Fl -trace-atomics-wait
+Print short summaries of calls to
+.Sy Atomics.wait() .
+.
.It Fl -trace-deprecation
Print stack traces for deprecations.
.
diff --git a/src/node.cc b/src/node.cc
index 1fed8193e7..66a4a368c1 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -227,11 +227,56 @@ int Environment::InitializeInspector(
}
#endif // HAVE_INSPECTOR && NODE_USE_V8_PLATFORM
+#define ATOMIC_WAIT_EVENTS(V) \
+ V(kStartWait, "started") \
+ V(kWokenUp, "was woken up by another thread") \
+ V(kTimedOut, "timed out") \
+ V(kTerminatedExecution, "was stopped by terminated execution") \
+ V(kAPIStopped, "was stopped through the embedder API") \
+ V(kNotEqual, "did not wait because the values mismatched") \
+
+static void AtomicsWaitCallback(Isolate::AtomicsWaitEvent event,
+ Local<v8::SharedArrayBuffer> array_buffer,
+ size_t offset_in_bytes, int64_t value,
+ double timeout_in_ms,
+ Isolate::AtomicsWaitWakeHandle* stop_handle,
+ void* data) {
+ Environment* env = static_cast<Environment*>(data);
+
+ const char* message;
+ switch (event) {
+#define V(key, msg) \
+ case Isolate::AtomicsWaitEvent::key: \
+ message = msg; \
+ break;
+ ATOMIC_WAIT_EVENTS(V)
+#undef V
+ }
+
+ fprintf(stderr,
+ "(node:%d) [Thread %" PRIu64 "] Atomics.wait(%p + %zx, %" PRId64
+ ", %.f) %s\n",
+ static_cast<int>(uv_os_getpid()),
+ env->thread_id(),
+ array_buffer->GetBackingStore()->Data(),
+ offset_in_bytes,
+ value,
+ timeout_in_ms,
+ message);
+}
+
void Environment::InitializeDiagnostics() {
isolate_->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
Environment::BuildEmbedderGraph, this);
if (options_->trace_uncaught)
isolate_->SetCaptureStackTraceForUncaughtExceptions(true);
+ if (options_->trace_atomics_wait) {
+ isolate_->SetAtomicsWaitCallback(AtomicsWaitCallback, this);
+ AddCleanupHook([](void* data) {
+ Environment* env = static_cast<Environment*>(data);
+ env->isolate()->SetAtomicsWaitCallback(nullptr, nullptr);
+ }, this);
+ }
#if defined HAVE_DTRACE || defined HAVE_ETW
InitDTrace(this);
diff --git a/src/node_options.cc b/src/node_options.cc
index b3e7f403b0..013c3ef825 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -435,6 +435,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"throw an exception on deprecations",
&EnvironmentOptions::throw_deprecation,
kAllowedInEnvironment);
+ AddOption("--trace-atomics-wait",
+ "trace Atomics.wait() operations",
+ &EnvironmentOptions::trace_atomics_wait,
+ kAllowedInEnvironment);
AddOption("--trace-deprecation",
"show stack traces on deprecations",
&EnvironmentOptions::trace_deprecation,
diff --git a/src/node_options.h b/src/node_options.h
index bbe5617cdc..54710e4877 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -140,6 +140,7 @@ class EnvironmentOptions : public Options {
std::string redirect_warnings;
bool test_udp_no_try_send = false;
bool throw_deprecation = false;
+ bool trace_atomics_wait = false;
bool trace_deprecation = false;
bool trace_exit = false;
bool trace_sync_io = false;
diff --git a/test/parallel/test-trace-atomics-wait.js b/test/parallel/test-trace-atomics-wait.js
new file mode 100644
index 0000000000..4681b77b59
--- /dev/null
+++ b/test/parallel/test-trace-atomics-wait.js
@@ -0,0 +1,79 @@
+'use strict';
+require('../common');
+const assert = require('assert');
+const child_process = require('child_process');
+const { Worker } = require('worker_threads');
+
+if (process.argv[2] === 'child') {
+ const i32arr = new Int32Array(new SharedArrayBuffer(8));
+ assert.strictEqual(Atomics.wait(i32arr, 0, 1), 'not-equal');
+ assert.strictEqual(Atomics.wait(i32arr, 0, 0, 10), 'timed-out');
+
+ new Worker(`
+ const i32arr = require('worker_threads').workerData;
+ Atomics.store(i32arr, 1, -1);
+ Atomics.notify(i32arr, 1);
+ Atomics.wait(i32arr, 1, -1);
+ `, { eval: true, workerData: i32arr });
+
+ Atomics.wait(i32arr, 1, 0);
+ assert.strictEqual(Atomics.load(i32arr, 1), -1);
+ Atomics.store(i32arr, 1, 0);
+ Atomics.notify(i32arr, 1);
+ return;
+}
+
+const proc = child_process.spawnSync(
+ process.execPath,
+ [ '--trace-atomics-wait', __filename, 'child' ],
+ { encoding: 'utf8', stdio: [ 'inherit', 'inherit', 'pipe' ] });
+
+if (proc.status !== 0) console.log(proc);
+assert.strictEqual(proc.status, 0);
+
+const SABAddress = proc.stderr.match(/Atomics\.wait\((?<SAB>.+) \+/).groups.SAB;
+const actualTimeline = proc.stderr
+ .replace(new RegExp(SABAddress, 'g'), '<address>')
+ .replace(new RegExp(`\\(node:${proc.pid}\\) `, 'g'), '')
+ .replace(/\binf(inity)?\b/gi, 'inf')
+ .replace(/\r/g, '')
+ .trim();
+console.log('+++ normalized stdout +++');
+console.log(actualTimeline);
+console.log('--- normalized stdout ---');
+
+const begin =
+`[Thread 0] Atomics.wait(<address> + 0, 1, inf) started
+[Thread 0] Atomics.wait(<address> + 0, 1, inf) did not wait because the \
+values mismatched
+[Thread 0] Atomics.wait(<address> + 0, 0, 10) started
+[Thread 0] Atomics.wait(<address> + 0, 0, 10) timed out`;
+
+const expectedTimelines = [
+ `${begin}
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) started
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) started
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) was woken up by another thread
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) was woken up by another thread`,
+ `${begin}
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) started
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) was woken up by another thread
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) started
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) did not wait because the \
+values mismatched`,
+ `${begin}
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) started
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) started
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) was woken up by another thread
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) did not wait because the \
+values mismatched`,
+ `${begin}
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) started
+[Thread 0] Atomics.wait(<address> + 4, 0, inf) did not wait because the \
+values mismatched
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) started
+[Thread 1] Atomics.wait(<address> + 4, -1, inf) did not wait because the \
+values mismatched`
+];
+
+assert(expectedTimelines.includes(actualTimeline));