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