Skip to content

Commit 3dd4089

Browse files
legendecascodebytere
authored andcommitted
src,lib: make ^C print a JS stack trace
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>
1 parent ef4d081 commit 3dd4089

20 files changed

+405
-15
lines changed

doc/api/cli.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,13 @@ added: v13.5.0
779779
Prints a stack trace whenever an environment is exited proactively,
780780
i.e. invoking `process.exit()`.
781781

782+
### `--trace-sigint`
783+
<!-- YAML
784+
added: REPLACEME
785+
-->
786+
787+
Prints a stack trace on SIGINT.
788+
782789
### `--trace-sync-io`
783790
<!-- YAML
784791
added: v2.1.0
@@ -1122,6 +1129,7 @@ Node.js options that are allowed are:
11221129
* `--trace-event-file-pattern`
11231130
* `--trace-events-enabled`
11241131
* `--trace-exit`
1132+
* `--trace-sigint`
11251133
* `--trace-sync-io`
11261134
* `--trace-tls`
11271135
* `--trace-uncaught`

doc/node.1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ Enable the collection of trace event tracing information.
367367
.It Fl -trace-exit
368368
Prints a stack trace whenever an environment is exited proactively,
369369
i.e. invoking `process.exit()`.
370+
.It Fl -trace-sigint
371+
Prints a stack trace on SIGINT.
370372
.
371373
.It Fl -trace-sync-io
372374
Print a stack trace whenever synchronous I/O is detected after the first turn of the event loop.

lib/internal/bootstrap/pre_execution.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ function prepareMainThreadExecution(expandArgv1 = false) {
3737

3838
setupDebugEnv();
3939

40+
// Print stack trace on `SIGINT` if option `--trace-sigint` presents.
41+
setupStacktracePrinterOnSigint();
42+
4043
// Process initial diagnostic reporting configuration, if present.
4144
initializeReport();
4245
initializeReportSignalHandlers(); // Main-thread-only.
@@ -149,6 +152,16 @@ function setupCoverageHooks(dir) {
149152
return coverageDirectory;
150153
}
151154

155+
function setupStacktracePrinterOnSigint() {
156+
if (!getOptionValue('--trace-sigint')) {
157+
return;
158+
}
159+
const { SigintWatchdog } = require('internal/watchdog');
160+
161+
const watchdog = new SigintWatchdog();
162+
watchdog.start();
163+
}
164+
152165
function initializeReport() {
153166
if (!getOptionValue('--experimental-report')) {
154167
return;

lib/internal/watchdog.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
const {
4+
TraceSigintWatchdog
5+
} = internalBinding('watchdog');
6+
7+
class SigintWatchdog extends TraceSigintWatchdog {
8+
_started = false;
9+
_effective = false;
10+
_onNewListener = (eve) => {
11+
if (eve === 'SIGINT' && this._effective) {
12+
super.stop();
13+
this._effective = false;
14+
}
15+
};
16+
_onRemoveListener = (eve) => {
17+
if (eve === 'SIGINT' && process.listenerCount('SIGINT') === 0 &&
18+
!this._effective) {
19+
super.start();
20+
this._effective = true;
21+
}
22+
}
23+
24+
start() {
25+
if (this._started) {
26+
return;
27+
}
28+
this._started = true;
29+
// Prepend sigint newListener to remove stop watchdog before signal wrap
30+
// been activated. Also make sigint removeListener been ran after signal
31+
// wrap been stopped.
32+
process.prependListener('newListener', this._onNewListener);
33+
process.addListener('removeListener', this._onRemoveListener);
34+
35+
if (process.listenerCount('SIGINT') === 0) {
36+
super.start();
37+
this._effective = true;
38+
}
39+
}
40+
41+
stop() {
42+
if (!this._started) {
43+
return;
44+
}
45+
this._started = false;
46+
process.removeListener('newListener', this._onNewListener);
47+
process.removeListener('removeListener', this._onRemoveListener);
48+
49+
if (this._effective) {
50+
super.stop();
51+
this._effective = false;
52+
}
53+
}
54+
}
55+
56+
57+
module.exports = {
58+
SigintWatchdog
59+
};

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@
210210
'lib/internal/vm/module.js',
211211
'lib/internal/worker.js',
212212
'lib/internal/worker/io.js',
213+
'lib/internal/watchdog.js',
213214
'lib/internal/streams/lazy_transform.js',
214215
'lib/internal/streams/async_iterator.js',
215216
'lib/internal/streams/buffer_list.js',

src/async_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ namespace node {
6868
V(TTYWRAP) \
6969
V(UDPSENDWRAP) \
7070
V(UDPWRAP) \
71+
V(SIGINTWATCHDOG) \
7172
V(WORKER) \
7273
V(WRITEWRAP) \
7374
V(ZLIB)

src/memory_tracker-inl.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ void MemoryTracker::TrackFieldWithSize(const char* edge_name,
8181
if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name);
8282
}
8383

84+
void MemoryTracker::TrackInlineFieldWithSize(const char* edge_name,
85+
size_t size,
86+
const char* node_name) {
87+
if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name);
88+
CHECK(CurrentNode());
89+
CurrentNode()->size_ -= size;
90+
}
91+
8492
void MemoryTracker::TrackField(const char* edge_name,
8593
const MemoryRetainer& value,
8694
const char* node_name) {
@@ -248,6 +256,12 @@ void MemoryTracker::TrackField(const char* name,
248256
TrackFieldWithSize(name, sizeof(value), "uv_async_t");
249257
}
250258

259+
void MemoryTracker::TrackInlineField(const char* name,
260+
const uv_async_t& value,
261+
const char* node_name) {
262+
TrackInlineFieldWithSize(name, sizeof(value), "uv_async_t");
263+
}
264+
251265
template <class NativeT, class V8T>
252266
void MemoryTracker::TrackField(const char* name,
253267
const AliasedBufferBase<NativeT, V8T>& value,

src/memory_tracker.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ class MemoryTracker {
135135
inline void TrackFieldWithSize(const char* edge_name,
136136
size_t size,
137137
const char* node_name = nullptr);
138+
inline void TrackInlineFieldWithSize(const char* edge_name,
139+
size_t size,
140+
const char* node_name = nullptr);
141+
138142
// Shortcut to extract the underlying object out of the smart pointer
139143
template <typename T>
140144
inline void TrackField(const char* edge_name,
@@ -228,6 +232,9 @@ class MemoryTracker {
228232
inline void TrackField(const char* edge_name,
229233
const uv_async_t& value,
230234
const char* node_name = nullptr);
235+
inline void TrackInlineField(const char* edge_name,
236+
const uv_async_t& value,
237+
const char* node_name = nullptr);
231238
template <class NativeT, class V8T>
232239
inline void TrackField(const char* edge_name,
233240
const AliasedBufferBase<NativeT, V8T>& value,

src/node_binding.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
V(v8) \
8888
V(wasi) \
8989
V(worker) \
90+
V(watchdog) \
9091
V(zlib)
9192

9293
#define NODE_BUILTIN_MODULES(V) \

src/node_options.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,11 @@ PerProcessOptionsParser::PerProcessOptionsParser(
758758
&PerProcessOptions::use_largepages,
759759
kAllowedInEnvironment);
760760

761+
AddOption("--trace-sigint",
762+
"enable printing JavaScript stacktrace on SIGINT",
763+
&PerProcessOptions::trace_sigint,
764+
kAllowedInEnvironment);
765+
761766
Insert(iop, &PerProcessOptions::get_per_isolate_options);
762767
}
763768

0 commit comments

Comments
 (0)