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 <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
legendecas authored and BethGriggs committed Feb 6, 2020
1 parent 1bcbc70 commit 8dc4e4e
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
Expand Up @@ -796,6 +796,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 @@ -1139,6 +1147,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
Expand Up @@ -359,6 +359,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
Expand Up @@ -931,6 +931,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
Expand Up @@ -515,6 +515,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
Expand Up @@ -142,6 +142,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
@@ -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 8dc4e4e

Please sign in to comment.