Skip to content

Commit

Permalink
cli: add --trace-exit cli option
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
legendecas authored and MylesBorins committed Dec 17, 2019
1 parent 510edea commit d8ce9a0
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 0 deletions.
9 changes: 9 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,14 @@ added: v7.7.0

Enables the collection of trace event tracing information.

### `--trace-exit`
<!-- YAML
added: REPLACEME
-->

Prints a stack trace whenever an environment is exited proactively,
i.e. invoking `process.exit()`.

### `--trace-sync-io`
<!-- YAML
added: v2.1.0
Expand Down Expand Up @@ -1112,6 +1120,7 @@ Node.js options that are allowed are:
* `--trace-event-categories`
* `--trace-event-file-pattern`
* `--trace-events-enabled`
* `--trace-exit`
* `--trace-sync-io`
* `--trace-tls`
* `--trace-uncaught`
Expand Down
4 changes: 4 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@ and
.It Fl -trace-events-enabled
Enable the collection of trace event tracing information.
.
.It Fl -trace-exit
Prints a stack trace whenever an environment is exited proactively,
i.e. invoking `process.exit()`.
.
.It Fl -trace-sync-io
Print a stack trace whenever synchronous I/O is detected after the first turn of the event loop.
.
Expand Down
15 changes: 15 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,21 @@ void AsyncHooks::grow_async_ids_stack() {
uv_key_t Environment::thread_local_env = {};

void Environment::Exit(int exit_code) {
if (options()->trace_exit) {
HandleScope handle_scope(isolate());

if (is_main_thread()) {
fprintf(stderr, "(node:%d) ", uv_os_getpid());
} else {
fprintf(stderr, "(node:%d, thread:%llu) ", uv_os_getpid(), thread_id());
}

fprintf(
stderr, "WARNING: Exited the environment with code %d\n", exit_code);
PrintStackTrace(
isolate(),
StackTrace::CurrentStackTrace(isolate(), 10, StackTrace::kDetailed));
}
if (is_main_thread()) {
stop_sub_worker_contexts();
DisposePlatform();
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"show stack traces on deprecations",
&EnvironmentOptions::trace_deprecation,
kAllowedInEnvironment);
AddOption("--trace-exit",
"show stack trace when an environment exits",
&EnvironmentOptions::trace_exit,
kAllowedInEnvironment);
AddOption("--trace-sync-io",
"show stack trace when use of sync IO is detected after the "
"first tick",
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class EnvironmentOptions : public Options {
bool test_udp_no_try_send = false;
bool throw_deprecation = false;
bool trace_deprecation = false;
bool trace_exit = false;
bool trace_sync_io = false;
bool trace_tls = false;
bool trace_uncaught = false;
Expand Down
59 changes: 59 additions & 0 deletions test/parallel/test-trace-exit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const { promisify } = require('util');
const execFile = promisify(require('child_process').execFile);
const { Worker, isMainThread, workerData } = require('worker_threads');

const variant = process.argv[process.argv.length - 1];
switch (true) {
case variant === 'main-thread': {
return;
}
case variant === 'main-thread-exit': {
return process.exit(0);
}
case variant.startsWith('worker-thread'): {
const worker = new Worker(__filename, { workerData: variant });
worker.on('error', common.mustNotCall());
worker.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 0);
}));
return;
}
case !isMainThread: {
if (workerData === 'worker-thread-exit') {
process.exit(0);
}
return;
}
}

(async function() {
for (const { execArgv, variant, warnings } of [
{ execArgv: ['--trace-exit'], variant: 'main-thread-exit', warnings: 1 },
{ execArgv: [], variant: 'main-thread-exit', warnings: 0 },
{ execArgv: ['--trace-exit'], variant: 'main-thread', warnings: 0 },
{ execArgv: [], variant: 'main-thread', warnings: 0 },
{ execArgv: ['--trace-exit'], variant: 'worker-thread-exit', warnings: 1 },
{ execArgv: [], variant: 'worker-thread-exit', warnings: 0 },
{ execArgv: ['--trace-exit'], variant: 'worker-thread', warnings: 0 },
{ execArgv: [], variant: 'worker-thread', warnings: 0 },
]) {
const { stdout, stderr } =
await execFile(process.execPath, [...execArgv, __filename, variant]);
assert.strictEqual(stdout, '');
const actualWarnings =
stderr.match(/WARNING: Exited the environment with code 0/g);
if (warnings === 0) {
assert.strictEqual(actualWarnings, null);
return;
}
assert.strictEqual(actualWarnings.length, warnings);

if (variant.startsWith('worker')) {
const workerIds = stderr.match(/\(node:\d+, thread:\d+)/g);
assert.strictEqual(workerIds.length, warnings);
}
}
})().then(common.mustCall());

0 comments on commit d8ce9a0

Please sign in to comment.