From 51ad99b49b2695e4d64d11cb414fc432c38ecef2 Mon Sep 17 00:00:00 2001 From: legendecas <legendecas@gmail.com> Date: Mon, 19 Aug 2019 21:03:08 +0800 Subject: [PATCH] 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> --- doc/api/cli.md | 8 + doc/node.1 | 2 + lib/internal/bootstrap/pre_execution.js | 13 ++ lib/internal/watchdog.js | 59 +++++++ node.gyp | 1 + src/async_wrap.h | 1 + src/memory_tracker-inl.h | 14 ++ src/memory_tracker.h | 7 + src/node_binding.cc | 1 + src/node_options.cc | 5 + src/node_options.h | 1 + src/node_watchdog.cc | 144 +++++++++++++++++- src/node_watchdog.h | 54 ++++++- test/pseudo-tty/test-trace-sigint-disabled.js | 39 +++++ .../pseudo-tty/test-trace-sigint-disabled.out | 0 test/pseudo-tty/test-trace-sigint-on-idle.js | 30 ++++ test/pseudo-tty/test-trace-sigint-on-idle.out | 1 + test/pseudo-tty/test-trace-sigint.js | 30 ++++ test/pseudo-tty/test-trace-sigint.out | 9 ++ test/sequential/test-async-wrap-getasyncid.js | 1 + 20 files changed, 405 insertions(+), 15 deletions(-) create mode 100644 lib/internal/watchdog.js create mode 100644 test/pseudo-tty/test-trace-sigint-disabled.js create mode 100644 test/pseudo-tty/test-trace-sigint-disabled.out create mode 100644 test/pseudo-tty/test-trace-sigint-on-idle.js create mode 100644 test/pseudo-tty/test-trace-sigint-on-idle.out create mode 100644 test/pseudo-tty/test-trace-sigint.js create mode 100644 test/pseudo-tty/test-trace-sigint.out diff --git a/doc/api/cli.md b/doc/api/cli.md index 821f82be1d2a6e..731ddfbc95bd48 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -820,6 +820,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 @@ -1149,6 +1156,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 8c9abd48435c6c..93ccf7f721a3c5 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -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. diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 1f4d5c04b9b9d5..46af6c30e0daa8 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 00000000000000..6a5b772111f4d6 --- /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 784ce677c3d081..ad2d1159b6e099 100644 --- a/node.gyp +++ b/node.gyp @@ -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', diff --git a/src/async_wrap.h b/src/async_wrap.h index 521560c795768e..66de5f48aa0b7a 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 2e39fa21a944d8..97be794bb86ba5 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) { @@ -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, diff --git a/src/memory_tracker.h b/src/memory_tracker.h index a400009aad6728..6cd372f493c41d 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, typename D> inline void TrackField(const char* edge_name, @@ -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, diff --git a/src/node_binding.cc b/src/node_binding.cc index 0facfc36cbcb78..9f9695c2251b2e 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -88,6 +88,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 d6b4ebe396cfeb..8f887404a03ce6 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -775,6 +775,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); } diff --git a/src/node_options.h b/src/node_options.h index 571ac305f9990c..6689a7292fa8c9 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -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; diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index f11902a1fa44bc..5cce8ffcb7b3b8 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__ @@ -162,8 +275,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; } @@ -259,15 +377,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); @@ -302,4 +418,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 14c5e19cca5c9a..bd960cb1ec8b18 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 00000000000000..5dc4dfa9b36afa --- /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 00000000000000..e69de29bb2d1d6 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 00000000000000..398aa8df097d2b --- /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 00000000000000..faaef62fc79f70 --- /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 00000000000000..8b539bfc88d7b8 --- /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 00000000000000..956cbafccc2d19 --- /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 7e9f77cd7a4cc2..8e532ec48c308d 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)