1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24// WARNING: THIS MODULE IS PENDING DEPRECATION. 25// 26// No new pull requests targeting this module will be accepted 27// unless they address existing, critical bugs. 28 29const { 30 ArrayPrototypeEvery, 31 ArrayPrototypeIndexOf, 32 ArrayPrototypeLastIndexOf, 33 ArrayPrototypePush, 34 ArrayPrototypeSlice, 35 ArrayPrototypeSplice, 36 Error, 37 FunctionPrototypeCall, 38 ObjectDefineProperty, 39 Promise, 40 ReflectApply, 41 SafeMap, 42 SafeWeakMap, 43 Symbol, 44} = primordials; 45 46const EventEmitter = require('events'); 47const { 48 ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, 49 ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, 50 ERR_UNHANDLED_ERROR, 51} = require('internal/errors').codes; 52const { createHook } = require('async_hooks'); 53const { useDomainTrampoline } = require('internal/async_hooks'); 54 55// TODO(addaleax): Use a non-internal solution for this. 56const kWeak = Symbol('kWeak'); 57const { WeakReference } = internalBinding('util'); 58 59// Overwrite process.domain with a getter/setter that will allow for more 60// effective optimizations 61const _domain = [null]; 62ObjectDefineProperty(process, 'domain', { 63 __proto__: null, 64 enumerable: true, 65 get: function() { 66 return _domain[0]; 67 }, 68 set: function(arg) { 69 return _domain[0] = arg; 70 }, 71}); 72 73const vmPromises = new SafeWeakMap(); 74const pairing = new SafeMap(); 75const asyncHook = createHook({ 76 init(asyncId, type, triggerAsyncId, resource) { 77 if (process.domain !== null && process.domain !== undefined) { 78 // If this operation is created while in a domain, let's mark it 79 pairing.set(asyncId, process.domain[kWeak]); 80 // Promises from other contexts, such as with the VM module, should not 81 // have a domain property as it can be used to escape the sandbox. 82 if (type !== 'PROMISE' || resource instanceof Promise) { 83 ObjectDefineProperty(resource, 'domain', { 84 __proto__: null, 85 configurable: true, 86 enumerable: false, 87 value: process.domain, 88 writable: true, 89 }); 90 // Because promises from other contexts don't get a domain field, 91 // the domain needs to be held alive another way. Stuffing it in a 92 // weakmap connected to the promise lifetime can fix that. 93 } else { 94 vmPromises.set(resource, process.domain); 95 } 96 } 97 }, 98 before(asyncId) { 99 const current = pairing.get(asyncId); 100 if (current !== undefined) { // Enter domain for this cb 101 // We will get the domain through current.get(), because the resource 102 // object's .domain property makes sure it is not garbage collected. 103 // However, we do need to make the reference to the domain non-weak, 104 // so that it cannot be garbage collected before the after() hook. 105 current.incRef(); 106 current.get().enter(); 107 } 108 }, 109 after(asyncId) { 110 const current = pairing.get(asyncId); 111 if (current !== undefined) { // Exit domain for this cb 112 const domain = current.get(); 113 current.decRef(); 114 domain.exit(); 115 } 116 }, 117 destroy(asyncId) { 118 pairing.delete(asyncId); // cleaning up 119 }, 120}); 121 122// When domains are in use, they claim full ownership of the 123// uncaught exception capture callback. 124if (process.hasUncaughtExceptionCaptureCallback()) { 125 throw new ERR_DOMAIN_CALLBACK_NOT_AVAILABLE(); 126} 127 128// Get the stack trace at the point where `domain` was required. 129// eslint-disable-next-line no-restricted-syntax 130const domainRequireStack = new Error('require(`domain`) at this point').stack; 131 132const { setUncaughtExceptionCaptureCallback } = process; 133process.setUncaughtExceptionCaptureCallback = function(fn) { 134 const err = new ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE(); 135 err.stack = err.stack + '\n' + '-'.repeat(40) + '\n' + domainRequireStack; 136 throw err; 137}; 138 139 140let sendMakeCallbackDeprecation = false; 141function emitMakeCallbackDeprecation({ target, method }) { 142 if (!sendMakeCallbackDeprecation) { 143 process.emitWarning( 144 'Using a domain property in MakeCallback is deprecated. Use the ' + 145 'async_context variant of MakeCallback or the AsyncResource class ' + 146 'instead. ' + 147 `(Triggered by calling ${method?.name || '<anonymous>'} ` + 148 `on ${target?.constructor?.name}.)`, 149 'DeprecationWarning', 'DEP0097'); 150 sendMakeCallbackDeprecation = true; 151 } 152} 153 154function topLevelDomainCallback(cb, ...args) { 155 const domain = this.domain; 156 if (exports.active && domain) 157 emitMakeCallbackDeprecation({ target: this, method: cb }); 158 159 if (domain) 160 domain.enter(); 161 const ret = ReflectApply(cb, this, args); 162 if (domain) 163 domain.exit(); 164 165 return ret; 166} 167 168// It's possible to enter one domain while already inside 169// another one. The stack is each entered domain. 170let stack = []; 171exports._stack = stack; 172useDomainTrampoline(topLevelDomainCallback); 173 174function updateExceptionCapture() { 175 if (ArrayPrototypeEvery(stack, 176 (domain) => domain.listenerCount('error') === 0)) { 177 setUncaughtExceptionCaptureCallback(null); 178 } else { 179 setUncaughtExceptionCaptureCallback(null); 180 setUncaughtExceptionCaptureCallback((er) => { 181 return process.domain._errorHandler(er); 182 }); 183 } 184} 185 186 187process.on('newListener', (name, listener) => { 188 if (name === 'uncaughtException' && 189 listener !== domainUncaughtExceptionClear) { 190 // Make sure the first listener for `uncaughtException` always clears 191 // the domain stack. 192 process.removeListener(name, domainUncaughtExceptionClear); 193 process.prependListener(name, domainUncaughtExceptionClear); 194 } 195}); 196 197process.on('removeListener', (name, listener) => { 198 if (name === 'uncaughtException' && 199 listener !== domainUncaughtExceptionClear) { 200 // If the domain listener would be the only remaining one, remove it. 201 const listeners = process.listeners('uncaughtException'); 202 if (listeners.length === 1 && listeners[0] === domainUncaughtExceptionClear) 203 process.removeListener(name, domainUncaughtExceptionClear); 204 } 205}); 206 207function domainUncaughtExceptionClear() { 208 stack.length = 0; 209 exports.active = process.domain = null; 210 updateExceptionCapture(); 211} 212 213 214class Domain extends EventEmitter { 215 constructor() { 216 super(); 217 218 this.members = []; 219 this[kWeak] = new WeakReference(this); 220 asyncHook.enable(); 221 222 this.on('removeListener', updateExceptionCapture); 223 this.on('newListener', updateExceptionCapture); 224 } 225} 226 227exports.Domain = Domain; 228 229exports.create = exports.createDomain = function createDomain() { 230 return new Domain(); 231}; 232 233// The active domain is always the one that we're currently in. 234exports.active = null; 235Domain.prototype.members = undefined; 236 237// Called by process._fatalException in case an error was thrown. 238Domain.prototype._errorHandler = function(er) { 239 let caught = false; 240 241 if ((typeof er === 'object' && er !== null) || typeof er === 'function') { 242 ObjectDefineProperty(er, 'domain', { 243 __proto__: null, 244 configurable: true, 245 enumerable: false, 246 value: this, 247 writable: true, 248 }); 249 er.domainThrown = true; 250 } 251 // Pop all adjacent duplicates of the currently active domain from the stack. 252 // This is done to prevent a domain's error handler to run within the context 253 // of itself, and re-entering itself recursively handler as a result of an 254 // exception thrown in its context. 255 while (exports.active === this) { 256 this.exit(); 257 } 258 259 // The top-level domain-handler is handled separately. 260 // 261 // The reason is that if V8 was passed a command line option 262 // asking it to abort on an uncaught exception (currently 263 // "--abort-on-uncaught-exception"), we want an uncaught exception 264 // in the top-level domain error handler to make the 265 // process abort. Using try/catch here would always make V8 think 266 // that these exceptions are caught, and thus would prevent it from 267 // aborting in these cases. 268 if (stack.length === 0) { 269 // If there's no error handler, do not emit an 'error' event 270 // as this would throw an error, make the process exit, and thus 271 // prevent the process 'uncaughtException' event from being emitted 272 // if a listener is set. 273 if (EventEmitter.listenerCount(this, 'error') > 0) { 274 // Clear the uncaughtExceptionCaptureCallback so that we know that, since 275 // the top-level domain is not active anymore, it would be ok to abort on 276 // an uncaught exception at this point 277 setUncaughtExceptionCaptureCallback(null); 278 try { 279 caught = this.emit('error', er); 280 } finally { 281 updateExceptionCapture(); 282 } 283 } 284 } else { 285 // Wrap this in a try/catch so we don't get infinite throwing 286 try { 287 // One of three things will happen here. 288 // 289 // 1. There is a handler, caught = true 290 // 2. There is no handler, caught = false 291 // 3. It throws, caught = false 292 // 293 // If caught is false after this, then there's no need to exit() 294 // the domain, because we're going to crash the process anyway. 295 caught = this.emit('error', er); 296 } catch (er2) { 297 // The domain error handler threw! oh no! 298 // See if another domain can catch THIS error, 299 // or else crash on the original one. 300 updateExceptionCapture(); 301 if (stack.length) { 302 exports.active = process.domain = stack[stack.length - 1]; 303 caught = process.domain._errorHandler(er2); 304 } else { 305 // Pass on to the next exception handler. 306 throw er2; 307 } 308 } 309 } 310 311 // Exit all domains on the stack. Uncaught exceptions end the 312 // current tick and no domains should be left on the stack 313 // between ticks. 314 domainUncaughtExceptionClear(); 315 316 return caught; 317}; 318 319 320Domain.prototype.enter = function() { 321 // Note that this might be a no-op, but we still need 322 // to push it onto the stack so that we can pop it later. 323 exports.active = process.domain = this; 324 ArrayPrototypePush(stack, this); 325 updateExceptionCapture(); 326}; 327 328 329Domain.prototype.exit = function() { 330 // Don't do anything if this domain is not on the stack. 331 const index = ArrayPrototypeLastIndexOf(stack, this); 332 if (index === -1) return; 333 334 // Exit all domains until this one. 335 ArrayPrototypeSplice(stack, index); 336 337 exports.active = stack.length === 0 ? undefined : stack[stack.length - 1]; 338 process.domain = exports.active; 339 updateExceptionCapture(); 340}; 341 342 343// note: this works for timers as well. 344Domain.prototype.add = function(ee) { 345 // If the domain is already added, then nothing left to do. 346 if (ee.domain === this) 347 return; 348 349 // Has a domain already - remove it first. 350 if (ee.domain) 351 ee.domain.remove(ee); 352 353 // Check for circular Domain->Domain links. 354 // They cause big issues. 355 // 356 // For example: 357 // var d = domain.create(); 358 // var e = domain.create(); 359 // d.add(e); 360 // e.add(d); 361 // e.emit('error', er); // RangeError, stack overflow! 362 if (this.domain && (ee instanceof Domain)) { 363 for (let d = this.domain; d; d = d.domain) { 364 if (ee === d) return; 365 } 366 } 367 368 ObjectDefineProperty(ee, 'domain', { 369 __proto__: null, 370 configurable: true, 371 enumerable: false, 372 value: this, 373 writable: true, 374 }); 375 ArrayPrototypePush(this.members, ee); 376}; 377 378 379Domain.prototype.remove = function(ee) { 380 ee.domain = null; 381 const index = ArrayPrototypeIndexOf(this.members, ee); 382 if (index !== -1) 383 ArrayPrototypeSplice(this.members, index, 1); 384}; 385 386 387Domain.prototype.run = function(fn) { 388 this.enter(); 389 const ret = ReflectApply(fn, this, ArrayPrototypeSlice(arguments, 1)); 390 this.exit(); 391 392 return ret; 393}; 394 395 396function intercepted(_this, self, cb, fnargs) { 397 if (fnargs[0] && fnargs[0] instanceof Error) { 398 const er = fnargs[0]; 399 er.domainBound = cb; 400 er.domainThrown = false; 401 ObjectDefineProperty(er, 'domain', { 402 __proto__: null, 403 configurable: true, 404 enumerable: false, 405 value: self, 406 writable: true, 407 }); 408 self.emit('error', er); 409 return; 410 } 411 412 self.enter(); 413 const ret = ReflectApply(cb, _this, ArrayPrototypeSlice(fnargs, 1)); 414 self.exit(); 415 416 return ret; 417} 418 419 420Domain.prototype.intercept = function(cb) { 421 const self = this; 422 423 function runIntercepted() { 424 return intercepted(this, self, cb, arguments); 425 } 426 427 return runIntercepted; 428}; 429 430 431function bound(_this, self, cb, fnargs) { 432 self.enter(); 433 const ret = ReflectApply(cb, _this, fnargs); 434 self.exit(); 435 436 return ret; 437} 438 439 440Domain.prototype.bind = function(cb) { 441 const self = this; 442 443 function runBound() { 444 return bound(this, self, cb, arguments); 445 } 446 447 ObjectDefineProperty(runBound, 'domain', { 448 __proto__: null, 449 configurable: true, 450 enumerable: false, 451 value: this, 452 writable: true, 453 }); 454 455 return runBound; 456}; 457 458// Override EventEmitter methods to make it domain-aware. 459EventEmitter.usingDomains = true; 460 461const eventInit = EventEmitter.init; 462EventEmitter.init = function(opts) { 463 ObjectDefineProperty(this, 'domain', { 464 __proto__: null, 465 configurable: true, 466 enumerable: false, 467 value: null, 468 writable: true, 469 }); 470 if (exports.active && !(this instanceof exports.Domain)) { 471 this.domain = exports.active; 472 } 473 474 return FunctionPrototypeCall(eventInit, this, opts); 475}; 476 477const eventEmit = EventEmitter.prototype.emit; 478EventEmitter.prototype.emit = function emit(...args) { 479 const domain = this.domain; 480 481 const type = args[0]; 482 const shouldEmitError = type === 'error' && 483 this.listenerCount(type) > 0; 484 485 // Just call original `emit` if current EE instance has `error` 486 // handler, there's no active domain or this is process 487 if (shouldEmitError || domain === null || domain === undefined || 488 this === process) { 489 return ReflectApply(eventEmit, this, args); 490 } 491 492 if (type === 'error') { 493 const er = args.length > 1 && args[1] ? 494 args[1] : new ERR_UNHANDLED_ERROR(); 495 496 if (typeof er === 'object') { 497 er.domainEmitter = this; 498 ObjectDefineProperty(er, 'domain', { 499 __proto__: null, 500 configurable: true, 501 enumerable: false, 502 value: domain, 503 writable: true, 504 }); 505 er.domainThrown = false; 506 } 507 508 // Remove the current domain (and its duplicates) from the domains stack and 509 // set the active domain to its parent (if any) so that the domain's error 510 // handler doesn't run in its own context. This prevents any event emitter 511 // created or any exception thrown in that error handler from recursively 512 // executing that error handler. 513 const origDomainsStack = ArrayPrototypeSlice(stack); 514 const origActiveDomain = process.domain; 515 516 // Travel the domains stack from top to bottom to find the first domain 517 // instance that is not a duplicate of the current active domain. 518 let idx = stack.length - 1; 519 while (idx > -1 && process.domain === stack[idx]) { 520 --idx; 521 } 522 523 // Change the stack to not contain the current active domain, and only the 524 // domains above it on the stack. 525 if (idx < 0) { 526 stack.length = 0; 527 } else { 528 ArrayPrototypeSplice(stack, idx + 1); 529 } 530 531 // Change the current active domain 532 if (stack.length > 0) { 533 exports.active = process.domain = stack[stack.length - 1]; 534 } else { 535 exports.active = process.domain = null; 536 } 537 538 updateExceptionCapture(); 539 540 domain.emit('error', er); 541 542 // Now that the domain's error handler has completed, restore the domains 543 // stack and the active domain to their original values. 544 exports._stack = stack = origDomainsStack; 545 exports.active = process.domain = origActiveDomain; 546 updateExceptionCapture(); 547 548 return false; 549 } 550 551 domain.enter(); 552 const ret = ReflectApply(eventEmit, this, args); 553 domain.exit(); 554 555 return ret; 556}; 557