Skip to content

Commit ad46cca

Browse files
jasnellrvagg
authored andcommitted
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: #22124 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent ca0fb3a commit ad46cca

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

doc/api/tracing.md

+3-1
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

+15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "node_internals.h"
44
#include "v8.h"
55

6+
#include <atomic>
7+
68
namespace node {
79

810
using v8::Array;
@@ -51,6 +53,9 @@ void SetupNextTick(const FunctionCallbackInfo<Value>& args) {
5153
}
5254

5355
void PromiseRejectCallback(PromiseRejectMessage message) {
56+
static std::atomic<uint64_t> unhandledRejections{0};
57+
static std::atomic<uint64_t> rejectionsHandledAfter{0};
58+
5459
Local<Promise> promise = message.GetPromise();
5560
Isolate* isolate = promise->GetIsolate();
5661
PromiseRejectEvent event = message.GetEvent();
@@ -65,13 +70,23 @@ void PromiseRejectCallback(PromiseRejectMessage message) {
6570

6671
if (value.IsEmpty())
6772
value = Undefined(isolate);
73+
74+
unhandledRejections++;
6875
} else if (event == v8::kPromiseHandlerAddedAfterReject) {
6976
callback = env->promise_reject_handled_function();
7077
value = Undefined(isolate);
78+
79+
rejectionsHandledAfter++;
7180
} else {
7281
return;
7382
}
7483

84+
TRACE_COUNTER2(TRACING_CATEGORY_NODE2(promises, rejections),
85+
"rejections",
86+
"unhandled", unhandledRejections,
87+
"handledAfter", rejectionsHandledAfter);
88+
89+
7590
Local<Value> args[] = { promise, value };
7691
MaybeLocal<Value> ret = callback->Call(env->context(),
7792
Undefined(isolate),
+48
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.strictEqual(trace.name, 'rejections');
43+
assert(trace.args.unhandled <= 2);
44+
assert(trace.args.handledAfter <= 1);
45+
});
46+
}));
47+
}));
48+
}

0 commit comments

Comments
 (0)