Skip to content

Commit 51ad99b

Browse files
legendecastargos
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: nodejs#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 6fe0129 commit 51ad99b

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
@@ -820,6 +820,13 @@ added: v12.16.0
820820
Prints a stack trace whenever an environment is exited proactively,
821821
i.e. invoking `process.exit()`.
822822

823+
### `--trace-sigint`
824+
<!-- YAML
825+
added: REPLACEME
826+
-->
827+
828+
Prints a stack trace on SIGINT.
829+
823830
### `--trace-sync-io`
824831
<!-- YAML
825832
added: v2.1.0
@@ -1149,6 +1156,7 @@ Node.js options that are allowed are:
11491156
* `--trace-event-file-pattern`
11501157
* `--trace-events-enabled`
11511158
* `--trace-exit`
1159+
* `--trace-sigint`
11521160
* `--trace-sync-io`
11531161
* `--trace-tls`
11541162
* `--trace-uncaught`

doc/node.1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ Enable the collection of trace event tracing information.
388388
.It Fl -trace-exit
389389
Prints a stack trace whenever an environment is exited proactively,
390390
i.e. invoking `process.exit()`.
391+
.It Fl -trace-sigint
392+
Prints a stack trace on SIGINT.
391393
.
392394
.It Fl -trace-sync-io
393395
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
@@ -211,6 +211,7 @@
211211
'lib/internal/vm/module.js',
212212
'lib/internal/worker.js',
213213
'lib/internal/worker/io.js',
214+
'lib/internal/watchdog.js',
214215
'lib/internal/streams/lazy_transform.js',
215216
'lib/internal/streams/async_iterator.js',
216217
'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) {
@@ -232,6 +240,12 @@ void MemoryTracker::TrackField(const char* name,
232240
TrackFieldWithSize(name, sizeof(value), "uv_async_t");
233241
}
234242

243+
void MemoryTracker::TrackInlineField(const char* name,
244+
const uv_async_t& value,
245+
const char* node_name) {
246+
TrackInlineFieldWithSize(name, sizeof(value), "uv_async_t");
247+
}
248+
235249
template <class NativeT, class V8T>
236250
void MemoryTracker::TrackField(const char* name,
237251
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, typename D>
140144
inline void TrackField(const char* edge_name,
@@ -220,6 +224,9 @@ class MemoryTracker {
220224
inline void TrackField(const char* edge_name,
221225
const uv_async_t& value,
222226
const char* node_name = nullptr);
227+
inline void TrackInlineField(const char* edge_name,
228+
const uv_async_t& value,
229+
const char* node_name = nullptr);
223230
template <class NativeT, class V8T>
224231
inline void TrackField(const char* edge_name,
225232
const AliasedBufferBase<NativeT, V8T>& value,

src/node_binding.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
V(v8) \
8989
V(wasi) \
9090
V(worker) \
91+
V(watchdog) \
9192
V(zlib)
9293

9394
#define NODE_BUILTIN_MODULES(V) \

src/node_options.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,11 @@ PerProcessOptionsParser::PerProcessOptionsParser(
775775
AddOption("--fast_calls_with_arguments_mismatches", "", NoOp{});
776776
AddOption("--harmony_numeric_separator", "", NoOp{});
777777

778+
AddOption("--trace-sigint",
779+
"enable printing JavaScript stacktrace on SIGINT",
780+
&PerProcessOptions::trace_sigint,
781+
kAllowedInEnvironment);
782+
778783
Insert(iop, &PerProcessOptions::get_per_isolate_options);
779784
}
780785

0 commit comments

Comments
 (0)