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 <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 732780f commit 7bfc339

6 files changed

Lines changed: 92 additions & 0 deletions

File tree

‎doc/api/cli.md‎

Lines changed: 9 additions & 0 deletions
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‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ and
353353
.ItFl-trace-events-enabled
354354
Enable the collection of trace event tracing information.
355355
.
356+
.ItFl-trace-exit
357+
Prints a stack trace whenever an environment is exited proactively,
358+
i.e. invoking `process.exit()`.
359+
.
356360
.ItFl-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‎

Lines changed: 15 additions & 0 deletions
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
voidEnvironment::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‎

Lines changed: 4 additions & 0 deletions
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‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constassert=require('assert');
4+
const{ promisify }=require('util');
5+
constexecFile=promisify(require('child_process').execFile);
6+
const{ Worker, isMainThread, workerData }=require('worker_threads');
7+
8+
constvariant=process.argv[process.argv.length-1];
9+
switch(true){
10+
casevariant==='main-thread': {
11+
return;
12+
}
13+
casevariant==='main-thread-exit': {
14+
returnprocess.exit(0);
15+
}
16+
casevariant.startsWith('worker-thread'): {
17+
constworker=newWorker(__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+
(asyncfunction(){
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+
awaitexecFile(process.execPath,[...execArgv,__filename,variant]);
45+
assert.strictEqual(stdout,'');
46+
constactualWarnings=
47+
stderr.match(/WARNING:Exitedtheenvironmentwithcode0/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+
constworkerIds=stderr.match(/\(node:\d+,thread:\d+)/g);
56+
assert.strictEqual(workerIds.length,warnings);
57+
}
58+
}
59+
})().then(common.mustCall());

0 commit comments

Comments
 (0)