From 6ed198537b567bddff67309f001739a8033dd1be Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 25 Apr 2018 12:18:32 +0200 Subject: [PATCH 1/6] deps: fix V8_PROMISE_INTERNAL_FIELD_COUNT in features.gypi It should have received a variable. Instead, it was set to a string that was coerced to a integer later on. This fixes it by correctly passing through the variable value. --- deps/v8/gypfiles/features.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/v8/gypfiles/features.gypi b/deps/v8/gypfiles/features.gypi index 69ff763be04ab5..2388ba2729cf3e 100644 --- a/deps/v8/gypfiles/features.gypi +++ b/deps/v8/gypfiles/features.gypi @@ -105,7 +105,7 @@ 'defines': ['ENABLE_DISASSEMBLER',], }], ['v8_promise_internal_field_count!=0', { - 'defines': ['V8_PROMISE_INTERNAL_FIELD_COUNT','v8_promise_internal_field_count'], + 'defines': ['V8_PROMISE_INTERNAL_FIELD_COUNT=<(v8_promise_internal_field_count)'], }], ['v8_enable_gdbjit==1', { 'defines': ['ENABLE_GDB_JIT_INTERFACE',], From 1eef8cf35ba78d098a47668e4b9c5983b66c43df Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 16 Apr 2018 23:35:44 +0200 Subject: [PATCH 2/6] src: exit on gc unhandled rejection --- configure | 3 +- doc/api/cli.md | 25 ++++++ doc/api/deprecations.md | 8 +- doc/api/errors.md | 6 ++ doc/api/process.md | 51 +++++++---- lib/internal/bootstrap/node.js | 22 +++-- lib/internal/errors.js | 2 + lib/internal/process/promises.js | 86 +++++++++++++------ src/bootstrapper.cc | 46 +++++++++- src/node.cc | 21 ++++- src/node_internals.h | 4 + test/benchmark/testcfg.py | 6 ++ .../message/promise_always_throw_unhandled.js | 17 ++++ .../promise_always_throw_unhandled.out | 15 ++++ test/message/promise_fast_reject.js | 18 ++++ test/message/promise_fast_reject.out | 24 ++++++ test/message/promise_unhandled_caught.js | 20 +++++ test/message/promise_unhandled_caught.out | 14 +++ test/message/promise_unhandled_reject.js | 53 ++++++++++++ test/message/promise_unhandled_reject.out | 61 +++++++++++++ .../unhandled_promise_trace_warnings.out | 8 -- .../test-async-wrap-pop-id-during-load.js | 4 +- test/parallel/test-next-tick-errors.js | 4 +- .../test-process-fatal-exception-tick.js | 9 +- test/parallel/test-promise-unhandled-error.js | 47 ++++++++++ .../test-promise-unhandled-silent-no-hook.js | 22 +++++ .../parallel/test-promise-unhandled-silent.js | 25 ++++++ .../test-promise-unhandled-warn-to-error.js | 28 ++++++ ...est-promises-unhandled-proxy-rejections.js | 38 ++++---- ...st-promises-unhandled-symbol-rejections.js | 6 -- ...promises-warning-on-unhandled-rejection.js | 10 +-- .../test-timers-immediate-queue-throw.js | 5 +- .../test-timers-unref-throw-then-ref.js | 6 +- 33 files changed, 604 insertions(+), 110 deletions(-) create mode 100644 test/benchmark/testcfg.py create mode 100644 test/message/promise_always_throw_unhandled.js create mode 100644 test/message/promise_always_throw_unhandled.out create mode 100644 test/message/promise_fast_reject.js create mode 100644 test/message/promise_fast_reject.out create mode 100644 test/message/promise_unhandled_caught.js create mode 100644 test/message/promise_unhandled_caught.out create mode 100644 test/message/promise_unhandled_reject.js create mode 100644 test/message/promise_unhandled_reject.out create mode 100644 test/parallel/test-promise-unhandled-error.js create mode 100644 test/parallel/test-promise-unhandled-silent-no-hook.js create mode 100644 test/parallel/test-promise-unhandled-silent.js create mode 100644 test/parallel/test-promise-unhandled-warn-to-error.js diff --git a/configure b/configure index c04118e9832b0c..7d13d3c982fc00 100755 --- a/configure +++ b/configure @@ -1140,7 +1140,8 @@ def configure_v8(o): o['variables']['v8_no_strict_aliasing'] = 1 # Work around compiler bugs. o['variables']['v8_optimized_debug'] = 0 # Compile with -O0 in debug builds. o['variables']['v8_random_seed'] = 0 # Use a random seed for hash tables. - o['variables']['v8_promise_internal_field_count'] = 1 # Add internal field to promises for async hooks. + # Add internal field to promises for async hooks and unhandled rejections. + o['variables']['v8_promise_internal_field_count'] = 2 o['variables']['v8_use_snapshot'] = 'false' if options.without_snapshot else 'true' o['variables']['v8_trace_maps'] = 1 if options.trace_maps else 0 o['variables']['node_use_v8_platform'] = b(not options.without_v8_platform) diff --git a/doc/api/cli.md b/doc/api/cli.md index 637c1cd9050557..c02413e972663d 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -625,6 +625,29 @@ Path to the file used to store the persistent REPL history. The default path is `~/.node_repl_history`, which is overridden by this variable. Setting the value to an empty string (`''` or `' '`) disables persistent REPL history. +### `NODE_UNHANDLED_REJECTION=state` + + +Setting this environment variable allows fine grained control over the behavior +of unhandled rejections. This may be set to either one of `SILENT`, `WARN`, +`ERROR_ON_GC` or `ERROR` while the default behavior without using the +environment variable is the same as `WARN`. `WARN` reflects the +[`unhandledRejection hook`][]. + +Setting the environment variable to `SILENT` prevents unhandled rejection +warnings from being logged, even if no [`unhandledRejection hook`][] is set. + +If set to `ERROR_ON_GC` unhandled rejections that are garbage collected are +turned into exceptions. + +If set to `ERROR` any unhandled rejection is going turn the rejection into an +exception. + +Any rejection that is turned into an exception can be caught by the +[`uncaughtException hook`][]. + ### `OPENSSL_CONF=file` +* `err` {Error} The uncaught exception. +* `errorOrigin` {string} Indicates if the exception originates from an + unhandled rejection or from synchronous errors. Can either be `'FROM_PROMISE'` + or `'FROM_ERROR'`. + The `'uncaughtException'` event is emitted when an uncaught JavaScript exception bubbles all the way back to the event loop. By default, Node.js handles such exceptions by printing the stack trace to `stderr` and exiting @@ -159,12 +170,13 @@ behavior. You may also change the [`process.exitCode`][] in provided exit code, otherwise in the presence of such handler the process will exit with 0. -The listener function is called with the `Error` object passed as the only -argument. - ```js -process.on('uncaughtException', (err) => { - fs.writeSync(1, `Caught exception: ${err}\n`); +process.on('uncaughtException', (err, errorOrigin) => { + fs.writeSync( + 1, + `Caught exception: ${err}\n` + + `Exception originated from unhandled rejection: ${errorOrigin}` + ); }); setTimeout(() => { @@ -216,6 +228,10 @@ changes: a process warning. --> +* `reason` {Error|any} The object with which the promise was rejected + (typically an [`Error`][] object). +* `promise` {Promise} The rejected promise. + The `'unhandledRejection'` event is emitted whenever a `Promise` is rejected and no error handler is attached to the promise within a turn of the event loop. When programming with Promises, exceptions are encapsulated as "rejected @@ -224,21 +240,15 @@ are propagated through a `Promise` chain. The `'unhandledRejection'` event is useful for detecting and keeping track of promises that were rejected whose rejections have not yet been handled. -The listener function is called with the following arguments: - -* `reason` {Error|any} The object with which the promise was rejected - (typically an [`Error`][] object). -* `p` the `Promise` that was rejected. - ```js -process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at:', p, 'reason:', reason); - // application specific logging, throwing an error, or other logic here +process.on('unhandledRejection', (reason, promise) => { + console.log('Unhandled Rejection at:', promise, 'reason:', reason); + // Application specific logging, throwing an error, or other logic here }); somePromise.then((res) => { - return reportToUser(JSON.pasre(res)); // note the typo (`pasre`) -}); // no `.catch()` or `.then()` + return reportToUser(JSON.pasre(res)); // Note the typo (`pasre`) +}); // No `.catch()` or `.then()` ``` The following will also trigger the `'unhandledRejection'` event to be @@ -251,7 +261,7 @@ function SomeResource() { } const resource = new SomeResource(); -// no .catch or .then on resource.loaded for at least a turn +// No .catch or .then on resource.loaded for at least a turn ``` In this example case, it is possible to track the rejection as a developer error @@ -259,7 +269,11 @@ as would typically be the case for other `'unhandledRejection'` events. To address such failures, a non-operational [`.catch(() => { })`][`promise.catch()`] handler may be attached to `resource.loaded`, which would prevent the `'unhandledRejection'` event from -being emitted. Alternatively, the [`'rejectionHandled'`][] event may be used. +being emitted. + +In case a rejected promise is garbage collected without ever having a +[`.catch(() => { })`][`promise.catch()`] handler attached, it will trigger an +`uncaughtException` just as any other uncaught JavaScript exception. ### Event: 'warning' + +See below .. + + + + + + + + + + + + + + + + + + + + ### `--throw-deprecation` * `err` {Error} The uncaught exception. @@ -271,10 +269,6 @@ address such failures, a non-operational `resource.loaded`, which would prevent the `'unhandledRejection'` event from being emitted. -In case a rejected promise is garbage collected without ever having a -[`.catch(() => { })`][`promise.catch()`] handler attached, it will trigger an -`uncaughtException` like all other uncaught JavaScript exceptions. - ### Event: 'warning'