2828
2929const util = require ( 'util' ) ;
3030const EventEmitter = require ( 'events' ) ;
31+ const errors = require ( 'internal/errors' ) ;
3132const { createHook } = require ( 'async_hooks' ) ;
3233
3334// communicate with events module, but don't require that
@@ -81,19 +82,77 @@ const asyncHook = createHook({
8182 }
8283} ) ;
8384
85+ // When domains are in use, they claim full ownership of the
86+ // uncaught exception capture callback.
87+ if ( process . hasUncaughtExceptionCaptureCallback ( ) ) {
88+ throw new errors . Error ( 'ERR_DOMAIN_CALLBACK_NOT_AVAILABLE' ) ;
89+ }
90+
91+ // Get the stack trace at the point where `domain` was required.
92+ const domainRequireStack = new Error ( 'require(`domain`) at this point' ) . stack ;
93+
94+ const { setUncaughtExceptionCaptureCallback } = process ;
95+ process . setUncaughtExceptionCaptureCallback = function ( fn ) {
96+ const err =
97+ new errors . Error ( 'ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE' ) ;
98+ err . stack = err . stack + '\n' + '-' . repeat ( 40 ) + '\n' + domainRequireStack ;
99+ throw err ;
100+ } ;
101+
84102// It's possible to enter one domain while already inside
85103// another one. The stack is each entered domain.
86104const stack = [ ] ;
87105exports . _stack = stack ;
88- process . _setupDomainUse ( stack ) ;
106+ process . _setupDomainUse ( ) ;
89107
90- class Domain extends EventEmitter {
108+ function updateExceptionCapture ( ) {
109+ if ( stack . every ( ( domain ) => domain . listenerCount ( 'error' ) === 0 ) ) {
110+ setUncaughtExceptionCaptureCallback ( null ) ;
111+ } else {
112+ setUncaughtExceptionCaptureCallback ( null ) ;
113+ setUncaughtExceptionCaptureCallback ( ( er ) => {
114+ return process . domain . _errorHandler ( er ) ;
115+ } ) ;
116+ }
117+ }
118+
119+
120+ process . on ( 'newListener' , ( name , listener ) => {
121+ if ( name === 'uncaughtException' &&
122+ listener !== domainUncaughtExceptionClear ) {
123+ // Make sure the first listener for `uncaughtException` always clears
124+ // the domain stack.
125+ process . removeListener ( name , domainUncaughtExceptionClear ) ;
126+ process . prependListener ( name , domainUncaughtExceptionClear ) ;
127+ }
128+ } ) ;
129+
130+ process . on ( 'removeListener' , ( name , listener ) => {
131+ if ( name === 'uncaughtException' &&
132+ listener !== domainUncaughtExceptionClear ) {
133+ // If the domain listener would be the only remaining one, remove it.
134+ const listeners = process . listeners ( 'uncaughtException' ) ;
135+ if ( listeners . length === 1 && listeners [ 0 ] === domainUncaughtExceptionClear )
136+ process . removeListener ( name , domainUncaughtExceptionClear ) ;
137+ }
138+ } ) ;
91139
140+ function domainUncaughtExceptionClear ( ) {
141+ stack . length = 0 ;
142+ exports . active = process . domain = null ;
143+ updateExceptionCapture ( ) ;
144+ }
145+
146+
147+ class Domain extends EventEmitter {
92148 constructor ( ) {
93149 super ( ) ;
94150
95151 this . members = [ ] ;
96152 asyncHook . enable ( ) ;
153+
154+ this . on ( 'removeListener' , updateExceptionCapture ) ;
155+ this . on ( 'newListener' , updateExceptionCapture ) ;
97156 }
98157}
99158
@@ -131,14 +190,14 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
131190 // prevent the process 'uncaughtException' event from being emitted
132191 // if a listener is set.
133192 if ( EventEmitter . listenerCount ( this , 'error' ) > 0 ) {
193+ // Clear the uncaughtExceptionCaptureCallback so that we know that, even
194+ // if technically the top-level domain is still active, it would
195+ // be ok to abort on an uncaught exception at this point
196+ setUncaughtExceptionCaptureCallback ( null ) ;
134197 try {
135- // Set the _emittingTopLevelDomainError so that we know that, even
136- // if technically the top-level domain is still active, it would
137- // be ok to abort on an uncaught exception at this point
138- process . _emittingTopLevelDomainError = true ;
139198 caught = this . emit ( 'error' , er ) ;
140199 } finally {
141- process . _emittingTopLevelDomainError = false ;
200+ updateExceptionCapture ( ) ;
142201 }
143202 }
144203 } else {
@@ -161,20 +220,21 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
161220 if ( this === exports . active ) {
162221 stack . pop ( ) ;
163222 }
223+ updateExceptionCapture ( ) ;
164224 if ( stack . length ) {
165225 exports . active = process . domain = stack [ stack . length - 1 ] ;
166- caught = process . _fatalException ( er2 ) ;
226+ caught = process . domain . _errorHandler ( er2 ) ;
167227 } else {
168- caught = false ;
228+ // Pass on to the next exception handler.
229+ throw er2 ;
169230 }
170231 }
171232 }
172233
173234 // Exit all domains on the stack. Uncaught exceptions end the
174235 // current tick and no domains should be left on the stack
175236 // between ticks.
176- stack . length = 0 ;
177- exports . active = process . domain = null ;
237+ domainUncaughtExceptionClear ( ) ;
178238
179239 return caught ;
180240} ;
@@ -185,6 +245,7 @@ Domain.prototype.enter = function() {
185245 // to push it onto the stack so that we can pop it later.
186246 exports . active = process . domain = this ;
187247 stack . push ( this ) ;
248+ updateExceptionCapture ( ) ;
188249} ;
189250
190251
@@ -198,6 +259,7 @@ Domain.prototype.exit = function() {
198259
199260 exports . active = stack [ stack . length - 1 ] ;
200261 process . domain = exports . active ;
262+ updateExceptionCapture ( ) ;
201263} ;
202264
203265
0 commit comments