diff options
author | Anna Henningsen <anna@addaleax.net> | 2017-11-20 19:57:20 +0100 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2017-11-29 15:58:42 +0100 |
commit | 4503da8a3a3b0b71d950a63de729ce495965f6ea (patch) | |
tree | 66ad243cd90cea3df8fc11eb7cf29597ddd56eb3 /lib/domain.js | |
parent | 04e3aa28bbcdf62f677dd314ad8c12556c18c15f (diff) | |
download | android-node-v8-4503da8a3a3b0b71d950a63de729ce495965f6ea.tar.gz android-node-v8-4503da8a3a3b0b71d950a63de729ce495965f6ea.tar.bz2 android-node-v8-4503da8a3a3b0b71d950a63de729ce495965f6ea.zip |
process: add flag for uncaught exception abort
Introduce `process.shouldAbortOnUncaughtException` to control
`--abort-on-uncaught-exception` behaviour, and implement
some of the domains functionality on top of it.
PR-URL: https://github.com/nodejs/node/pull/17159
Refs: https://github.com/nodejs/node/issues/17143
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Diffstat (limited to 'lib/domain.js')
-rw-r--r-- | lib/domain.js | 84 |
1 files changed, 73 insertions, 11 deletions
diff --git a/lib/domain.js b/lib/domain.js index dc3c550866..6c85ca2b17 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -28,6 +28,7 @@ const util = require('util'); const EventEmitter = require('events'); +const errors = require('internal/errors'); const { createHook } = require('async_hooks'); // communicate with events module, but don't require that @@ -81,19 +82,77 @@ const asyncHook = createHook({ } }); +// When domains are in use, they claim full ownership of the +// uncaught exception capture callback. +if (process.hasUncaughtExceptionCaptureCallback()) { + throw new errors.Error('ERR_DOMAIN_CALLBACK_NOT_AVAILABLE'); +} + +// Get the stack trace at the point where `domain` was required. +const domainRequireStack = new Error('require(`domain`) at this point').stack; + +const { setUncaughtExceptionCaptureCallback } = process; +process.setUncaughtExceptionCaptureCallback = function(fn) { + const err = + new errors.Error('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE'); + err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack; + throw err; +}; + // It's possible to enter one domain while already inside // another one. The stack is each entered domain. const stack = []; exports._stack = stack; -process._setupDomainUse(stack); +process._setupDomainUse(); -class Domain extends EventEmitter { +function updateExceptionCapture() { + if (stack.every((domain) => domain.listenerCount('error') === 0)) { + setUncaughtExceptionCaptureCallback(null); + } else { + setUncaughtExceptionCaptureCallback(null); + setUncaughtExceptionCaptureCallback((er) => { + return process.domain._errorHandler(er); + }); + } +} + + +process.on('newListener', (name, listener) => { + if (name === 'uncaughtException' && + listener !== domainUncaughtExceptionClear) { + // Make sure the first listener for `uncaughtException` always clears + // the domain stack. + process.removeListener(name, domainUncaughtExceptionClear); + process.prependListener(name, domainUncaughtExceptionClear); + } +}); + +process.on('removeListener', (name, listener) => { + if (name === 'uncaughtException' && + listener !== domainUncaughtExceptionClear) { + // If the domain listener would be the only remaining one, remove it. + const listeners = process.listeners('uncaughtException'); + if (listeners.length === 1 && listeners[0] === domainUncaughtExceptionClear) + process.removeListener(name, domainUncaughtExceptionClear); + } +}); +function domainUncaughtExceptionClear() { + stack.length = 0; + exports.active = process.domain = null; + updateExceptionCapture(); +} + + +class Domain extends EventEmitter { constructor() { super(); this.members = []; asyncHook.enable(); + + this.on('removeListener', updateExceptionCapture); + this.on('newListener', updateExceptionCapture); } } @@ -131,14 +190,14 @@ Domain.prototype._errorHandler = function _errorHandler(er) { // prevent the process 'uncaughtException' event from being emitted // if a listener is set. if (EventEmitter.listenerCount(this, 'error') > 0) { + // Clear the uncaughtExceptionCaptureCallback so that we know that, even + // if technically the top-level domain is still active, it would + // be ok to abort on an uncaught exception at this point + setUncaughtExceptionCaptureCallback(null); try { - // Set the _emittingTopLevelDomainError so that we know that, even - // if technically the top-level domain is still active, it would - // be ok to abort on an uncaught exception at this point - process._emittingTopLevelDomainError = true; caught = this.emit('error', er); } finally { - process._emittingTopLevelDomainError = false; + updateExceptionCapture(); } } } else { @@ -161,11 +220,13 @@ Domain.prototype._errorHandler = function _errorHandler(er) { if (this === exports.active) { stack.pop(); } + updateExceptionCapture(); if (stack.length) { exports.active = process.domain = stack[stack.length - 1]; - caught = process._fatalException(er2); + caught = process.domain._errorHandler(er2); } else { - caught = false; + // Pass on to the next exception handler. + throw er2; } } } @@ -173,8 +234,7 @@ Domain.prototype._errorHandler = function _errorHandler(er) { // Exit all domains on the stack. Uncaught exceptions end the // current tick and no domains should be left on the stack // between ticks. - stack.length = 0; - exports.active = process.domain = null; + domainUncaughtExceptionClear(); return caught; }; @@ -185,6 +245,7 @@ Domain.prototype.enter = function() { // to push it onto the stack so that we can pop it later. exports.active = process.domain = this; stack.push(this); + updateExceptionCapture(); }; @@ -198,6 +259,7 @@ Domain.prototype.exit = function() { exports.active = stack[stack.length - 1]; process.domain = exports.active; + updateExceptionCapture(); }; |