Skip to content

Commit d8ce9a0

Browse files
legendecasMylesBorins
authored andcommitted
cli: add --trace-exit cli option
It could be convenient to trace abnormal exit of the Node.js processes that printing stacktrace on each `process.exit` call with a cli option. This also takes effects on worker threads. PR-URL: #30516 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 510edea commit d8ce9a0

File tree

6 files changed

+92
-0
lines changed

6 files changed

+92
-0
lines changed

doc/api/cli.md

+9
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,14 @@ added: v7.7.0
784784

785785
Enables the collection of trace event tracing information.
786786

787+
### `--trace-exit`
788+
<!-- YAML
789+
added: REPLACEME
790+
-->
791+
792+
Prints a stack trace whenever an environment is exited proactively,
793+
i.e. invoking `process.exit()`.
794+
787795
### `--trace-sync-io`
788796
<!-- YAML
789797
added: v2.1.0
@@ -1112,6 +1120,7 @@ Node.js options that are allowed are:
11121120
* `--trace-event-categories`
11131121
* `--trace-event-file-pattern`
11141122
* `--trace-events-enabled`
1123+
* `--trace-exit`
11151124
* `--trace-sync-io`
11161125
* `--trace-tls`
11171126
* `--trace-uncaught`

doc/node.1

+4
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,10 @@ and
355355
.It Fl -trace-events-enabled
356356
Enable the collection of trace event tracing information.
357357
.
358+
.It Fl -trace-exit
359+
Prints a stack trace whenever an environment is exited proactively,
360+
i.e. invoking `process.exit()`.
361+
.
358362
.It Fl -trace-sync-io
359363
Print a stack trace whenever synchronous I/O is detected after the first turn of the event loop.
360364
.

src/env.cc

+15
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,21 @@ void AsyncHooks::grow_async_ids_stack() {
940940
uv_key_t Environment::thread_local_env = {};
941941

942942
void Environment::Exit(int exit_code) {
943+
if (options()->trace_exit) {
944+
HandleScope handle_scope(isolate());
945+
946+
if (is_main_thread()) {
947+
fprintf(stderr, "(node:%d) ", uv_os_getpid());
948+
} else {
949+
fprintf(stderr, "(node:%d, thread:%llu) ", uv_os_getpid(), thread_id());
950+
}
951+
952+
fprintf(
953+
stderr, "WARNING: Exited the environment with code %d\n", exit_code);
954+
PrintStackTrace(
955+
isolate(),
956+
StackTrace::CurrentStackTrace(isolate(), 10, StackTrace::kDetailed));
957+
}
943958
if (is_main_thread()) {
944959
stop_sub_worker_contexts();
945960
DisposePlatform();

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
480480
"show stack traces on deprecations",
481481
&EnvironmentOptions::trace_deprecation,
482482
kAllowedInEnvironment);
483+
AddOption("--trace-exit",
484+
"show stack trace when an environment exits",
485+
&EnvironmentOptions::trace_exit,
486+
kAllowedInEnvironment);
483487
AddOption("--trace-sync-io",
484488
"show stack trace when use of sync IO is detected after the "
485489
"first tick",

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class EnvironmentOptions : public Options {
141141
bool test_udp_no_try_send = false;
142142
bool throw_deprecation = false;
143143
bool trace_deprecation = false;
144+
bool trace_exit = false;
144145
bool trace_sync_io = false;
145146
bool trace_tls = false;
146147
bool trace_uncaught = false;

test/parallel/test-trace-exit.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const { promisify } = require('util');
5+
const execFile = promisify(require('child_process').execFile);
6+
const { Worker, isMainThread, workerData } = require('worker_threads');
7+
8+
const variant = process.argv[process.argv.length - 1];
9+
switch (true) {
10+
case variant === 'main-thread': {
11+
return;
12+
}
13+
case variant === 'main-thread-exit': {
14+
return process.exit(0);
15+
}
16+
case variant.startsWith('worker-thread'): {
17+
const worker = new Worker(__filename, { workerData: variant });
18+
worker.on('error', common.mustNotCall());
19+
worker.on('exit', common.mustCall((code) => {
20+
assert.strictEqual(code, 0);
21+
}));
22+
return;
23+
}
24+
case !isMainThread: {
25+
if (workerData === 'worker-thread-exit') {
26+
process.exit(0);
27+
}
28+
return;
29+
}
30+
}
31+
32+
(async function() {
33+
for (const { execArgv, variant, warnings } of [
34+
{ execArgv: ['--trace-exit'], variant: 'main-thread-exit', warnings: 1 },
35+
{ execArgv: [], variant: 'main-thread-exit', warnings: 0 },
36+
{ execArgv: ['--trace-exit'], variant: 'main-thread', warnings: 0 },
37+
{ execArgv: [], variant: 'main-thread', warnings: 0 },
38+
{ execArgv: ['--trace-exit'], variant: 'worker-thread-exit', warnings: 1 },
39+
{ execArgv: [], variant: 'worker-thread-exit', warnings: 0 },
40+
{ execArgv: ['--trace-exit'], variant: 'worker-thread', warnings: 0 },
41+
{ execArgv: [], variant: 'worker-thread', warnings: 0 },
42+
]) {
43+
const { stdout, stderr } =
44+
await execFile(process.execPath, [...execArgv, __filename, variant]);
45+
assert.strictEqual(stdout, '');
46+
const actualWarnings =
47+
stderr.match(/WARNING: Exited the environment with code 0/g);
48+
if (warnings === 0) {
49+
assert.strictEqual(actualWarnings, null);
50+
return;
51+
}
52+
assert.strictEqual(actualWarnings.length, warnings);
53+
54+
if (variant.startsWith('worker')) {
55+
const workerIds = stderr.match(/\(node:\d+, thread:\d+)/g);
56+
assert.strictEqual(workerIds.length, warnings);
57+
}
58+
}
59+
})().then(common.mustCall());

0 commit comments

Comments
 (0)