Skip to content

Commit

Permalink
src,lib: make ^C print a JS stack trace
Browse files Browse the repository at this point in the history
If terminating the process with ctrl-c / SIGINT, prints a JS stacktrace
leading up to the currently executing code.

The feature would be enabled under option `--trace-sigint`.

Conditions of no stacktrace on sigint:

- has (an) active sigint listener(s);
- main thread is idle (i.e. uv polling), a message instead of stacktrace
  would be printed.

PR-URL: #29207
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Christopher Hiller <boneskull@boneskull.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
  • Loading branch information
legendecas authored and targos committed Apr 28, 2020
1 parent f537377 commit b038ad9
Show file tree
Hide file tree
Showing 20 changed files with 405 additions and 15 deletions.
8 changes: 8 additions & 0 deletions doc/api/cli.md
Expand Up @@ -821,6 +821,13 @@ added: v12.16.0
Prints a stack trace whenever an environment is exited proactively,
i.e. invoking `process.exit()`.

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

Prints a stack trace on SIGINT.

### `--trace-sync-io`
<!-- YAML
added: v2.1.0
Expand Down Expand Up @@ -1150,6 +1157,7 @@ Node.js options that are allowed are:
* `--trace-event-file-pattern`
* `--trace-events-enabled`
* `--trace-exit`
* `--trace-sigint`
* `--trace-sync-io`
* `--trace-tls`
* `--trace-uncaught`
Expand Down
2 changes: 2 additions & 0 deletions doc/node.1
Expand Up @@ -388,6 +388,8 @@ 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-sigint
Prints a stack trace on SIGINT.
.
.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
13 changes: 13 additions & 0 deletions lib/internal/bootstrap/pre_execution.js
Expand Up @@ -37,6 +37,9 @@ function prepareMainThreadExecution(expandArgv1 = false) {

setupDebugEnv();

// Print stack trace on `SIGINT` if option `--trace-sigint` presents.
setupStacktracePrinterOnSigint();

// Process initial diagnostic reporting configuration, if present.
initializeReport();
initializeReportSignalHandlers(); // Main-thread-only.
Expand Down Expand Up @@ -149,6 +152,16 @@ function setupCoverageHooks(dir) {
return coverageDirectory;
}

function setupStacktracePrinterOnSigint() {
if (!getOptionValue('--trace-sigint')) {
return;
}
const { SigintWatchdog } = require('internal/watchdog');

const watchdog = new SigintWatchdog();
watchdog.start();
}

function initializeReport() {
if (!getOptionValue('--experimental-report')) {
return;
Expand Down
59 changes: 59 additions & 0 deletions lib/internal/watchdog.js
@@ -0,0 +1,59 @@
'use strict';

const {
TraceSigintWatchdog
} = internalBinding('watchdog');

class SigintWatchdog extends TraceSigintWatchdog {
_started = false;
_effective = false;
_onNewListener = (eve) => {
if (eve === 'SIGINT' && this._effective) {
super.stop();
this._effective = false;
}
};
_onRemoveListener = (eve) => {
if (eve === 'SIGINT' && process.listenerCount('SIGINT') === 0 &&
!this._effective) {
super.start();
this._effective = true;
}
}

start() {
if (this._started) {
return;
}
this._started = true;
// Prepend sigint newListener to remove stop watchdog before signal wrap
// been activated. Also make sigint removeListener been ran after signal
// wrap been stopped.
process.prependListener('newListener', this._onNewListener);
process.addListener('removeListener', this._onRemoveListener);

if (process.listenerCount('SIGINT') === 0) {
super.start();
this._effective = true;
}
}

stop() {
if (!this._started) {
return;
}
this._started = false;
process.removeListener('newListener', this._onNewListener);
process.removeListener('removeListener', this._onRemoveListener);

if (this._effective) {
super.stop();
this._effective = false;
}
}
}


module.exports = {
SigintWatchdog
};
1 change: 1 addition & 0 deletions node.gyp
Expand Up @@ -211,6 +211,7 @@
'lib/internal/vm/module.js',
'lib/internal/worker.js',
'lib/internal/worker/io.js',
'lib/internal/watchdog.js',
'lib/internal/streams/lazy_transform.js',
'lib/internal/streams/async_iterator.js',
'lib/internal/streams/buffer_list.js',
Expand Down
1 change: 1 addition & 0 deletions src/async_wrap.h
Expand Up @@ -68,6 +68,7 @@ namespace node {
V(TTYWRAP) \
V(UDPSENDWRAP) \
V(UDPWRAP) \
V(SIGINTWATCHDOG) \
V(WORKER) \
V(WRITEWRAP) \
V(ZLIB)
Expand Down
14 changes: 14 additions & 0 deletions src/memory_tracker-inl.h
Expand Up @@ -81,6 +81,14 @@ void MemoryTracker::TrackFieldWithSize(const char* edge_name,
if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name);
}

void MemoryTracker::TrackInlineFieldWithSize(const char* edge_name,
size_t size,
const char* node_name) {
if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name);
CHECK(CurrentNode());
CurrentNode()->size_ -= size;
}

void MemoryTracker::TrackField(const char* edge_name,
const MemoryRetainer& value,
const char* node_name) {
Expand Down Expand Up @@ -232,6 +240,12 @@ void MemoryTracker::TrackField(const char* name,
TrackFieldWithSize(name, sizeof(value), "uv_async_t");
}

void MemoryTracker::TrackInlineField(const char* name,
const uv_async_t& value,
const char* node_name) {
TrackInlineFieldWithSize(name, sizeof(value), "uv_async_t");
}

template <class NativeT, class V8T>
void MemoryTracker::TrackField(const char* name,
const AliasedBufferBase<NativeT, V8T>& value,
Expand Down
7 changes: 7 additions & 0 deletions src/memory_tracker.h
Expand Up @@ -135,6 +135,10 @@ class MemoryTracker {
inline void TrackFieldWithSize(const char* edge_name,
size_t size,
const char* node_name = nullptr);
inline void TrackInlineFieldWithSize(const char* edge_name,
size_t size,
const char* node_name = nullptr);

// Shortcut to extract the underlying object out of the smart pointer
template <typename T, typename D>
inline void TrackField(const char* edge_name,
Expand Down Expand Up @@ -220,6 +224,9 @@ class MemoryTracker {
inline void TrackField(const char* edge_name,
const uv_async_t& value,
const char* node_name = nullptr);
inline void TrackInlineField(const char* edge_name,
const uv_async_t& value,
const char* node_name = nullptr);
template <class NativeT, class V8T>
inline void TrackField(const char* edge_name,
const AliasedBufferBase<NativeT, V8T>& value,
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Expand Up @@ -88,6 +88,7 @@
V(v8) \
V(wasi) \
V(worker) \
V(watchdog) \
V(zlib)

#define NODE_BUILTIN_MODULES(V) \
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.cc
Expand Up @@ -806,6 +806,11 @@ PerProcessOptionsParser::PerProcessOptionsParser(
AddOption("--fast_calls_with_arguments_mismatches", "", NoOp{});
AddOption("--harmony_numeric_separator", "", NoOp{});

AddOption("--trace-sigint",
"enable printing JavaScript stacktrace on SIGINT",
&PerProcessOptions::trace_sigint,
kAllowedInEnvironment);

Insert(iop, &PerProcessOptions::get_per_isolate_options);
}

Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Expand Up @@ -239,6 +239,7 @@ class PerProcessOptions : public Options {
bool force_fips_crypto = false;
#endif
#endif
bool trace_sigint = false;

#ifdef NODE_REPORT
std::vector<std::string> cmdline;
Expand Down

0 comments on commit b038ad9

Please sign in to comment.