Skip to content

Commit 7bfc339

Browse files
legendecastargos
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 <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 732780f commit 7bfc339

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
@@ -786,6 +786,14 @@ added: v7.7.0
786786

787787
Enables the collection of trace event tracing information.
788788

789+
### `--trace-exit`
790+
<!-- YAML
791+
added: REPLACEME
792+
-->
793+
794+
Prints a stack trace whenever an environment is exited proactively,
795+
i.e. invoking `process.exit()`.
796+
789797
### `--trace-sync-io`
790798
<!-- YAML
791799
added: v2.1.0
@@ -1128,6 +1136,7 @@ Node.js options that are allowed are:
11281136
* `--trace-event-categories`
11291137
* `--trace-event-file-pattern`
11301138
* `--trace-events-enabled`
1139+
* `--trace-exit`
11311140
* `--trace-sync-io`
11321141
* `--trace-tls`
11331142
* `--trace-uncaught`

doc/node.1

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

src/env.cc

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

933933
void Environment::Exit(int exit_code) {
934+
if (options()->trace_exit) {
935+
HandleScope handle_scope(isolate());
936+
937+
if (is_main_thread()) {
938+
fprintf(stderr, "(node:%d) ", uv_os_getpid());
939+
} else {
940+
fprintf(stderr, "(node:%d, thread:%llu) ", uv_os_getpid(), thread_id());
941+
}
942+
943+
fprintf(
944+
stderr, "WARNING: Exited the environment with code %d\n", exit_code);
945+
PrintStackTrace(
946+
isolate(),
947+
StackTrace::CurrentStackTrace(isolate(), 10, StackTrace::kDetailed));
948+
}
934949
if (is_main_thread()) {
935950
stop_sub_worker_contexts();
936951
DisposePlatform();

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
511511
"show stack traces on deprecations",
512512
&EnvironmentOptions::trace_deprecation,
513513
kAllowedInEnvironment);
514+
AddOption("--trace-exit",
515+
"show stack trace when an environment exits",
516+
&EnvironmentOptions::trace_exit,
517+
kAllowedInEnvironment);
514518
AddOption("--trace-sync-io",
515519
"show stack trace when use of sync IO is detected after the "
516520
"first tick",

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class EnvironmentOptions : public Options {
142142
bool test_udp_no_try_send = false;
143143
bool throw_deprecation = false;
144144
bool trace_deprecation = false;
145+
bool trace_exit = false;
145146
bool trace_sync_io = false;
146147
bool trace_tls = false;
147148
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)