1'use strict'; 2 3const { 4 ArrayPrototypeSlice, 5 ErrorCaptureStackTrace, 6 ObjectPrototypeHasOwnProperty, 7 ObjectDefineProperty, 8 Symbol, 9} = primordials; 10 11const async_wrap = internalBinding('async_wrap'); 12const { setCallbackTrampoline } = async_wrap; 13/* async_hook_fields is a Uint32Array wrapping the uint32_t array of 14 * Environment::AsyncHooks::fields_[]. Each index tracks the number of active 15 * hooks for each type. 16 * 17 * async_id_fields is a Float64Array wrapping the double array of 18 * Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for 19 * the various asynchronous states of the application. These are: 20 * kExecutionAsyncId: The async_id assigned to the resource responsible for the 21 * current execution stack. 22 * kTriggerAsyncId: The async_id of the resource that caused (or 'triggered') 23 * the resource corresponding to the current execution stack. 24 * kAsyncIdCounter: Incremental counter tracking the next assigned async_id. 25 * kDefaultTriggerAsyncId: Written immediately before a resource's constructor 26 * that sets the value of the init()'s triggerAsyncId. The precedence order 27 * of retrieving the triggerAsyncId value is: 28 * 1. the value passed directly to the constructor 29 * 2. value set in kDefaultTriggerAsyncId 30 * 3. executionAsyncId of the current resource. 31 * 32 * async_ids_stack is a Float64Array that contains part of the async ID 33 * stack. Each pushAsyncContext() call adds two doubles to it, and each 34 * popAsyncContext() call removes two doubles from it. 35 * It has a fixed size, so if that is exceeded, calls to the native 36 * side are used instead in pushAsyncContext() and popAsyncContext(). 37 */ 38const { 39 async_hook_fields, 40 async_id_fields, 41 execution_async_resources 42} = async_wrap; 43// Store the pair executionAsyncId and triggerAsyncId in a AliasedFloat64Array 44// in Environment::AsyncHooks::async_ids_stack_ which tracks the resource 45// responsible for the current execution stack. This is unwound as each resource 46// exits. In the case of a fatal exception this stack is emptied after calling 47// each hook's after() callback. 48const { 49 pushAsyncContext: pushAsyncContext_, 50 popAsyncContext: popAsyncContext_, 51 executionAsyncResource: executionAsyncResource_, 52 clearAsyncIdStack, 53} = async_wrap; 54// For performance reasons, only track Promises when a hook is enabled. 55const { setPromiseHooks } = async_wrap; 56// Properties in active_hooks are used to keep track of the set of hooks being 57// executed in case another hook is enabled/disabled. The new set of hooks is 58// then restored once the active set of hooks is finished executing. 59const active_hooks = { 60 // Array of all AsyncHooks that will be iterated whenever an async event 61 // fires. Using var instead of (preferably const) in order to assign 62 // active_hooks.tmp_array if a hook is enabled/disabled during hook 63 // execution. 64 array: [], 65 // Use a counter to track nested calls of async hook callbacks and make sure 66 // the active_hooks.array isn't altered mid execution. 67 call_depth: 0, 68 // Use to temporarily store and updated active_hooks.array if the user 69 // enables or disables a hook while hooks are being processed. If a hook is 70 // enabled() or disabled() during hook execution then the current set of 71 // active hooks is duplicated and set equal to active_hooks.tmp_array. Any 72 // subsequent changes are on the duplicated array. When all hooks have 73 // completed executing active_hooks.tmp_array is assigned to 74 // active_hooks.array. 75 tmp_array: null, 76 // Keep track of the field counts held in active_hooks.tmp_array. Because the 77 // async_hook_fields can't be reassigned, store each uint32 in an array that 78 // is written back to async_hook_fields when active_hooks.array is restored. 79 tmp_fields: null 80}; 81 82const { registerDestroyHook } = async_wrap; 83const { enqueueMicrotask } = internalBinding('task_queue'); 84const { resource_symbol, owner_symbol } = internalBinding('symbols'); 85 86// Each constant tracks how many callbacks there are for any given step of 87// async execution. These are tracked so if the user didn't include callbacks 88// for a given step, that step can bail out early. 89const { 90 kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve, 91 kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId, 92 kDefaultTriggerAsyncId, kStackLength, kUsesExecutionAsyncResource, 93} = async_wrap.constants; 94 95const { async_id_symbol, 96 trigger_async_id_symbol } = internalBinding('symbols'); 97 98// Lazy load of internal/util/inspect; 99let inspect; 100 101// Used in AsyncHook and AsyncResource. 102const init_symbol = Symbol('init'); 103const before_symbol = Symbol('before'); 104const after_symbol = Symbol('after'); 105const destroy_symbol = Symbol('destroy'); 106const promise_resolve_symbol = Symbol('promiseResolve'); 107const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative'); 108const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative'); 109const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative'); 110const emitPromiseResolveNative = 111 emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative'); 112 113let domain_cb; 114function useDomainTrampoline(fn) { 115 domain_cb = fn; 116} 117 118function callbackTrampoline(asyncId, resource, cb, ...args) { 119 const index = async_hook_fields[kStackLength] - 1; 120 execution_async_resources[index] = resource; 121 122 if (asyncId !== 0 && hasHooks(kBefore)) 123 emitBeforeNative(asyncId); 124 125 let result; 126 if (asyncId === 0 && typeof domain_cb === 'function') { 127 args.unshift(cb); 128 result = domain_cb.apply(this, args); 129 } else { 130 result = cb.apply(this, args); 131 } 132 133 if (asyncId !== 0 && hasHooks(kAfter)) 134 emitAfterNative(asyncId); 135 136 execution_async_resources.pop(); 137 return result; 138} 139 140setCallbackTrampoline(callbackTrampoline); 141 142const topLevelResource = {}; 143 144function executionAsyncResource() { 145 // Indicate to the native layer that this function is likely to be used, 146 // in which case it will inform JS about the current async resource via 147 // the trampoline above. 148 async_hook_fields[kUsesExecutionAsyncResource] = 1; 149 150 const index = async_hook_fields[kStackLength] - 1; 151 if (index === -1) return topLevelResource; 152 const resource = execution_async_resources[index] || 153 executionAsyncResource_(index); 154 return lookupPublicResource(resource); 155} 156 157function inspectExceptionValue(e) { 158 inspect = inspect ?? require('internal/util/inspect').inspect; 159 return { message: inspect(e) }; 160} 161 162// Used to fatally abort the process if a callback throws. 163function fatalError(e) { 164 if (typeof e?.stack === 'string') { 165 process._rawDebug(e.stack); 166 } else { 167 const o = inspectExceptionValue(e); 168 ErrorCaptureStackTrace(o, fatalError); 169 process._rawDebug(o.stack); 170 } 171 172 const { getOptionValue } = require('internal/options'); 173 if (getOptionValue('--abort-on-uncaught-exception')) { 174 process.abort(); 175 } 176 process.exit(1); 177} 178 179function lookupPublicResource(resource) { 180 if (typeof resource !== 'object' || resource === null) return resource; 181 // TODO(addaleax): Merge this with owner_symbol and use it across all 182 // AsyncWrap instances. 183 const publicResource = resource[resource_symbol]; 184 if (publicResource !== undefined) 185 return publicResource; 186 return resource; 187} 188 189// Emit From Native // 190 191// Used by C++ to call all init() callbacks. Because some state can be setup 192// from C++ there's no need to perform all the same operations as in 193// emitInitScript. 194function emitInitNative(asyncId, type, triggerAsyncId, resource) { 195 active_hooks.call_depth += 1; 196 resource = lookupPublicResource(resource); 197 // Use a single try/catch for all hooks to avoid setting up one per iteration. 198 try { 199 // Using var here instead of let because "for (var ...)" is faster than let. 200 // Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364 201 for (var i = 0; i < active_hooks.array.length; i++) { 202 if (typeof active_hooks.array[i][init_symbol] === 'function') { 203 active_hooks.array[i][init_symbol]( 204 asyncId, type, triggerAsyncId, 205 resource 206 ); 207 } 208 } 209 } catch (e) { 210 fatalError(e); 211 } finally { 212 active_hooks.call_depth -= 1; 213 } 214 215 // Hooks can only be restored if there have been no recursive hook calls. 216 // Also the active hooks do not need to be restored if enable()/disable() 217 // weren't called during hook execution, in which case active_hooks.tmp_array 218 // will be null. 219 if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) { 220 restoreActiveHooks(); 221 } 222} 223 224// Called from native. The asyncId stack handling is taken care of there 225// before this is called. 226function emitHook(symbol, asyncId) { 227 active_hooks.call_depth += 1; 228 // Use a single try/catch for all hook to avoid setting up one per 229 // iteration. 230 try { 231 // Using var here instead of let because "for (var ...)" is faster than let. 232 // Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364 233 for (var i = 0; i < active_hooks.array.length; i++) { 234 if (typeof active_hooks.array[i][symbol] === 'function') { 235 active_hooks.array[i][symbol](asyncId); 236 } 237 } 238 } catch (e) { 239 fatalError(e); 240 } finally { 241 active_hooks.call_depth -= 1; 242 } 243 244 // Hooks can only be restored if there have been no recursive hook calls. 245 // Also the active hooks do not need to be restored if enable()/disable() 246 // weren't called during hook execution, in which case 247 // active_hooks.tmp_array will be null. 248 if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) { 249 restoreActiveHooks(); 250 } 251} 252 253function emitHookFactory(symbol, name) { 254 const fn = emitHook.bind(undefined, symbol); 255 256 // Set the name property of the function as it looks good in the stack trace. 257 ObjectDefineProperty(fn, 'name', { 258 value: name 259 }); 260 return fn; 261} 262 263// Manage Active Hooks // 264 265function getHookArrays() { 266 if (active_hooks.call_depth === 0) 267 return [active_hooks.array, async_hook_fields]; 268 // If this hook is being enabled while in the middle of processing the array 269 // of currently active hooks then duplicate the current set of active hooks 270 // and store this there. This shouldn't fire until the next time hooks are 271 // processed. 272 if (active_hooks.tmp_array === null) 273 storeActiveHooks(); 274 return [active_hooks.tmp_array, active_hooks.tmp_fields]; 275} 276 277 278function storeActiveHooks() { 279 active_hooks.tmp_array = ArrayPrototypeSlice(active_hooks.array); 280 // Don't want to make the assumption that kInit to kDestroy are indexes 0 to 281 // 4. So do this the long way. 282 active_hooks.tmp_fields = []; 283 copyHooks(active_hooks.tmp_fields, async_hook_fields); 284} 285 286function copyHooks(destination, source) { 287 destination[kInit] = source[kInit]; 288 destination[kBefore] = source[kBefore]; 289 destination[kAfter] = source[kAfter]; 290 destination[kDestroy] = source[kDestroy]; 291 destination[kPromiseResolve] = source[kPromiseResolve]; 292} 293 294 295// Then restore the correct hooks array in case any hooks were added/removed 296// during hook callback execution. 297function restoreActiveHooks() { 298 active_hooks.array = active_hooks.tmp_array; 299 copyHooks(async_hook_fields, active_hooks.tmp_fields); 300 301 active_hooks.tmp_array = null; 302 active_hooks.tmp_fields = null; 303} 304 305function trackPromise(promise, parent) { 306 if (promise[async_id_symbol]) { 307 return; 308 } 309 310 // Get trigger id from parent async id before making the async id of the 311 // child so if a new one must be made it will be lower than the child. 312 const triggerAsyncId = parent ? getOrSetAsyncId(parent) : 313 getDefaultTriggerAsyncId(); 314 315 promise[async_id_symbol] = newAsyncId(); 316 promise[trigger_async_id_symbol] = triggerAsyncId; 317} 318 319function promiseInitHook(promise, parent) { 320 trackPromise(promise, parent); 321 const asyncId = promise[async_id_symbol]; 322 const triggerAsyncId = promise[trigger_async_id_symbol]; 323 emitInitScript(asyncId, 'PROMISE', triggerAsyncId, promise); 324} 325 326function promiseInitHookWithDestroyTracking(promise, parent) { 327 promiseInitHook(promise, parent); 328 destroyTracking(promise, parent); 329} 330 331const destroyedSymbol = Symbol('destroyed'); 332 333function destroyTracking(promise, parent) { 334 trackPromise(promise, parent); 335 const asyncId = promise[async_id_symbol]; 336 const destroyed = { destroyed: false }; 337 promise[destroyedSymbol] = destroyed; 338 registerDestroyHook(promise, asyncId, destroyed); 339} 340 341function promiseBeforeHook(promise) { 342 trackPromise(promise); 343 const asyncId = promise[async_id_symbol]; 344 const triggerId = promise[trigger_async_id_symbol]; 345 emitBeforeScript(asyncId, triggerId, promise); 346} 347 348function promiseAfterHook(promise) { 349 trackPromise(promise); 350 const asyncId = promise[async_id_symbol]; 351 if (hasHooks(kAfter)) { 352 emitAfterNative(asyncId); 353 } 354 if (asyncId === executionAsyncId()) { 355 // This condition might not be true if async_hooks was enabled during 356 // the promise callback execution. 357 // Popping it off the stack can be skipped in that case, because it is 358 // known that it would correspond to exactly one call with 359 // PromiseHookType::kBefore that was not witnessed by the PromiseHook. 360 popAsyncContext(asyncId); 361 } 362} 363 364function promiseResolveHook(promise) { 365 trackPromise(promise); 366 const asyncId = promise[async_id_symbol]; 367 emitPromiseResolveNative(asyncId); 368} 369 370let wantPromiseHook = false; 371function enableHooks() { 372 async_hook_fields[kCheck] += 1; 373} 374 375function updatePromiseHookMode() { 376 wantPromiseHook = true; 377 let initHook; 378 if (initHooksExist()) { 379 initHook = destroyHooksExist() ? promiseInitHookWithDestroyTracking : 380 promiseInitHook; 381 } else if (destroyHooksExist()) { 382 initHook = destroyTracking; 383 } 384 setPromiseHooks( 385 initHook, 386 promiseBeforeHook, 387 promiseAfterHook, 388 promiseResolveHooksExist() ? promiseResolveHook : undefined, 389 ); 390} 391 392function disableHooks() { 393 async_hook_fields[kCheck] -= 1; 394 395 wantPromiseHook = false; 396 397 // Delay the call to `disablePromiseHook()` because we might currently be 398 // between the `before` and `after` calls of a Promise. 399 enqueueMicrotask(disablePromiseHookIfNecessary); 400} 401 402function disablePromiseHookIfNecessary() { 403 if (!wantPromiseHook) { 404 setPromiseHooks(undefined, undefined, undefined, undefined); 405 } 406} 407 408// Internal Embedder API // 409 410// Increment the internal id counter and return the value. Important that the 411// counter increment first. Since it's done the same way in 412// Environment::new_async_uid() 413function newAsyncId() { 414 return ++async_id_fields[kAsyncIdCounter]; 415} 416 417function getOrSetAsyncId(object) { 418 if (ObjectPrototypeHasOwnProperty(object, async_id_symbol)) { 419 return object[async_id_symbol]; 420 } 421 422 return object[async_id_symbol] = newAsyncId(); 423} 424 425 426// Return the triggerAsyncId meant for the constructor calling it. It's up to 427// the user to safeguard this call and make sure it's zero'd out when the 428// constructor is complete. 429function getDefaultTriggerAsyncId() { 430 const defaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId]; 431 // If defaultTriggerAsyncId isn't set, use the executionAsyncId 432 if (defaultTriggerAsyncId < 0) 433 return async_id_fields[kExecutionAsyncId]; 434 return defaultTriggerAsyncId; 435} 436 437 438function clearDefaultTriggerAsyncId() { 439 async_id_fields[kDefaultTriggerAsyncId] = -1; 440} 441 442 443function defaultTriggerAsyncIdScope(triggerAsyncId, block, ...args) { 444 if (triggerAsyncId === undefined) 445 return block.apply(null, args); 446 // CHECK(NumberIsSafeInteger(triggerAsyncId)) 447 // CHECK(triggerAsyncId > 0) 448 const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId]; 449 async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId; 450 451 try { 452 return block.apply(null, args); 453 } finally { 454 async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId; 455 } 456} 457 458function hasHooks(key) { 459 return async_hook_fields[key] > 0; 460} 461 462function enabledHooksExist() { 463 return hasHooks(kCheck); 464} 465 466function initHooksExist() { 467 return hasHooks(kInit); 468} 469 470function afterHooksExist() { 471 return hasHooks(kAfter); 472} 473 474function destroyHooksExist() { 475 return hasHooks(kDestroy); 476} 477 478function promiseResolveHooksExist() { 479 return hasHooks(kPromiseResolve); 480} 481 482 483function emitInitScript(asyncId, type, triggerAsyncId, resource) { 484 // Short circuit all checks for the common case. Which is that no hooks have 485 // been set. Do this to remove performance impact for embedders (and core). 486 if (!hasHooks(kInit)) 487 return; 488 489 if (triggerAsyncId === null) { 490 triggerAsyncId = getDefaultTriggerAsyncId(); 491 } 492 493 emitInitNative(asyncId, type, triggerAsyncId, resource); 494} 495 496 497function emitBeforeScript(asyncId, triggerAsyncId, resource) { 498 pushAsyncContext(asyncId, triggerAsyncId, resource); 499 500 if (hasHooks(kBefore)) 501 emitBeforeNative(asyncId); 502} 503 504 505function emitAfterScript(asyncId) { 506 if (hasHooks(kAfter)) 507 emitAfterNative(asyncId); 508 509 popAsyncContext(asyncId); 510} 511 512 513function emitDestroyScript(asyncId) { 514 // Return early if there are no destroy callbacks, or invalid asyncId. 515 if (!hasHooks(kDestroy) || asyncId <= 0) 516 return; 517 async_wrap.queueDestroyAsyncId(asyncId); 518} 519 520 521function hasAsyncIdStack() { 522 return hasHooks(kStackLength); 523} 524 525 526// This is the equivalent of the native push_async_ids() call. 527function pushAsyncContext(asyncId, triggerAsyncId, resource) { 528 const offset = async_hook_fields[kStackLength]; 529 execution_async_resources[offset] = resource; 530 if (offset * 2 >= async_wrap.async_ids_stack.length) 531 return pushAsyncContext_(asyncId, triggerAsyncId); 532 async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId]; 533 async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId]; 534 async_hook_fields[kStackLength]++; 535 async_id_fields[kExecutionAsyncId] = asyncId; 536 async_id_fields[kTriggerAsyncId] = triggerAsyncId; 537} 538 539 540// This is the equivalent of the native pop_async_ids() call. 541function popAsyncContext(asyncId) { 542 const stackLength = async_hook_fields[kStackLength]; 543 if (stackLength === 0) return false; 544 545 if (enabledHooksExist() && async_id_fields[kExecutionAsyncId] !== asyncId) { 546 // Do the same thing as the native code (i.e. crash hard). 547 return popAsyncContext_(asyncId); 548 } 549 550 const offset = stackLength - 1; 551 async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset]; 552 async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1]; 553 execution_async_resources.pop(); 554 async_hook_fields[kStackLength] = offset; 555 return offset > 0; 556} 557 558 559function executionAsyncId() { 560 return async_id_fields[kExecutionAsyncId]; 561} 562 563function triggerAsyncId() { 564 return async_id_fields[kTriggerAsyncId]; 565} 566 567 568module.exports = { 569 executionAsyncId, 570 triggerAsyncId, 571 // Private API 572 getHookArrays, 573 symbols: { 574 async_id_symbol, trigger_async_id_symbol, 575 init_symbol, before_symbol, after_symbol, destroy_symbol, 576 promise_resolve_symbol, owner_symbol 577 }, 578 constants: { 579 kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve 580 }, 581 enableHooks, 582 disableHooks, 583 updatePromiseHookMode, 584 clearDefaultTriggerAsyncId, 585 clearAsyncIdStack, 586 hasAsyncIdStack, 587 executionAsyncResource, 588 // Internal Embedder API 589 newAsyncId, 590 getOrSetAsyncId, 591 getDefaultTriggerAsyncId, 592 defaultTriggerAsyncIdScope, 593 enabledHooksExist, 594 initHooksExist, 595 afterHooksExist, 596 destroyHooksExist, 597 emitInit: emitInitScript, 598 emitBefore: emitBeforeScript, 599 emitAfter: emitAfterScript, 600 emitDestroy: emitDestroyScript, 601 registerDestroyHook, 602 useDomainTrampoline, 603 nativeHooks: { 604 init: emitInitNative, 605 before: emitBeforeNative, 606 after: emitAfterNative, 607 destroy: emitDestroyNative, 608 promise_resolve: emitPromiseResolveNative 609 } 610}; 611