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]>
legendecas authored and MylesBorins committed Dec 17, 2019
1 parent 510edea commit d8ce9a0
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
@@ -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
@@ -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`
4 changes: 4 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
@@ -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.
.
15 changes: 15 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
@@ -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();
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
@@ -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",
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
@@ -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;
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.