Skip to content

Commit 9ca9452

Browse files
committed
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).
1 parent a4c1cf5 commit 9ca9452

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

doc/api/tracing.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ The available categories are:
1818
The [`async_hooks`] events have a unique `asyncId` and a special `triggerId`
1919
`triggerAsyncId` property.
2020
* `node.bootstrap` - Enables capture of Node.js bootstrap milestones.
21+
* `node.fs.sync` - Enables capture of trace data for file system sync methods.
2122
* `node.perf` - Enables capture of [Performance API] measurements.
2223
* `node.perf.usertiming` - Enables capture of only Performance API User Timing
2324
measures and marks.
2425
* `node.perf.timerify` - Enables capture of only Performance API timerify
2526
measurements.
26-
* `node.fs.sync` - Enables capture of trace data for file system sync methods.
27+
* `node.promises.rejections` - Enables capture of trace data tracking the number
28+
of unhandled Promise rejections and handled-after-rejections.
2729
* `node.vm.script` - Enables capture of trace data for the `vm` module's
2830
`runInNewContext()`, `runInContext()`, and `runInThisContext()` methods.
2931
* `v8` - The [V8] events are GC, compiling, and execution related.

src/bootstrapper.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
5151
}
5252

5353
void PromiseRejectCallback(PromiseRejectMessage message) {
54+
static uint64_t unhandledRejections = 0;
55+
static uint64_t rejectionsHandledAfter = 0;
56+
5457
Local<Promise> promise = message.GetPromise();
5558
Isolate* isolate = promise->GetIsolate();
5659
PromiseRejectEvent event = message.GetEvent();
@@ -65,13 +68,23 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
6568

6669
if (value.IsEmpty())
6770
value = Undefined(isolate);
71+
72+
unhandledRejections++;
6873
} else if (event == v8::kPromiseHandlerAddedAfterReject) {
6974
callback = env->promise_reject_handled_function();
7075
value = Undefined(isolate);
76+
77+
rejectionsHandledAfter++;
7178
} else {
7279
return;
7380
}
7481

82+
TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
83+
"rejections",
84+
"unhandled", unhandledRejections,
85+
"handledAfter", rejectionsHandledAfter);
86+
87+
7588
Local<Value> args[] = { promise, value };
7689
MaybeLocal<Value> ret = callback->Call(env->context(),
7790
Undefined(isolate),
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
const path = require('path');
6+
const fs = require('fs');
7+
const tmpdir = require('../common/tmpdir');
8+
9+
common.disableCrashOnUnhandledRejection();
10+
11+
if (!common.isMainThread)
12+
common.skip('process.chdir is not available in Workers');
13+
14+
if (process.argv[2] === 'child') {
15+
const p = Promise.reject(1); // Handled later
16+
Promise.reject(2); // Unhandled
17+
setImmediate(() => {
18+
p.catch(() => { /* intentional noop */ });
19+
});
20+
} else {
21+
tmpdir.refresh();
22+
process.chdir(tmpdir.path);
23+
24+
const proc = cp.fork(__filename,
25+
[ 'child' ], {
26+
execArgv: [
27+
'--no-warnings',
28+
'--trace-event-categories',
29+
'node.promises.rejections'
30+
]
31+
});
32+
33+
proc.once('exit', common.mustCall(() => {
34+
const file = path.join(tmpdir.path, 'node_trace.1.log');
35+
36+
assert(common.fileExists(file));
37+
fs.readFile(file, common.mustCall((err, data) => {
38+
const traces = JSON.parse(data.toString()).traceEvents
39+
.filter((trace) => trace.cat !== '__metadata');
40+
traces.forEach((trace) => {
41+
assert.strictEqual(trace.pid, proc.pid);
42+
assert(trace.name === 'rejections');
43+
assert(trace.args.unhandled <= 2);
44+
assert(trace.args.handledAfter <= 1);
45+
});
46+
}));
47+
}));
48+
}

0 commit comments

Comments
 (0)