0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-29 07:00:59 +01:00

process: add unhandled-rejection throw and warn-with-error-code

This PR defines two new modes for the --unhandled-rejections flag.

The first mode is called "throw". The "throw" mode first emits
unhandledRejection. If this hook is not set, the "throw" mode will
raise the unhandled rejection as an uncaught exception.

The second mode is called "warn-with-error-code". The
"warn-with-error-code" mode first emits unhandledRejection. If this
hook is not set, the "warn-with-error-code" mode will trigger a
warning and set the process's exit code to 1.

The PR doesn't change the default behavior for unhandled rejections.
That will come in a separate PR.

Refs: https://github.com/nodejs/node/pull/33021

PR-URL: https://github.com/nodejs/node/pull/33475
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Dan Fabulich 2020-05-19 17:41:01 -07:00 committed by Matheus Marchini
parent bc71278563
commit ff74e35c0b
No known key found for this signature in database
GPG Key ID: BE516BA4874DB4D9
7 changed files with 164 additions and 9 deletions

View File

@ -983,11 +983,15 @@ for the very first unhandled rejection in case no [`unhandledRejection`][] hook
is used.
Using this flag allows to change what should happen when an unhandled rejection
occurs. One of three modes can be chosen:
occurs. One of the following modes can be chosen:
* `throw`: Emit [`unhandledRejection`][]. If this hook is not set, raise the
unhandled rejection as an uncaught exception.
* `strict`: Raise the unhandled rejection as an uncaught exception.
* `warn`: Always trigger a warning, no matter if the [`unhandledRejection`][]
hook is set or not but do not print the deprecation warning.
* `warn-with-error-code`: Emit [`unhandledRejection`][]. If this hook is not
set, trigger a warning, and set the process exit code to 1.
* `none`: Silence all warnings.
### `--use-bundled-ca`, `--use-openssl-ca`

View File

@ -30,22 +30,37 @@ const pendingUnhandledRejections = [];
const asyncHandledRejections = [];
let lastPromiseId = 0;
// --unhandled-rejection=none:
// --unhandled-rejections=none:
// Emit 'unhandledRejection', but do not emit any warning.
const kIgnoreUnhandledRejections = 0;
// --unhandled-rejection=warn:
// --unhandled-rejections=warn:
// Emit 'unhandledRejection', then emit 'UnhandledPromiseRejectionWarning'.
const kAlwaysWarnUnhandledRejections = 1;
// --unhandled-rejection=strict:
// --unhandled-rejections=strict:
// Emit 'uncaughtException'. If it's not handled, print the error to stderr
// and exit the process.
// Otherwise, emit 'unhandledRejection'. If 'unhandledRejection' is not
// handled, emit 'UnhandledPromiseRejectionWarning'.
const kThrowUnhandledRejections = 2;
// --unhandled-rejection is unset:
// Emit 'unhandledRejection', if it's handled, emit
const kStrictUnhandledRejections = 2;
// --unhandled-rejections=throw:
// Emit 'unhandledRejection', if it's unhandled, emit
// 'uncaughtException'. If it's not handled, print the error to stderr
// and exit the process.
const kThrowUnhandledRejections = 3;
// --unhandled-rejections=warn-with-error-code:
// Emit 'unhandledRejection', if it's unhandled, emit
// 'UnhandledPromiseRejectionWarning', then set process exit code to 1.
const kWarnWithErrorCodeUnhandledRejections = 4;
// --unhandled-rejections is unset:
// Emit 'unhandledRejection', if it's unhandled, emit
// 'UnhandledPromiseRejectionWarning', then emit deprecation warning.
const kDefaultUnhandledRejections = 3;
const kDefaultUnhandledRejections = 5;
let unhandledRejectionsMode;
@ -65,7 +80,11 @@ function getUnhandledRejectionsMode() {
case 'warn':
return kAlwaysWarnUnhandledRejections;
case 'strict':
return kStrictUnhandledRejections;
case 'throw':
return kThrowUnhandledRejections;
case 'warn-with-error-code':
return kWarnWithErrorCodeUnhandledRejections;
default:
return kDefaultUnhandledRejections;
}
@ -188,7 +207,7 @@ function processPromiseRejections() {
promiseInfo.warned = true;
const { reason, uid } = promiseInfo;
switch (unhandledRejectionsMode) {
case kThrowUnhandledRejections: {
case kStrictUnhandledRejections: {
const err = reason instanceof Error ?
reason : generateUnhandledRejectionError(reason);
triggerUncaughtException(err, true /* fromPromise */);
@ -205,6 +224,23 @@ function processPromiseRejections() {
emitUnhandledRejectionWarning(uid, reason);
break;
}
case kThrowUnhandledRejections: {
const handled = process.emit('unhandledRejection', reason, promise);
if (!handled) {
const err = reason instanceof Error ?
reason : generateUnhandledRejectionError(reason);
triggerUncaughtException(err, true /* fromPromise */);
}
break;
}
case kWarnWithErrorCodeUnhandledRejections: {
const handled = process.emit('unhandledRejection', reason, promise);
if (!handled) {
emitUnhandledRejectionWarning(uid, reason);
process.exitCode = 1;
}
break;
}
case kDefaultUnhandledRejections: {
const handled = process.emit('unhandledRejection', reason, promise);
if (!handled) {

View File

@ -118,6 +118,8 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
}
if (!unhandled_rejections.empty() &&
unhandled_rejections != "warn-with-error-code" &&
unhandled_rejections != "throw" &&
unhandled_rejections != "strict" &&
unhandled_rejections != "warn" &&
unhandled_rejections != "none") {

View File

@ -0,0 +1,10 @@
// Flags: --unhandled-rejections=warn-with-error-code
'use strict';
const common = require('../common');
const assert = require('assert');
common.disableCrashOnUnhandledRejection();
Promise.reject(new Error('alas'));
process.on('exit', assert.strictEqual.bind(null, 1));

View File

@ -0,0 +1,10 @@
*UnhandledPromiseRejectionWarning: Error: alas
at *promise_unhandled_warn_with_error.js:*:*
at *
at *
at *
at *
at *
at *
(Use `node --trace-warnings ...` to show where the warning was created)
*UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

View File

@ -0,0 +1,38 @@
// Flags: --unhandled-rejections=throw
'use strict';
const common = require('../common');
const Countdown = require('../common/countdown');
const assert = require('assert');
common.disableCrashOnUnhandledRejection();
// Verify that the unhandledRejection handler prevents triggering
// uncaught exceptions
const err1 = new Error('One');
const errors = [err1, null];
const ref = new Promise(() => {
throw err1;
});
// Explicitly reject `null`.
Promise.reject(null);
process.on('warning', common.mustNotCall('warning'));
process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
process.on('exit', assert.strictEqual.bind(null, 0));
process.on('uncaughtException', common.mustNotCall('uncaughtException'));
const timer = setTimeout(() => console.log(ref), 1000);
const counter = new Countdown(2, () => {
clearTimeout(timer);
});
process.on('unhandledRejection', common.mustCall((err) => {
counter.dec();
const knownError = errors.shift();
assert.deepStrictEqual(err, knownError);
}, 2));

View File

@ -0,0 +1,55 @@
// Flags: --unhandled-rejections=throw
'use strict';
const common = require('../common');
const Countdown = require('../common/countdown');
const assert = require('assert');
common.disableCrashOnUnhandledRejection();
// Verify that unhandled rejections always trigger uncaught exceptions instead
// of triggering unhandled rejections.
const err1 = new Error('One');
const err2 = new Error(
'This error originated either by throwing ' +
'inside of an async function without a catch block, or by rejecting a ' +
'promise which was not handled with .catch(). The promise rejected with the' +
' reason "null".'
);
err2.code = 'ERR_UNHANDLED_REJECTION';
Object.defineProperty(err2, 'name', {
value: 'UnhandledPromiseRejection',
writable: true,
configurable: true
});
const errors = [err1, err2];
const identical = [true, false];
const ref = new Promise(() => {
throw err1;
});
// Explicitly reject `null`.
Promise.reject(null);
process.on('warning', common.mustNotCall('warning'));
// If we add an unhandledRejection handler, the exception won't be thrown
// process.on('unhandledRejection', common.mustCall(2));
process.on('rejectionHandled', common.mustNotCall('rejectionHandled'));
process.on('exit', assert.strictEqual.bind(null, 0));
const timer = setTimeout(() => console.log(ref), 1000);
const counter = new Countdown(2, () => {
clearTimeout(timer);
});
process.on('uncaughtException', common.mustCall((err, origin) => {
counter.dec();
assert.strictEqual(origin, 'unhandledRejection', err);
const knownError = errors.shift();
assert.deepStrictEqual(err, knownError);
// Check if the errors are reference equal.
assert(identical.shift() ? err === knownError : err !== knownError);
}, 2));