Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src,lib: make ^C print a JS stack trace #29207

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/cli.md
Expand Up @@ -779,6 +779,13 @@ added: v13.5.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 @@ -1122,6 +1129,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 @@ -367,6 +367,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;
mscdex marked this conversation as resolved.
Show resolved Hide resolved
_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);
mscdex marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -210,6 +210,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 @@ -248,6 +256,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>
inline void TrackField(const char* edge_name,
Expand Down Expand Up @@ -228,6 +232,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 @@ -87,6 +87,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 @@ -758,6 +758,11 @@ PerProcessOptionsParser::PerProcessOptionsParser(
&PerProcessOptions::use_largepages,
kAllowedInEnvironment);

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 @@ -237,6 +237,7 @@ class PerProcessOptions : public Options {
#endif
#endif
std::string use_largepages = "off";
bool trace_sigint = false;

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