diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index 79a8f896f1533e..c6801645f952ee 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -193,11 +193,11 @@ See [`Intl.Segmenter`](https://github.com/tc39/proposal-intl-segmenter). ### DEP0018: Unhandled promise rejections -Type: Runtime +Type: End-of-Life -Unhandled promise rejections are deprecated. In the future, promise rejections -that are not handled will terminate the Node.js process with a non-zero exit -code. +Any unhandled promise rejection that is garbage collected is going to exit the +process similar to unhandled exceptions. Please make sure to always handle all +possible rejections. ### DEP0019: require('.') resolved outside directory diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index 3ce558611e46e1..21ddb09b52674e 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -359,7 +359,7 @@ const { clearIdStack, asyncIdStackSize } = async_wrap; const { kAfter, kCurrentAsyncId, kInitTriggerId } = async_wrap.constants; - process._fatalException = function(er) { + process._fatalException = function(er, fromPromise) { var caught; // It's possible that kInitTriggerId was set for a constructor call that @@ -369,7 +369,7 @@ if (process.domain && process.domain._errorHandler) caught = process.domain._errorHandler(er); - if (!caught) + if (!caught && !fromPromise) caught = process.emit('uncaughtException', er); // If someone handled it, then great. otherwise, die in C++ land diff --git a/lib/internal/process/promises.js b/lib/internal/process/promises.js index 663e6f5fec6985..204057720f97c7 100644 --- a/lib/internal/process/promises.js +++ b/lib/internal/process/promises.js @@ -2,21 +2,13 @@ const { safeToString } = process.binding('util'); -const promiseRejectEvent = process._promiseRejectEvent; -const hasBeenNotifiedProperty = new WeakMap(); -const promiseToGuidProperty = new WeakMap(); -const pendingUnhandledRejections = []; -let lastPromiseId = 1; - -exports.setup = setupPromises; - -function getAsynchronousRejectionWarningObject(uid) { - return new Error('Promise rejection was handled ' + - `asynchronously (rejection id: ${uid})`); -} - -function setupPromises(scheduleMicrotasks) { - let deprecationWarned = false; +exports.setup = function setup(scheduleMicrotasks) { + const promiseRejectEvent = process._promiseRejectEvent; + const hasBeenNotifiedProperty = new Map(); + const promiseToGuidProperty = new Map(); + const pendingUnhandledRejections = []; + const promiseInternals = {}; + let lastPromiseId = 1; process._setupPromises(function(event, promise, reason) { if (event === promiseRejectEvent.unhandled) @@ -25,7 +17,7 @@ function setupPromises(scheduleMicrotasks) { rejectionHandled(promise); else require('assert').fail(null, null, 'unexpected PromiseRejectEvent'); - }); + }, promiseInternals); function unhandledRejection(promise, reason) { hasBeenNotifiedProperty.set(promise, false); @@ -35,34 +27,36 @@ function setupPromises(scheduleMicrotasks) { function rejectionHandled(promise) { const hasBeenNotified = hasBeenNotifiedProperty.get(promise); - if (hasBeenNotified !== undefined) { - hasBeenNotifiedProperty.delete(promise); + hasBeenNotifiedProperty.delete(promise); + if (hasBeenNotified) { const uid = promiseToGuidProperty.get(promise); promiseToGuidProperty.delete(promise); - if (hasBeenNotified === true) { - let warning = null; - if (!process.listenerCount('rejectionHandled')) { - // Generate the warning object early to get a good stack trace. - warning = getAsynchronousRejectionWarningObject(uid); - } - process.nextTick(function() { - if (!process.emit('rejectionHandled', promise)) { - if (warning === null) - warning = getAsynchronousRejectionWarningObject(uid); - warning.name = 'PromiseRejectionHandledWarning'; - warning.id = uid; - process.emitWarning(warning); - } - }); + let warning = null; + if (!process.listenerCount('rejectionHandled')) { + // Generate the warning object early to get a good stack trace. + warning = new Error('Promise rejection was handled ' + + `asynchronously (rejection id: ${uid})`); } - + promiseInternals.untrackPromise(promise); + process.nextTick(function() { + if (!process.emit('rejectionHandled', promise)) { + if (warning === null) + warning = new Error('Promise rejection was handled ' + + `asynchronously (rejection id: ${uid})`); + warning.name = 'PromiseRejectionHandledWarning'; + warning.id = uid; + process.emitWarning(warning); + } + }); + } else { + promiseToGuidProperty.delete(promise); } } - function emitWarning(uid, reason) { - const warning = new Error( - `Unhandled promise rejection (rejection id: ${uid}): ` + - safeToString(reason)); + function emitWarning(promise, reason) { + const uid = promiseToGuidProperty.get(promise); + const warning = new Error('Unhandled promise rejection ' + + `(rejection id: ${uid}): ${safeToString(reason)}`); warning.name = 'UnhandledPromiseRejectionWarning'; warning.id = uid; try { @@ -73,14 +67,6 @@ function setupPromises(scheduleMicrotasks) { // ignored } process.emitWarning(warning); - if (!deprecationWarned) { - deprecationWarned = true; - process.emitWarning( - 'Unhandled promise rejections are deprecated. In the future, ' + - 'promise rejections that are not handled will terminate the ' + - 'Node.js process with a non-zero exit code.', - 'DeprecationWarning', 'DEP0018'); - } } function emitPendingUnhandledRejections() { @@ -90,9 +76,9 @@ function setupPromises(scheduleMicrotasks) { const reason = pendingUnhandledRejections.shift(); if (hasBeenNotifiedProperty.get(promise) === false) { hasBeenNotifiedProperty.set(promise, true); - const uid = promiseToGuidProperty.get(promise); if (!process.emit('unhandledRejection', reason, promise)) { - emitWarning(uid, reason); + promiseInternals.trackPromise(promise); + emitWarning(promise, reason); } else { hadListeners = true; } @@ -107,4 +93,4 @@ function setupPromises(scheduleMicrotasks) { } return emitPendingUnhandledRejections; -} +}; diff --git a/node.gyp b/node.gyp index 338b7f324e4f6a..37f30a0b976203 100644 --- a/node.gyp +++ b/node.gyp @@ -228,6 +228,7 @@ 'src/stream_wrap.cc', 'src/tcp_wrap.cc', 'src/timer_wrap.cc', + 'src/track-promise.cc', 'src/tracing/agent.cc', 'src/tracing/node_trace_buffer.cc', 'src/tracing/node_trace_writer.cc', @@ -270,6 +271,8 @@ 'src/node_revert.h', 'src/node_i18n.h', 'src/pipe_wrap.h', + 'src/track-promise.h', + 'src/track-promise-inl.h', 'src/tty_wrap.h', 'src/tcp_wrap.h', 'src/udp_wrap.h', @@ -692,6 +695,7 @@ '<(OBJ_TRACING_PATH)<(OBJ_SEPARATOR)node_trace_buffer.<(OBJ_SUFFIX)', '<(OBJ_TRACING_PATH)<(OBJ_SEPARATOR)node_trace_writer.<(OBJ_SUFFIX)', '<(OBJ_TRACING_PATH)<(OBJ_SEPARATOR)trace_event.<(OBJ_SUFFIX)', + '<(OBJ_PATH)<(OBJ_SEPARATOR)track-promise.<(OBJ_SUFFIX)', ], }], ['v8_enable_inspector==1', { diff --git a/src/env-inl.h b/src/env-inl.h index 28512003f0fe91..af8a7c0d29369d 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -280,7 +280,8 @@ inline Environment* Environment::GetCurrent( inline Environment::Environment(IsolateData* isolate_data, v8::Local context) - : isolate_(context->GetIsolate()), + : promise_tracker_(this), + isolate_(context->GetIsolate()), isolate_data_(isolate_data), async_hooks_(context->GetIsolate()), timer_base_(uv_now(isolate_data->event_loop())), diff --git a/src/env.h b/src/env.h index 25db2e14e9f346..dd2fd1cc65614b 100644 --- a/src/env.h +++ b/src/env.h @@ -31,6 +31,7 @@ #endif #include "handle_wrap.h" #include "req-wrap.h" +#include "track-promise.h" #include "util.h" #include "uv.h" #include "v8.h" @@ -236,6 +237,7 @@ class ModuleWrap; V(preference_string, "preference") \ V(priority_string, "priority") \ V(produce_cached_data_string, "produceCachedData") \ + V(promise_rejection_index_string, "_promiseRejectionIndex") \ V(raw_string, "raw") \ V(read_host_object_string, "_readHostObject") \ V(readable_string, "readable") \ @@ -294,6 +296,7 @@ class ModuleWrap; V(zero_return_string, "ZERO_RETURN") #define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ + V(array_from, v8::Function) \ V(as_external, v8::External) \ V(async_hooks_destroy_function, v8::Function) \ V(async_hooks_init_function, v8::Function) \ @@ -312,8 +315,9 @@ class ModuleWrap; V(performance_entry_callback, v8::Function) \ V(performance_entry_template, v8::Function) \ V(process_object, v8::Object) \ - V(promise_reject_function, v8::Function) \ V(promise_wrap_template, v8::ObjectTemplate) \ + V(promise_unhandled_rejection_function, v8::Function) \ + V(promise_unhandled_rejection, v8::Function) \ V(push_values_to_array_function, v8::Function) \ V(randombytes_constructor_template, v8::ObjectTemplate) \ V(script_context_constructor_template, v8::FunctionTemplate) \ @@ -678,6 +682,9 @@ class Environment { bool RemovePromiseHook(promise_hook_func fn, void* arg); bool EmitNapiWarning(); + PromiseTracker promise_tracker_; + int64_t promise_tracker_index_ = 0; + private: inline void ThrowError(v8::Local (*fun)(v8::Local), const char* errmsg); diff --git a/src/node.cc b/src/node.cc index 541c5f5eec641f..538b64144c482f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -65,6 +65,8 @@ #include "req-wrap-inl.h" #include "string_bytes.h" #include "tracing/agent.h" +#include "track-promise.h" +#include "track-promise-inl.h" #include "util.h" #include "uv.h" #if NODE_USE_V8_PLATFORM @@ -90,6 +92,7 @@ #include #include +#include #if defined(NODE_HAVE_I18N_SUPPORT) #include @@ -133,6 +136,7 @@ using v8::Array; using v8::ArrayBuffer; using v8::Boolean; using v8::Context; +using v8::Debug; using v8::EscapableHandleScope; using v8::Exception; using v8::Float64Array; @@ -158,6 +162,7 @@ using v8::PromiseRejectMessage; using v8::PropertyCallbackInfo; using v8::ScriptOrigin; using v8::SealHandleScope; +using v8::Set; using v8::String; using v8::TryCatch; using v8::Uint32Array; @@ -1299,6 +1304,49 @@ void SetupNextTick(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count)); } +} // anonymous namespace + + +Local GetPromiseReason(Environment* env, Local promise) { + Local internal_props = + Debug::GetInternalProperties(env->isolate(), + promise).ToLocalChecked().As(); + + return internal_props->Get(3); +} + + +void TrackPromise(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsObject()); + Local promise = args[0].As(); + + promise->Set(env->context(), + env->promise_rejection_index_string(), + Number::New(env->isolate(), env->promise_tracker_index_++)) + .FromJust(); + + // Make some sort of list size check so as to not leak memory. + if (env->promise_tracker_.Size() > 10000) { + // XXX(Fishrock123): Do some intelligent logic here? + return; + } + + env->promise_tracker_.TrackPromise(promise); +} + + +void UntrackPromise(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsObject()); + Local promise = args[0].As(); + + env->promise_tracker_.UntrackPromise(promise); +} + + void PromiseRejectCallback(PromiseRejectMessage message) { Local promise = message.GetPromise(); Isolate* isolate = promise->GetIsolate(); @@ -1306,7 +1354,7 @@ void PromiseRejectCallback(PromiseRejectMessage message) { Local event = Integer::New(isolate, message.GetEvent()); Environment* env = Environment::GetCurrent(isolate); - Local callback = env->promise_reject_function(); + Local callback = env->promise_unhandled_rejection_function(); if (value.IsEmpty()) value = Undefined(isolate); @@ -1317,22 +1365,25 @@ void PromiseRejectCallback(PromiseRejectMessage message) { callback->Call(process, arraysize(args), args); } + void SetupPromises(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); CHECK(args[0]->IsFunction()); + CHECK(args[1]->IsObject()); isolate->SetPromiseRejectCallback(PromiseRejectCallback); - env->set_promise_reject_function(args[0].As()); + env->set_promise_unhandled_rejection_function(args[0].As()); + + env->SetMethod(args[1].As(), "trackPromise", TrackPromise); + env->SetMethod(args[1].As(), "untrackPromise", UntrackPromise); env->process_object()->Delete( env->context(), FIXED_ONE_BYTE_STRING(isolate, "_setupPromises")).FromJust(); } -} // anonymous namespace - void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) { Environment* env = Environment::GetCurrent(isolate); @@ -1805,10 +1856,9 @@ void AppendExceptionLine(Environment* env, arrow_str).FromMaybe(false)); } - -static void ReportException(Environment* env, - Local er, - Local message) { +void ReportException(Environment* env, + Local er, + Local message) { HandleScope scope(env->isolate()); AppendExceptionLine(env, er, message, FATAL_ERROR); @@ -2745,6 +2795,14 @@ NO_RETURN void FatalError(const char* location, const char* message) { void FatalException(Isolate* isolate, Local error, Local message) { + InternalFatalException(isolate, error, message, false); +} + + +void InternalFatalException(Isolate* isolate, + Local error, + Local message, + bool from_promise) { HandleScope scope(isolate); Environment* env = Environment::GetCurrent(isolate); @@ -2767,9 +2825,12 @@ void FatalException(Isolate* isolate, // Do not call FatalException when _fatalException handler throws fatal_try_catch.SetVerbose(false); + Local argv[2] = { error, + Boolean::New(env->isolate(), from_promise) }; + // this will return true if the JS layer handled it, false otherwise Local caught = - fatal_exception_function->Call(process_object, 1, &error); + fatal_exception_function->Call(process_object, 2, argv); if (fatal_try_catch.HasCaught()) { // the fatal exception function threw, so we must exit @@ -4724,6 +4785,31 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, PERFORMANCE_MARK(&env, LOOP_EXIT); } + Local oldest_rejected_promise; + int64_t lowest_rejection_index = -1; + + env.promise_tracker_.ForEach([&](Environment* env, Local promise) { + Local index_ = + promise->Get(env->context(), env->promise_rejection_index_string()) + .ToLocalChecked(); + CHECK(index_->IsNumber()); + int64_t index = index_->IntegerValue(env->context()).FromJust(); + if (index < lowest_rejection_index || lowest_rejection_index == -1) { + oldest_rejected_promise = promise; + lowest_rejection_index = index; + } + }); + + if (!oldest_rejected_promise.IsEmpty()) { + Local orp = oldest_rejected_promise.As(); + Local err = GetPromiseReason(&env, orp); + Local message = Exception::CreateMessage(isolate, err); + + // XXX(Fishrock123): Should this just call ReportException and + // set exit_code = 1 instead? + InternalFatalException(isolate, err, message, true); + } + env.set_trace_sync_io(false); const int exit_code = EmitExit(&env); diff --git a/src/node_internals.h b/src/node_internals.h index fd8cc26a2881ca..229b2ada027c2c 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -183,6 +183,19 @@ constexpr size_t arraysize(const T(&)[N]) { return N; } bool IsExceptionDecorated(Environment* env, v8::Local er); enum ErrorHandlingMode { FATAL_ERROR, CONTEXTIFY_ERROR }; + +v8::Local GetPromiseReason(Environment* env, + v8::Local promise); + +void InternalFatalException(v8::Isolate* isolate, + v8::Local error, + v8::Local message, + bool from_promise); + +void ReportException(Environment* env, + v8::Local er, + v8::Local message); + void AppendExceptionLine(Environment* env, v8::Local er, v8::Local message, diff --git a/src/track-promise-inl.h b/src/track-promise-inl.h new file mode 100644 index 00000000000000..073dbafd8903da --- /dev/null +++ b/src/track-promise-inl.h @@ -0,0 +1,23 @@ +#ifndef SRC_TRACK_PROMISE_INL_H_ +#define SRC_TRACK_PROMISE_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "track-promise.h" +#include "env.h" + +namespace node { + +template +void PromiseTracker::ForEach(Iterator fn) { + for (auto it = set_.begin(); it != set_.end(); ++it) { + v8::Local object = (*it)->Get(env_->isolate()); + fn(env_, object); + } +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_TRACK_PROMISE_INL_H_ diff --git a/src/track-promise.cc b/src/track-promise.cc new file mode 100644 index 00000000000000..1e1b5cc17928c5 --- /dev/null +++ b/src/track-promise.cc @@ -0,0 +1,71 @@ +#include "track-promise.h" +#include "env.h" +#include "env-inl.h" +#include "node_internals.h" + +namespace node { + +using v8::Exception; +using v8::Isolate; +using v8::Local; +using v8::Message; +using v8::Object; +using v8::Persistent; +using v8::Value; +using v8::WeakCallbackInfo; +using v8::WeakCallbackType; + +size_t v8ObjectHash::operator() (Persistent* handle) const { + Local object = handle->Get(env->isolate()); + return static_cast(object->GetIdentityHash()); +} + +bool v8ObjectEquals::operator() (Persistent* lhs, + Persistent* rhs) const { + Local lhs_ = lhs->Get(env->isolate()); + Local rhs_ = rhs->Get(env->isolate()); + return lhs_->StrictEquals(rhs_); +} + +void PromiseTracker::TrackPromise(Local promise) { + Persistent* p = new Persistent(env_->isolate(), promise); + set_.insert(p); + p->SetWeak(p, WeakCallback, WeakCallbackType::kFinalizer); + p->MarkIndependent(); +} + +void PromiseTracker::UntrackPromise(Local promise) { + Persistent p(env_->isolate(), promise); + auto it = set_.find(&p); + if (it != set_.end()) { + (*it)->Reset(); + set_.erase(it); + delete *it; + } + p.Reset(); +} + +bool PromiseTracker::HasPromise(Local promise) { + Persistent p(env_->isolate(), promise); + bool found = set_.find(&p) != set_.end(); + p.Reset(); + return found; +} + +void PromiseTracker::WeakCallback( + const v8::WeakCallbackInfo>& data) { + Environment* env = Environment::GetCurrent(data.GetIsolate()); + env->promise_tracker_.WeakCallback(data.GetParameter()); +} + +void PromiseTracker::WeakCallback(v8::Persistent* persistent_) { + Local promise = persistent_->Get(env_->isolate()); + CHECK(HasPromise(promise)); + Local err = node::GetPromiseReason(env_, promise); + Local message = Exception::CreateMessage(env_->isolate(), err); + + node::InternalFatalException(env_->isolate(), err, message, true); + UntrackPromise(promise); +} + +} // namespace node diff --git a/src/track-promise.h b/src/track-promise.h new file mode 100644 index 00000000000000..b2e51d52c53bc8 --- /dev/null +++ b/src/track-promise.h @@ -0,0 +1,57 @@ +#ifndef SRC_TRACK_PROMISE_H_ +#define SRC_TRACK_PROMISE_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" + +#include +#include + +namespace node { + +class Environment; + +struct v8ObjectHash { + size_t operator() (v8::Persistent* handle) const; + + Environment* env; +}; + +struct v8ObjectEquals { + bool operator() (v8::Persistent* lhs, + v8::Persistent* rhs) const; + + Environment* env; +}; + +class PromiseTracker { + public: + explicit inline PromiseTracker(Environment* env) + : env_(env), set_(0, v8ObjectHash({env}), v8ObjectEquals({env})) { } + + void TrackPromise(v8::Local promise); + void UntrackPromise(v8::Local promise); + bool HasPromise(v8::Local promise); + + template + inline void ForEach(Iterator fn); + + inline size_t Size() const { return set_.size(); } + + static void WeakCallback( + const v8::WeakCallbackInfo>& data); + private: + inline void WeakCallback(v8::Persistent* promise); + + Environment* env_; + std::unordered_set*, + v8ObjectHash, + v8ObjectEquals> set_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_TRACK_PROMISE_H_ diff --git a/test/addons-napi/test_promise/test.js b/test/addons-napi/test_promise/test.js index 4c2a2e5e76c9fb..00d6aee3412d09 100644 --- a/test/addons-napi/test_promise/test.js +++ b/test/addons-napi/test_promise/test.js @@ -6,6 +6,8 @@ const assert = require('assert'); let expected_result, promise; +process.on('unhandledRejection', () => {}); + // A resolution expected_result = 42; promise = test_promise.createPromise(); diff --git a/test/message/promise_fast_handled_reject.js b/test/message/promise_fast_handled_reject.js new file mode 100644 index 00000000000000..93e339de90f48c --- /dev/null +++ b/test/message/promise_fast_handled_reject.js @@ -0,0 +1,30 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const p1 = new Promise((res, rej) => { + throw new Error('One'); +}); + +new Promise((res, rej) => { + throw new Error('Two'); +}); + +const p3 = new Promise((res, rej) => { + throw new Error('Three'); +}); + +process.nextTick(() => { + setTimeout(common.mustCall(() => { + p1.catch(() => {}); + p3.catch(() => {}); + }), 1); +}); + +process.on('rejectionHandled', () => {}); // Ignore + +process.on('uncaughtException', (err) => + assert.fail('Should not trigger uncaught exception')); + +process.on('exit', () => process._rawDebug('exit event emitted')); diff --git a/test/message/promise_fast_handled_reject.out b/test/message/promise_fast_handled_reject.out new file mode 100644 index 00000000000000..4cf1c66870105f --- /dev/null +++ b/test/message/promise_fast_handled_reject.out @@ -0,0 +1,19 @@ +(node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): One +(node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Two +(node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): Three +exit event emitted +*test*message*promise_fast_handled_reject.js:* + throw new Error('Two'); + ^ + +Error: Two + at *test*message*promise_fast_handled_reject.js:*:* + at Promise () + at Object. (*test*message*promise_fast_handled_reject.js:*:*) + at Module._compile (module.js:*:*) + at Object.Module._extensions..js (module.js:*:*) + at Module.load (module.js:*:*) + at tryModuleLoad (module.js:*:*) + at Function.Module._load (module.js:*:*) + at Function.Module.runMain (module.js:*:*) + at startup (bootstrap_node.js:*:*) diff --git a/test/message/promise_fast_reject.js b/test/message/promise_fast_reject.js new file mode 100644 index 00000000000000..dc8723c54cf7a3 --- /dev/null +++ b/test/message/promise_fast_reject.js @@ -0,0 +1,20 @@ +'use strict'; + +// We should always have the stacktrace of the oldest rejection. +// Theoretically the GC could handle this differently. + +require('../common'); +const assert = require('assert'); + +new Promise(function(res, rej) { + throw new Error('One'); +}); + +new Promise(function(res, rej) { + throw new Error('Two'); +}); + +process.on('uncaughtException', (err) => + assert.fail('Should not trigger uncaught exception')); + +process.on('exit', () => process._rawDebug('exit event emitted')); diff --git a/test/message/promise_fast_reject.out b/test/message/promise_fast_reject.out new file mode 100644 index 00000000000000..018b8eb74d0c4c --- /dev/null +++ b/test/message/promise_fast_reject.out @@ -0,0 +1,18 @@ +(node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): ReferenceError: One +(node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): ReferenceError: Two +exit event emitted +*test*message*promise_fast_reject.js:* + throw new Error('One'); + ^ + +Error: One + at *test*message*promise_fast_reject.js:*:* + at Promise () + at Object. (*test*message*promise_fast_reject.js:*:*) + at Module._compile (module.js:*:*) + at Object.Module._extensions..js (module.js:*:*) + at Module.load (module.js:*:*) + at tryModuleLoad (module.js:*:*) + at Function.Module._load (module.js:*:*) + at Function.Module.runMain (module.js:*:*) + at startup (bootstrap_node.js:*:*) diff --git a/test/message/promise_reject.js b/test/message/promise_reject.js new file mode 100644 index 00000000000000..10e943a44422b3 --- /dev/null +++ b/test/message/promise_reject.js @@ -0,0 +1,22 @@ +// Flags: --expose-gc + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +Promise.reject(new Error('oops')); + +// Manually call GC due to possible memory constraints with attempting to +// trigger it "naturally". +setTimeout(common.mustCall(() => { + global.gc(); + global.gc(); + global.gc(); +}), 2); + +process.on('uncaughtException', (err) => { + assert.fail('Should not trigger uncaught exception'); +}); + +process.on('exit', () => process._rawDebug('exit event emitted')); diff --git a/test/message/promise_reject.out b/test/message/promise_reject.out new file mode 100644 index 00000000000000..0ee3fd1709e31a --- /dev/null +++ b/test/message/promise_reject.out @@ -0,0 +1,16 @@ +(node:*) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: oops +exit event emitted +*test*message*promise_reject.js:* +Promise.reject(new Error('oops')); + ^ + +Error: oops + at *test*message*promise_reject.js:*:* + at Module._compile (module.js:*:*) + at Object.Module._extensions..js (module.js:*:*) + at Module.load (module.js:*:*) + at tryModuleLoad (module.js:*:*) + at Function.Module._load (module.js:*:*) + at Function.Module.runMain (module.js:*:*) + at startup (bootstrap_node.js:*:*) + at bootstrap_node.js:*:* diff --git a/test/message/unhandled_promise_trace_warnings.out b/test/message/unhandled_promise_trace_warnings.out index 0cf65dcdbb82a5..a97dc67604f4cd 100644 --- a/test/message/unhandled_promise_trace_warnings.out +++ b/test/message/unhandled_promise_trace_warnings.out @@ -8,18 +8,8 @@ at * at * at * -(node:*) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. - at * - at * - at * - at * - at * - at * - at * - at * (node:*) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1) - at getAsynchronousRejectionWarningObject (internal/process/promises.js:*) - at rejectionHandled (internal/process/promises.js:*) + at * at * at Promise.then * at Promise.catch * diff --git a/test/parallel/test-promises-gc-before-handled.js b/test/parallel/test-promises-gc-before-handled.js new file mode 100644 index 00000000000000..1920eb246c39b8 --- /dev/null +++ b/test/parallel/test-promises-gc-before-handled.js @@ -0,0 +1,24 @@ +// Flags: --expose-gc + +'use strict'; +const common = require('../common'); + +const p = new Promise((res, rej) => { + throw new Error('oops'); +}); + +// Manually call GC due to possible memory constraints with attempting to +// trigger it "naturally". +setTimeout(common.mustCall(() => { + global.gc(); + global.gc(); + global.gc(); + setTimeout(common.mustCall(() => { + global.gc(); + global.gc(); + global.gc(); + setTimeout(common.mustCall(() => { + p.catch(() => {}); + }, 1), 250); + }), 20); +}), 20); diff --git a/test/parallel/test-promises-handled-reject.js b/test/parallel/test-promises-handled-reject.js new file mode 100644 index 00000000000000..366d784645fd6d --- /dev/null +++ b/test/parallel/test-promises-handled-reject.js @@ -0,0 +1,17 @@ +// Flags: --expose-gc + +'use strict'; +const common = require('../common'); + +const p = new Promise((res, rej) => { + throw new Error('oops'); +}); + +// Manually call GC due to possible memory constraints with attempting to +// trigger it "naturally". +setTimeout(common.mustCall(() => { + p.catch(() => {}); + global.gc(); + global.gc(); + global.gc(); +}, 1), 2); diff --git a/test/parallel/test-promises-unhandled-proxy-rejections.js b/test/parallel/test-promises-unhandled-proxy-rejections.js index 91808af14588d3..cb940d63705593 100644 --- a/test/parallel/test-promises-unhandled-proxy-rejections.js +++ b/test/parallel/test-promises-unhandled-proxy-rejections.js @@ -34,5 +34,6 @@ common.expectWarning({ UnhandledPromiseRejectionWarning: expectedPromiseWarning, }); -// ensure this doesn't crash -Promise.reject(thorny); +// Ensure this doesn't crash +const p = Promise.reject(thorny); +p.catch(() => {}); diff --git a/test/parallel/test-promises-unhandled-symbol-rejections.js b/test/parallel/test-promises-unhandled-symbol-rejections.js index a60a5f2e8aac30..3fda973e11f14b 100644 --- a/test/parallel/test-promises-unhandled-symbol-rejections.js +++ b/test/parallel/test-promises-unhandled-symbol-rejections.js @@ -1,18 +1,18 @@ 'use strict'; const common = require('../common'); -const expectedDeprecationWarning = 'Unhandled promise rejections are ' + - 'deprecated. In the future, promise ' + - 'rejections that are not handled will ' + - 'terminate the Node.js process with a ' + - 'non-zero exit code.'; +const expectedHandledWarning = 'Promise rejection was handled ' + + 'asynchronously (rejection id: 1)'; const expectedPromiseWarning = 'Unhandled promise rejection (rejection id: ' + '1): Symbol()'; common.expectWarning({ - DeprecationWarning: expectedDeprecationWarning, UnhandledPromiseRejectionWarning: expectedPromiseWarning, + PromiseRejectionHandledWarning: expectedHandledWarning }); // ensure this doesn't crash -Promise.reject(Symbol()); +const p = Promise.reject(Symbol()); +setTimeout(() => { + p.catch(() => {}); +}, 1); diff --git a/test/parallel/test-promises-warning-on-unhandled-rejection.js b/test/parallel/test-promises-warning-on-unhandled-rejection.js index 10f95162a09597..7b3e2982994965 100644 --- a/test/parallel/test-promises-warning-on-unhandled-rejection.js +++ b/test/parallel/test-promises-warning-on-unhandled-rejection.js @@ -7,23 +7,18 @@ const common = require('../common'); const assert = require('assert'); -let b = 0; +let handled = false; process.on('warning', common.mustCall((warning) => { - switch (b++) { - case 0: - assert.strictEqual(warning.name, 'UnhandledPromiseRejectionWarning'); - assert(/Unhandled promise rejection/.test(warning.message)); - break; - case 1: - assert.strictEqual(warning.name, 'DeprecationWarning'); - break; - case 2: - assert.strictEqual(warning.name, 'PromiseRejectionHandledWarning'); - assert(/Promise rejection was handled asynchronously/ - .test(warning.message)); + if (handled === false) { + assert.strictEqual(warning.name, 'UnhandledPromiseRejectionWarning'); + assert(/^Unhandled promise rejection/.test(warning.message)); + handled = true; + } else { + assert.strictEqual(warning.name, 'PromiseRejectionHandledWarning'); + assert(/^Promise rejection was handled asynchronous/.test(warning.message)); } -}, 3)); +}, 2)); const p = Promise.reject('This was rejected'); setImmediate(common.mustCall(() => p.catch(() => {})));