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