28
28
29
29
const util = require ( 'util' ) ;
30
30
const EventEmitter = require ( 'events' ) ;
31
+ const errors = require ( 'internal/errors' ) ;
31
32
const { createHook } = require ( 'async_hooks' ) ;
32
33
33
34
// communicate with events module, but don't require that
@@ -81,19 +82,77 @@ const asyncHook = createHook({
81
82
}
82
83
} ) ;
83
84
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
+
84
102
// It's possible to enter one domain while already inside
85
103
// another one. The stack is each entered domain.
86
104
const stack = [ ] ;
87
105
exports . _stack = stack ;
88
- process . _setupDomainUse ( stack ) ;
106
+ process . _setupDomainUse ( ) ;
89
107
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
+ } ) ;
91
139
140
+ function domainUncaughtExceptionClear ( ) {
141
+ stack . length = 0 ;
142
+ exports . active = process . domain = null ;
143
+ updateExceptionCapture ( ) ;
144
+ }
145
+
146
+
147
+ class Domain extends EventEmitter {
92
148
constructor ( ) {
93
149
super ( ) ;
94
150
95
151
this . members = [ ] ;
96
152
asyncHook . enable ( ) ;
153
+
154
+ this . on ( 'removeListener' , updateExceptionCapture ) ;
155
+ this . on ( 'newListener' , updateExceptionCapture ) ;
97
156
}
98
157
}
99
158
@@ -131,14 +190,14 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
131
190
// prevent the process 'uncaughtException' event from being emitted
132
191
// if a listener is set.
133
192
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 ) ;
134
197
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 ;
139
198
caught = this . emit ( 'error' , er ) ;
140
199
} finally {
141
- process . _emittingTopLevelDomainError = false ;
200
+ updateExceptionCapture ( ) ;
142
201
}
143
202
}
144
203
} else {
@@ -161,20 +220,21 @@ Domain.prototype._errorHandler = function _errorHandler(er) {
161
220
if ( this === exports . active ) {
162
221
stack . pop ( ) ;
163
222
}
223
+ updateExceptionCapture ( ) ;
164
224
if ( stack . length ) {
165
225
exports . active = process . domain = stack [ stack . length - 1 ] ;
166
- caught = process . _fatalException ( er2 ) ;
226
+ caught = process . domain . _errorHandler ( er2 ) ;
167
227
} else {
168
- caught = false ;
228
+ // Pass on to the next exception handler.
229
+ throw er2 ;
169
230
}
170
231
}
171
232
}
172
233
173
234
// Exit all domains on the stack. Uncaught exceptions end the
174
235
// current tick and no domains should be left on the stack
175
236
// between ticks.
176
- stack . length = 0 ;
177
- exports . active = process . domain = null ;
237
+ domainUncaughtExceptionClear ( ) ;
178
238
179
239
return caught ;
180
240
} ;
@@ -185,6 +245,7 @@ Domain.prototype.enter = function() {
185
245
// to push it onto the stack so that we can pop it later.
186
246
exports . active = process . domain = this ;
187
247
stack . push ( this ) ;
248
+ updateExceptionCapture ( ) ;
188
249
} ;
189
250
190
251
@@ -198,6 +259,7 @@ Domain.prototype.exit = function() {
198
259
199
260
exports . active = stack [ stack . length - 1 ] ;
200
261
process . domain = exports . active ;
262
+ updateExceptionCapture ( ) ;
201
263
} ;
202
264
203
265
0 commit comments