Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlegendecas <legendecas@gmail.com>2019-08-19 16:03:08 +0300
committerlegendecas <legendecas@gmail.com>2020-01-28 08:52:27 +0300
commit7b7e7bd185aa606cf2845747dd3844a9b12ec9ab (patch)
tree5081e7a080edf45d2b5f82039e0d014c0ab46d96
parent78743f8e3912fa04670ceab5365f081b3079507b (diff)
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: https://github.com/nodejs/node/pull/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>
-rw-r--r--doc/api/cli.md8
-rw-r--r--doc/node.12
-rw-r--r--lib/internal/bootstrap/pre_execution.js13
-rw-r--r--lib/internal/watchdog.js59
-rw-r--r--node.gyp1
-rw-r--r--src/async_wrap.h1
-rw-r--r--src/memory_tracker-inl.h14
-rw-r--r--src/memory_tracker.h7
-rw-r--r--src/node_binding.cc1
-rw-r--r--src/node_options.cc5
-rw-r--r--src/node_options.h1
-rw-r--r--src/node_watchdog.cc144
-rw-r--r--src/node_watchdog.h54
-rw-r--r--test/pseudo-tty/test-trace-sigint-disabled.js39
-rw-r--r--test/pseudo-tty/test-trace-sigint-disabled.out0
-rw-r--r--test/pseudo-tty/test-trace-sigint-on-idle.js30
-rw-r--r--test/pseudo-tty/test-trace-sigint-on-idle.out1
-rw-r--r--test/pseudo-tty/test-trace-sigint.js30
-rw-r--r--test/pseudo-tty/test-trace-sigint.out9
-rw-r--r--test/sequential/test-async-wrap-getasyncid.js1
20 files changed, 405 insertions, 15 deletions
diff --git a/doc/api/cli.md b/doc/api/cli.md
index bb053193f5b..42ef76031eb 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -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
@@ -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`
diff --git a/doc/node.1 b/doc/node.1
index 9a75cd073ee..0227bd383a5 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -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.
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index 1f4d5c04b9b..46af6c30e0d 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -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.
@@ -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;
diff --git a/lib/internal/watchdog.js b/lib/internal/watchdog.js
new file mode 100644
index 00000000000..6a5b772111f
--- /dev/null
+++ b/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
+};
diff --git a/node.gyp b/node.gyp
index 1495b0dab2d..529a36ba83c 100644
--- a/node.gyp
+++ b/node.gyp
@@ -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',
diff --git a/src/async_wrap.h b/src/async_wrap.h
index dd82497a259..86acf54cd68 100644
--- a/src/async_wrap.h
+++ b/src/async_wrap.h
@@ -68,6 +68,7 @@ namespace node {
V(TTYWRAP) \
V(UDPSENDWRAP) \
V(UDPWRAP) \
+ V(SIGINTWATCHDOG) \
V(WORKER) \
V(WRITEWRAP) \
V(ZLIB)
diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h
index 938aba1a7a8..1a28e2dd792 100644
--- a/src/memory_tracker-inl.h
+++ b/src/memory_tracker-inl.h
@@ -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) {
@@ -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,
diff --git a/src/memory_tracker.h b/src/memory_tracker.h
index 7e39da5ecf6..616976ab2af 100644
--- a/src/memory_tracker.h
+++ b/src/memory_tracker.h
@@ -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,
@@ -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,
diff --git a/src/node_binding.cc b/src/node_binding.cc
index 82836585c58..37c64000650 100644
--- a/src/node_binding.cc
+++ b/src/node_binding.cc
@@ -87,6 +87,7 @@
V(v8) \
V(wasi) \
V(worker) \
+ V(watchdog) \
V(zlib)
#define NODE_BUILTIN_MODULES(V) \
diff --git a/src/node_options.cc b/src/node_options.cc
index 2f0d1a9ad7e..8c801f500c3 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -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);
}
diff --git a/src/node_options.h b/src/node_options.h
index 788a4815e50..afb12c393cb 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -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;
diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc
index 4ab625ce65b..4cc75c31604 100644
--- a/src/node_watchdog.cc
+++ b/src/node_watchdog.cc
@@ -21,6 +21,7 @@
#include <algorithm>
+#include "async_wrap-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node_errors.h"
@@ -30,6 +31,12 @@
namespace node {
+using v8::Context;
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Object;
+using v8::Value;
+
Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out)
: isolate_(isolate), timed_out_(timed_out) {
@@ -106,10 +113,116 @@ SigintWatchdog::~SigintWatchdog() {
SigintWatchdogHelper::GetInstance()->Stop();
}
-
-void SigintWatchdog::HandleSigint() {
+SignalPropagation SigintWatchdog::HandleSigint() {
*received_signal_ = true;
isolate_->TerminateExecution();
+ return SignalPropagation::kStopPropagation;
+}
+
+void TraceSigintWatchdog::Init(Environment* env, Local<Object> target) {
+ Local<FunctionTemplate> constructor = env->NewFunctionTemplate(New);
+ constructor->InstanceTemplate()->SetInternalFieldCount(1);
+ Local<v8::String> js_sigint_watch_dog =
+ FIXED_ONE_BYTE_STRING(env->isolate(), "TraceSigintWatchdog");
+ constructor->SetClassName(js_sigint_watch_dog);
+ constructor->Inherit(HandleWrap::GetConstructorTemplate(env));
+
+ env->SetProtoMethod(constructor, "start", Start);
+ env->SetProtoMethod(constructor, "stop", Stop);
+
+ target
+ ->Set(env->context(),
+ js_sigint_watch_dog,
+ constructor->GetFunction(env->context()).ToLocalChecked())
+ .Check();
+}
+
+void TraceSigintWatchdog::New(const FunctionCallbackInfo<Value>& args) {
+ // This constructor should not be exposed to public javascript.
+ // Therefore we assert that we are not trying to call this as a
+ // normal function.
+ CHECK(args.IsConstructCall());
+ Environment* env = Environment::GetCurrent(args);
+ new TraceSigintWatchdog(env, args.This());
+}
+
+void TraceSigintWatchdog::Start(const FunctionCallbackInfo<Value>& args) {
+ TraceSigintWatchdog* watchdog;
+ ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
+ // Register this watchdog with the global SIGINT/Ctrl+C listener.
+ SigintWatchdogHelper::GetInstance()->Register(watchdog);
+ // Start the helper thread, if that has not already happened.
+ int r = SigintWatchdogHelper::GetInstance()->Start();
+ CHECK_EQ(r, 0);
+}
+
+void TraceSigintWatchdog::Stop(const FunctionCallbackInfo<Value>& args) {
+ TraceSigintWatchdog* watchdog;
+ ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder());
+ SigintWatchdogHelper::GetInstance()->Unregister(watchdog);
+ SigintWatchdogHelper::GetInstance()->Stop();
+}
+
+TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local<Object> object)
+ : HandleWrap(env,
+ object,
+ reinterpret_cast<uv_handle_t*>(&handle_),
+ AsyncWrap::PROVIDER_SIGINTWATCHDOG) {
+ int r = uv_async_init(env->event_loop(), &handle_, [](uv_async_t* handle) {
+ TraceSigintWatchdog* watchdog =
+ ContainerOf(&TraceSigintWatchdog::handle_, handle);
+ watchdog->signal_flag_ = SignalFlags::FromIdle;
+ watchdog->HandleInterrupt();
+ });
+ CHECK_EQ(r, 0);
+ uv_unref(reinterpret_cast<uv_handle_t*>(&handle_));
+}
+
+SignalPropagation TraceSigintWatchdog::HandleSigint() {
+ /**
+ * In case of uv loop polling, i.e. no JS currently running, activate the
+ * loop to run a piece of JS code to trigger interruption.
+ */
+ CHECK_EQ(uv_async_send(&handle_), 0);
+ env()->isolate()->RequestInterrupt(
+ [](v8::Isolate* isolate, void* data) {
+ TraceSigintWatchdog* self = static_cast<TraceSigintWatchdog*>(data);
+ if (self->signal_flag_ == SignalFlags::None) {
+ self->signal_flag_ = SignalFlags::FromInterrupt;
+ }
+ self->HandleInterrupt();
+ },
+ this);
+ return SignalPropagation::kContinuePropagation;
+}
+
+void TraceSigintWatchdog::HandleInterrupt() {
+ // Do not nest interrupts.
+ if (interrupting) {
+ return;
+ }
+ interrupting = true;
+ if (signal_flag_ == SignalFlags::None) {
+ return;
+ }
+ Environment* env_ = env();
+ // FIXME: Before
+ // https://github.com/nodejs/node/pull/29207#issuecomment-527667993 get
+ // fixed, additional JavaScript code evaluation shall be prevented from
+ // running during interruption.
+ FPrintF(stderr,
+ "KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`\n");
+ if (signal_flag_ == SignalFlags::FromInterrupt) {
+ PrintStackTrace(env_->isolate(),
+ v8::StackTrace::CurrentStackTrace(
+ env_->isolate(), 10, v8::StackTrace::kDetailed));
+ }
+ signal_flag_ = SignalFlags::None;
+ interrupting = false;
+
+ SigintWatchdogHelper::GetInstance()->Unregister(this);
+ SigintWatchdogHelper::GetInstance()->Stop();
+ raise(SIGINT);
}
#ifdef __POSIX__
@@ -163,8 +276,13 @@ bool SigintWatchdogHelper::InformWatchdogsAboutSignal() {
instance.has_pending_signal_ = true;
}
- for (auto it : instance.watchdogs_)
- it->HandleSigint();
+ for (auto it = instance.watchdogs_.rbegin(); it != instance.watchdogs_.rend();
+ it++) {
+ SignalPropagation wp = (*it)->HandleSigint();
+ if (wp == SignalPropagation::kStopPropagation) {
+ break;
+ }
+ }
return is_stopping;
}
@@ -260,15 +378,13 @@ bool SigintWatchdogHelper::HasPendingSignal() {
return has_pending_signal_;
}
-
-void SigintWatchdogHelper::Register(SigintWatchdog* wd) {
+void SigintWatchdogHelper::Register(SigintWatchdogBase* wd) {
Mutex::ScopedLock lock(list_mutex_);
watchdogs_.push_back(wd);
}
-
-void SigintWatchdogHelper::Unregister(SigintWatchdog* wd) {
+void SigintWatchdogHelper::Unregister(SigintWatchdogBase* wd) {
Mutex::ScopedLock lock(list_mutex_);
auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd);
@@ -303,4 +419,16 @@ SigintWatchdogHelper::~SigintWatchdogHelper() {
SigintWatchdogHelper SigintWatchdogHelper::instance;
+namespace watchdog {
+static void Initialize(Local<Object> target,
+ Local<Value> unused,
+ Local<Context> context,
+ void* priv) {
+ Environment* env = Environment::GetCurrent(context);
+ TraceSigintWatchdog::Init(env, target);
+}
+} // namespace watchdog
+
} // namespace node
+
+NODE_MODULE_CONTEXT_AWARE_INTERNAL(watchdog, node::watchdog::Initialize);
diff --git a/src/node_watchdog.h b/src/node_watchdog.h
index 0fc133a96c4..ec44c09f517 100644
--- a/src/node_watchdog.h
+++ b/src/node_watchdog.h
@@ -24,9 +24,12 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
-#include "uv.h"
-#include "node_mutex.h"
#include <vector>
+#include "handle_wrap.h"
+#include "memory_tracker-inl.h"
+#include "node_mutex.h"
+#include "uv.h"
+#include "v8.h"
#ifdef __POSIX__
#include <pthread.h>
@@ -34,6 +37,11 @@
namespace node {
+enum class SignalPropagation {
+ kContinuePropagation,
+ kStopPropagation,
+};
+
class Watchdog {
public:
explicit Watchdog(v8::Isolate* isolate,
@@ -54,24 +62,56 @@ class Watchdog {
bool* timed_out_;
};
-class SigintWatchdog {
+class SigintWatchdogBase {
+ public:
+ virtual ~SigintWatchdogBase() = default;
+ virtual SignalPropagation HandleSigint() = 0;
+};
+
+class SigintWatchdog : public SigintWatchdogBase {
public:
explicit SigintWatchdog(v8::Isolate* isolate,
bool* received_signal = nullptr);
~SigintWatchdog();
v8::Isolate* isolate() { return isolate_; }
- void HandleSigint();
+ SignalPropagation HandleSigint() override;
private:
v8::Isolate* isolate_;
bool* received_signal_;
};
+class TraceSigintWatchdog : public HandleWrap, public SigintWatchdogBase {
+ public:
+ static void Init(Environment* env, Local<v8::Object> target);
+ static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void Start(const v8::FunctionCallbackInfo<Value>& args);
+ static void Stop(const v8::FunctionCallbackInfo<Value>& args);
+
+ SignalPropagation HandleSigint() override;
+
+ inline void MemoryInfo(node::MemoryTracker* tracker) const override {
+ tracker->TrackInlineField("handle_", handle_);
+ }
+ SET_MEMORY_INFO_NAME(TraceSigintWatchdog)
+ SET_SELF_SIZE(TraceSigintWatchdog)
+
+ private:
+ enum class SignalFlags { None, FromIdle, FromInterrupt };
+
+ TraceSigintWatchdog(Environment* env, Local<v8::Object> object);
+ void HandleInterrupt();
+
+ bool interrupting = false;
+ uv_async_t handle_;
+ SignalFlags signal_flag_ = SignalFlags::None;
+};
+
class SigintWatchdogHelper {
public:
static SigintWatchdogHelper* GetInstance() { return &instance; }
- void Register(SigintWatchdog* watchdog);
- void Unregister(SigintWatchdog* watchdog);
+ void Register(SigintWatchdogBase* watchdog);
+ void Unregister(SigintWatchdogBase* watchdog);
bool HasPendingSignal();
int Start();
@@ -88,7 +128,7 @@ class SigintWatchdogHelper {
Mutex mutex_;
Mutex list_mutex_;
- std::vector<SigintWatchdog*> watchdogs_;
+ std::vector<SigintWatchdogBase*> watchdogs_;
bool has_pending_signal_;
#ifdef __POSIX__
diff --git a/test/pseudo-tty/test-trace-sigint-disabled.js b/test/pseudo-tty/test-trace-sigint-disabled.js
new file mode 100644
index 00000000000..5dc4dfa9b36
--- /dev/null
+++ b/test/pseudo-tty/test-trace-sigint-disabled.js
@@ -0,0 +1,39 @@
+'use strict';
+
+const { mustCall } = require('../common');
+const childProcess = require('child_process');
+const assert = require('assert');
+
+if (process.env.CHILD === 'true') {
+ main();
+} else {
+ // Use inherited stdio child process to prevent test tools from determining
+ // the case as crashed from SIGINT
+ const cp = childProcess.spawn(
+ process.execPath,
+ ['--trace-sigint', __filename],
+ {
+ env: { ...process.env, CHILD: 'true' },
+ stdio: 'inherit'
+ });
+ cp.on('exit', mustCall((code, signal) => {
+ assert.strictEqual(signal, null);
+ assert.strictEqual(code, 0);
+ }));
+}
+
+function main() {
+ // Deactivate colors even if the tty does support colors.
+ process.env.NODE_DISABLE_COLORS = '1';
+
+ const noop = mustCall(() => {
+ process.exit(0);
+ });
+ process.on('SIGINT', noop);
+ // Try testing re-add 'SIGINT' listeners
+ process.removeListener('SIGINT', noop);
+ process.on('SIGINT', noop);
+
+ process.kill(process.pid, 'SIGINT');
+ setTimeout(() => { assert.fail('unreachable path'); }, 10 * 1000);
+}
diff --git a/test/pseudo-tty/test-trace-sigint-disabled.out b/test/pseudo-tty/test-trace-sigint-disabled.out
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/test/pseudo-tty/test-trace-sigint-disabled.out
diff --git a/test/pseudo-tty/test-trace-sigint-on-idle.js b/test/pseudo-tty/test-trace-sigint-on-idle.js
new file mode 100644
index 00000000000..398aa8df097
--- /dev/null
+++ b/test/pseudo-tty/test-trace-sigint-on-idle.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const { mustCall } = require('../common');
+const childProcess = require('child_process');
+const assert = require('assert');
+
+if (process.env.CHILD === 'true') {
+ main();
+} else {
+ // Use inherited stdio child process to prevent test tools from determining
+ // the case as crashed from SIGINT
+ const cp = childProcess.spawn(
+ process.execPath,
+ ['--trace-sigint', __filename],
+ {
+ env: { ...process.env, CHILD: 'true' },
+ stdio: 'inherit'
+ });
+ setTimeout(() => cp.kill('SIGINT'), 1 * 1000);
+ cp.on('exit', mustCall((code, signal) => {
+ assert.strictEqual(signal, 'SIGINT');
+ assert.strictEqual(code, null);
+ }));
+}
+
+function main() {
+ // Deactivate colors even if the tty does support colors.
+ process.env.NODE_DISABLE_COLORS = '1';
+ setTimeout(() => {}, 10 * 1000);
+}
diff --git a/test/pseudo-tty/test-trace-sigint-on-idle.out b/test/pseudo-tty/test-trace-sigint-on-idle.out
new file mode 100644
index 00000000000..faaef62fc79
--- /dev/null
+++ b/test/pseudo-tty/test-trace-sigint-on-idle.out
@@ -0,0 +1 @@
+KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`
diff --git a/test/pseudo-tty/test-trace-sigint.js b/test/pseudo-tty/test-trace-sigint.js
new file mode 100644
index 00000000000..8b539bfc88d
--- /dev/null
+++ b/test/pseudo-tty/test-trace-sigint.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const { mustCall } = require('../common');
+const childProcess = require('child_process');
+const assert = require('assert');
+
+if (process.env.CHILD === 'true') {
+ main();
+} else {
+ // Use inherited stdio child process to prevent test tools from determining
+ // the case as crashed from SIGINT
+ const cp = childProcess.spawn(
+ process.execPath,
+ ['--trace-sigint', __filename],
+ {
+ env: { ...process.env, CHILD: 'true' },
+ stdio: 'inherit'
+ });
+ cp.on('exit', mustCall((code, signal) => {
+ assert.strictEqual(signal, 'SIGINT');
+ assert.strictEqual(code, null);
+ }));
+}
+
+function main() {
+ // Deactivate colors even if the tty does support colors.
+ process.env.NODE_DISABLE_COLORS = '1';
+ process.kill(process.pid, 'SIGINT');
+ while (true) {}
+}
diff --git a/test/pseudo-tty/test-trace-sigint.out b/test/pseudo-tty/test-trace-sigint.out
new file mode 100644
index 00000000000..956cbafccc2
--- /dev/null
+++ b/test/pseudo-tty/test-trace-sigint.out
@@ -0,0 +1,9 @@
+KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`
+ at main (*/test-trace-sigint.js:*)
+ at */test-trace-sigint.js:*
+ at *
+ at *
+ at *
+ at *
+ at *
+ at *
diff --git a/test/sequential/test-async-wrap-getasyncid.js b/test/sequential/test-async-wrap-getasyncid.js
index 7e9f77cd7a4..8e532ec48c3 100644
--- a/test/sequential/test-async-wrap-getasyncid.js
+++ b/test/sequential/test-async-wrap-getasyncid.js
@@ -51,6 +51,7 @@ const { getSystemErrorName } = require('util');
delete providers.HTTPCLIENTREQUEST;
delete providers.HTTPINCOMINGMESSAGE;
delete providers.ELDHISTOGRAM;
+ delete providers.SIGINTWATCHDOG;
const objKeys = Object.keys(providers);
if (objKeys.length > 0)