summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2018-08-01 19:01:14 -0700
committerJames M Snell <jasnell@gmail.com>2018-08-07 07:43:18 -0700
commit080316b32a0e6320e012214c32725099a0248de6 (patch)
tree8f9d6047d172e0e83c1a7a740ce3a6a2e11d26eb
parentfe069cca6a87bcbc7030e8a1e631a81ba8c49580 (diff)
downloadnode-new-080316b32a0e6320e012214c32725099a0248de6.tar.gz
trace_events: add node.promises category, rejection counter
Allow the trace event log to include a count of unhandled rejections and handled after rejections. This is useful primarily as a quick check to see if rejections may be going unhandled (and unnoticed in a process). PR-URL: https://github.com/nodejs/node/pull/22124 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
-rw-r--r--doc/api/tracing.md4
-rw-r--r--src/bootstrapper.cc15
-rw-r--r--test/parallel/test-trace-event-promises.js48
3 files changed, 66 insertions, 1 deletions
diff --git a/doc/api/tracing.md b/doc/api/tracing.md
index ca91b8aac7..e067760643 100644
--- a/doc/api/tracing.md
+++ b/doc/api/tracing.md
@@ -18,12 +18,14 @@ The available categories are:
The [`async_hooks`] events have a unique `asyncId` and a special `triggerId`
`triggerAsyncId` property.
* `node.bootstrap` - Enables capture of Node.js bootstrap milestones.
+* `node.fs.sync` - Enables capture of trace data for file system sync methods.
* `node.perf` - Enables capture of [Performance API] measurements.
* `node.perf.usertiming` - Enables capture of only Performance API User Timing
measures and marks.
* `node.perf.timerify` - Enables capture of only Performance API timerify
measurements.
-* `node.fs.sync` - Enables capture of trace data for file system sync methods.
+* `node.promises.rejections` - Enables capture of trace data tracking the number
+ of unhandled Promise rejections and handled-after-rejections.
* `node.vm.script` - Enables capture of trace data for the `vm` module's
`runInNewContext()`, `runInContext()`, and `runInThisContext()` methods.
* `v8` - The [V8] events are GC, compiling, and execution related.
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index 5f40d62a82..94bb941377 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -3,6 +3,8 @@
#include "node_internals.h"
#include "v8.h"
+#include <atomic>
+
namespace node {
using v8::Array;
@@ -51,6 +53,9 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
}
void PromiseRejectCallback(PromiseRejectMessage message) {
+ static std::atomic<uint64_t> unhandledRejections{0};
+ static std::atomic<uint64_t> rejectionsHandledAfter{0};
+
Local<Promise> promise = message.GetPromise();
Isolate* isolate = promise->GetIsolate();
PromiseRejectEvent event = message.GetEvent();
@@ -65,13 +70,23 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
if (value.IsEmpty())
value = Undefined(isolate);
+
+ unhandledRejections++;
} else if (event == v8::kPromiseHandlerAddedAfterReject) {
callback = env->promise_reject_handled_function();
value = Undefined(isolate);
+
+ rejectionsHandledAfter++;
} else {
return;
}
+ TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
+ "rejections",
+ "unhandled", unhandledRejections,
+ "handledAfter", rejectionsHandledAfter);
+
+
Local<Value> args[] = { promise, value };
MaybeLocal<Value> ret = callback->Call(env->context(),
Undefined(isolate),
diff --git a/test/parallel/test-trace-event-promises.js b/test/parallel/test-trace-event-promises.js
new file mode 100644
index 0000000000..69d35f25ec
--- /dev/null
+++ b/test/parallel/test-trace-event-promises.js
@@ -0,0 +1,48 @@
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+const cp = require('child_process');
+const path = require('path');
+const fs = require('fs');
+const tmpdir = require('../common/tmpdir');
+
+common.disableCrashOnUnhandledRejection();
+
+if (!common.isMainThread)
+ common.skip('process.chdir is not available in Workers');
+
+if (process.argv[2] === 'child') {
+ const p = Promise.reject(1); // Handled later
+ Promise.reject(2); // Unhandled
+ setImmediate(() => {
+ p.catch(() => { /* intentional noop */ });
+ });
+} else {
+ tmpdir.refresh();
+ process.chdir(tmpdir.path);
+
+ const proc = cp.fork(__filename,
+ [ 'child' ], {
+ execArgv: [
+ '--no-warnings',
+ '--trace-event-categories',
+ 'node.promises.rejections'
+ ]
+ });
+
+ proc.once('exit', common.mustCall(() => {
+ const file = path.join(tmpdir.path, 'node_trace.1.log');
+
+ assert(common.fileExists(file));
+ fs.readFile(file, common.mustCall((err, data) => {
+ const traces = JSON.parse(data.toString()).traceEvents
+ .filter((trace) => trace.cat !== '__metadata');
+ traces.forEach((trace) => {
+ assert.strictEqual(trace.pid, proc.pid);
+ assert.strictEqual(trace.name, 'rejections');
+ assert(trace.args.unhandled <= 2);
+ assert(trace.args.handledAfter <= 1);
+ });
+ }));
+ }));
+}