• 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 promiseHooks = require('internal/promise_hooks');
12
13const async_wrap = internalBinding('async_wrap');
14const { setCallbackTrampoline } = async_wrap;
15/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
16 * Environment::AsyncHooks::fields_[]. Each index tracks the number of active
17 * hooks for each type.
18 *
19 * async_id_fields is a Float64Array wrapping the double array of
20 * Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for
21 * the various asynchronous states of the application. These are:
22 *  kExecutionAsyncId: The async_id assigned to the resource responsible for the
23 *    current execution stack.
24 *  kTriggerAsyncId: The async_id of the resource that caused (or 'triggered')
25 *    the resource corresponding to the current execution stack.
26 *  kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
27 *  kDefaultTriggerAsyncId: Written immediately before a resource's constructor
28 *    that sets the value of the init()'s triggerAsyncId. The precedence order
29 *    of retrieving the triggerAsyncId value is:
30 *    1. the value passed directly to the constructor
31 *    2. value set in kDefaultTriggerAsyncId
32 *    3. executionAsyncId of the current resource.
33 *
34 * async_ids_stack is a Float64Array that contains part of the async ID
35 * stack. Each pushAsyncContext() call adds two doubles to it, and each
36 * popAsyncContext() call removes two doubles from it.
37 * It has a fixed size, so if that is exceeded, calls to the native
38 * side are used instead in pushAsyncContext() and popAsyncContext().
39 */
40const {
41  async_hook_fields,
42  async_id_fields,
43  execution_async_resources,
44} = async_wrap;
45// Store the pair executionAsyncId and triggerAsyncId in a AliasedFloat64Array
46// in Environment::AsyncHooks::async_ids_stack_ which tracks the resource
47// responsible for the current execution stack. This is unwound as each resource
48// exits. In the case of a fatal exception this stack is emptied after calling
49// each hook's after() callback.
50const {
51  pushAsyncContext: pushAsyncContext_,
52  popAsyncContext: popAsyncContext_,
53  executionAsyncResource: executionAsyncResource_,
54  clearAsyncIdStack,
55} = 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
140const topLevelResource = {};
141
142function executionAsyncResource() {
143  // Indicate to the native layer that this function is likely to be used,
144  // in which case it will inform JS about the current async resource via
145  // the trampoline above.
146  async_hook_fields[kUsesExecutionAsyncResource] = 1;
147
148  const index = async_hook_fields[kStackLength] - 1;
149  if (index === -1) return topLevelResource;
150  const resource = execution_async_resources[index] ||
151    executionAsyncResource_(index);
152  return lookupPublicResource(resource);
153}
154
155function inspectExceptionValue(e) {
156  inspect ??= require('internal/util/inspect').inspect;
157  return { message: inspect(e) };
158}
159
160// Used to fatally abort the process if a callback throws.
161function fatalError(e) {
162  if (typeof e?.stack === 'string') {
163    process._rawDebug(e.stack);
164  } else {
165    const o = inspectExceptionValue(e);
166    ErrorCaptureStackTrace(o, fatalError);
167    process._rawDebug(o.stack);
168  }
169
170  const { getOptionValue } = require('internal/options');
171  if (getOptionValue('--abort-on-uncaught-exception')) {
172    process.abort();
173  }
174  process.exit(1);
175}
176
177function lookupPublicResource(resource) {
178  if (typeof resource !== 'object' || resource === null) return resource;
179  // TODO(addaleax): Merge this with owner_symbol and use it across all
180  // AsyncWrap instances.
181  const publicResource = resource[resource_symbol];
182  if (publicResource !== undefined)
183    return publicResource;
184  return resource;
185}
186
187// Emit From Native //
188
189// Used by C++ to call all init() callbacks. Because some state can be setup
190// from C++ there's no need to perform all the same operations as in
191// emitInitScript.
192function emitInitNative(asyncId, type, triggerAsyncId, resource) {
193  active_hooks.call_depth += 1;
194  resource = lookupPublicResource(resource);
195  // Use a single try/catch for all hooks to avoid setting up one per iteration.
196  try {
197    // Using var here instead of let because "for (var ...)" is faster than let.
198    // Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364
199    // eslint-disable-next-line no-var
200    for (var i = 0; i < active_hooks.array.length; i++) {
201      if (typeof active_hooks.array[i][init_symbol] === 'function') {
202        active_hooks.array[i][init_symbol](
203          asyncId, type, triggerAsyncId,
204          resource,
205        );
206      }
207    }
208  } catch (e) {
209    fatalError(e);
210  } finally {
211    active_hooks.call_depth -= 1;
212  }
213
214  // Hooks can only be restored if there have been no recursive hook calls.
215  // Also the active hooks do not need to be restored if enable()/disable()
216  // weren't called during hook execution, in which case active_hooks.tmp_array
217  // will be null.
218  if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
219    restoreActiveHooks();
220  }
221}
222
223// Called from native. The asyncId stack handling is taken care of there
224// before this is called.
225function emitHook(symbol, asyncId) {
226  active_hooks.call_depth += 1;
227  // Use a single try/catch for all hook to avoid setting up one per
228  // iteration.
229  try {
230    // Using var here instead of let because "for (var ...)" is faster than let.
231    // Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364
232    // eslint-disable-next-line no-var
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    __proto__: null,
259    value: name,
260  });
261  return fn;
262}
263
264// Manage Active Hooks //
265
266function getHookArrays() {
267  if (active_hooks.call_depth === 0)
268    return [active_hooks.array, async_hook_fields];
269  // If this hook is being enabled while in the middle of processing the array
270  // of currently active hooks then duplicate the current set of active hooks
271  // and store this there. This shouldn't fire until the next time hooks are
272  // processed.
273  if (active_hooks.tmp_array === null)
274    storeActiveHooks();
275  return [active_hooks.tmp_array, active_hooks.tmp_fields];
276}
277
278
279function storeActiveHooks() {
280  active_hooks.tmp_array = ArrayPrototypeSlice(active_hooks.array);
281  // Don't want to make the assumption that kInit to kDestroy are indexes 0 to
282  // 4. So do this the long way.
283  active_hooks.tmp_fields = [];
284  copyHooks(active_hooks.tmp_fields, async_hook_fields);
285}
286
287function copyHooks(destination, source) {
288  destination[kInit] = source[kInit];
289  destination[kBefore] = source[kBefore];
290  destination[kAfter] = source[kAfter];
291  destination[kDestroy] = source[kDestroy];
292  destination[kPromiseResolve] = source[kPromiseResolve];
293}
294
295
296// Then restore the correct hooks array in case any hooks were added/removed
297// during hook callback execution.
298function restoreActiveHooks() {
299  active_hooks.array = active_hooks.tmp_array;
300  copyHooks(async_hook_fields, active_hooks.tmp_fields);
301
302  active_hooks.tmp_array = null;
303  active_hooks.tmp_fields = null;
304}
305
306function trackPromise(promise, parent) {
307  if (promise[async_id_symbol]) {
308    return;
309  }
310
311  // Get trigger id from parent async id before making the async id of the
312  // child so if a new one must be made it will be lower than the child.
313  const triggerAsyncId = parent ? getOrSetAsyncId(parent) :
314    getDefaultTriggerAsyncId();
315
316  promise[async_id_symbol] = newAsyncId();
317  promise[trigger_async_id_symbol] = triggerAsyncId;
318}
319
320function promiseInitHook(promise, parent) {
321  trackPromise(promise, parent);
322  const asyncId = promise[async_id_symbol];
323  const triggerAsyncId = promise[trigger_async_id_symbol];
324  emitInitScript(asyncId, 'PROMISE', triggerAsyncId, promise);
325}
326
327function promiseInitHookWithDestroyTracking(promise, parent) {
328  promiseInitHook(promise, parent);
329  destroyTracking(promise, parent);
330}
331
332function destroyTracking(promise, parent) {
333  trackPromise(promise, parent);
334  const asyncId = promise[async_id_symbol];
335  registerDestroyHook(promise, asyncId);
336}
337
338function promiseBeforeHook(promise) {
339  trackPromise(promise);
340  const asyncId = promise[async_id_symbol];
341  const triggerId = promise[trigger_async_id_symbol];
342  emitBeforeScript(asyncId, triggerId, promise);
343}
344
345function promiseAfterHook(promise) {
346  trackPromise(promise);
347  const asyncId = promise[async_id_symbol];
348  if (hasHooks(kAfter)) {
349    emitAfterNative(asyncId);
350  }
351  if (asyncId === executionAsyncId()) {
352    // This condition might not be true if async_hooks was enabled during
353    // the promise callback execution.
354    // Popping it off the stack can be skipped in that case, because it is
355    // known that it would correspond to exactly one call with
356    // PromiseHookType::kBefore that was not witnessed by the PromiseHook.
357    popAsyncContext(asyncId);
358  }
359}
360
361function promiseResolveHook(promise) {
362  trackPromise(promise);
363  const asyncId = promise[async_id_symbol];
364  emitPromiseResolveNative(asyncId);
365}
366
367let wantPromiseHook = false;
368function enableHooks() {
369  async_hook_fields[kCheck] += 1;
370
371  setCallbackTrampoline(callbackTrampoline);
372}
373
374let stopPromiseHook;
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  if (stopPromiseHook) stopPromiseHook();
385  stopPromiseHook = promiseHooks.createHook({
386    init: initHook,
387    before: promiseBeforeHook,
388    after: promiseAfterHook,
389    settled: promiseResolveHooksExist() ? promiseResolveHook : undefined,
390  });
391}
392
393function disableHooks() {
394  async_hook_fields[kCheck] -= 1;
395
396  wantPromiseHook = false;
397
398  setCallbackTrampoline();
399
400  // Delay the call to `disablePromiseHook()` because we might currently be
401  // between the `before` and `after` calls of a Promise.
402  enqueueMicrotask(disablePromiseHookIfNecessary);
403}
404
405function disablePromiseHookIfNecessary() {
406  if (!wantPromiseHook && stopPromiseHook) {
407    stopPromiseHook();
408  }
409}
410
411// Internal Embedder API //
412
413// Increment the internal id counter and return the value. Important that the
414// counter increment first. Since it's done the same way in
415// Environment::new_async_uid()
416function newAsyncId() {
417  return ++async_id_fields[kAsyncIdCounter];
418}
419
420function getOrSetAsyncId(object) {
421  if (ObjectPrototypeHasOwnProperty(object, async_id_symbol)) {
422    return object[async_id_symbol];
423  }
424
425  return object[async_id_symbol] = newAsyncId();
426}
427
428
429// Return the triggerAsyncId meant for the constructor calling it. It's up to
430// the user to safeguard this call and make sure it's zero'd out when the
431// constructor is complete.
432function getDefaultTriggerAsyncId() {
433  const defaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
434  // If defaultTriggerAsyncId isn't set, use the executionAsyncId
435  if (defaultTriggerAsyncId < 0)
436    return async_id_fields[kExecutionAsyncId];
437  return defaultTriggerAsyncId;
438}
439
440
441function clearDefaultTriggerAsyncId() {
442  async_id_fields[kDefaultTriggerAsyncId] = -1;
443}
444
445/**
446 * Sets a default top level trigger ID to be used
447 * @template {Array<unknown>} T
448 * @template {unknown} R
449 * @param {number} triggerAsyncId
450 * @param { (...T: args) => R } block
451 * @param  {T} args
452 * @returns {R}
453 */
454function defaultTriggerAsyncIdScope(triggerAsyncId, block, ...args) {
455  if (triggerAsyncId === undefined)
456    return block.apply(null, args);
457  // CHECK(NumberIsSafeInteger(triggerAsyncId))
458  // CHECK(triggerAsyncId > 0)
459  const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
460  async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId;
461
462  try {
463    return block.apply(null, args);
464  } finally {
465    async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId;
466  }
467}
468
469function hasHooks(key) {
470  return async_hook_fields[key] > 0;
471}
472
473function enabledHooksExist() {
474  return hasHooks(kCheck);
475}
476
477function initHooksExist() {
478  return hasHooks(kInit);
479}
480
481function afterHooksExist() {
482  return hasHooks(kAfter);
483}
484
485function destroyHooksExist() {
486  return hasHooks(kDestroy);
487}
488
489function promiseResolveHooksExist() {
490  return hasHooks(kPromiseResolve);
491}
492
493
494function emitInitScript(asyncId, type, triggerAsyncId, resource) {
495  // Short circuit all checks for the common case. Which is that no hooks have
496  // been set. Do this to remove performance impact for embedders (and core).
497  if (!hasHooks(kInit))
498    return;
499
500  if (triggerAsyncId === null) {
501    triggerAsyncId = getDefaultTriggerAsyncId();
502  }
503
504  emitInitNative(asyncId, type, triggerAsyncId, resource);
505}
506
507
508function emitBeforeScript(asyncId, triggerAsyncId, resource) {
509  pushAsyncContext(asyncId, triggerAsyncId, resource);
510
511  if (hasHooks(kBefore))
512    emitBeforeNative(asyncId);
513}
514
515
516function emitAfterScript(asyncId) {
517  if (hasHooks(kAfter))
518    emitAfterNative(asyncId);
519
520  popAsyncContext(asyncId);
521}
522
523
524function emitDestroyScript(asyncId) {
525  // Return early if there are no destroy callbacks, or invalid asyncId.
526  if (!hasHooks(kDestroy) || asyncId <= 0)
527    return;
528  async_wrap.queueDestroyAsyncId(asyncId);
529}
530
531
532function hasAsyncIdStack() {
533  return hasHooks(kStackLength);
534}
535
536
537// This is the equivalent of the native push_async_ids() call.
538function pushAsyncContext(asyncId, triggerAsyncId, resource) {
539  const offset = async_hook_fields[kStackLength];
540  execution_async_resources[offset] = resource;
541  if (offset * 2 >= async_wrap.async_ids_stack.length)
542    return pushAsyncContext_(asyncId, triggerAsyncId);
543  async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
544  async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
545  async_hook_fields[kStackLength]++;
546  async_id_fields[kExecutionAsyncId] = asyncId;
547  async_id_fields[kTriggerAsyncId] = triggerAsyncId;
548}
549
550
551// This is the equivalent of the native pop_async_ids() call.
552function popAsyncContext(asyncId) {
553  const stackLength = async_hook_fields[kStackLength];
554  if (stackLength === 0) return false;
555
556  if (enabledHooksExist() && async_id_fields[kExecutionAsyncId] !== asyncId) {
557    // Do the same thing as the native code (i.e. crash hard).
558    return popAsyncContext_(asyncId);
559  }
560
561  const offset = stackLength - 1;
562  async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
563  async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
564  execution_async_resources.pop();
565  async_hook_fields[kStackLength] = offset;
566  return offset > 0;
567}
568
569
570function executionAsyncId() {
571  return async_id_fields[kExecutionAsyncId];
572}
573
574function triggerAsyncId() {
575  return async_id_fields[kTriggerAsyncId];
576}
577
578
579module.exports = {
580  executionAsyncId,
581  triggerAsyncId,
582  // Private API
583  getHookArrays,
584  symbols: {
585    async_id_symbol, trigger_async_id_symbol,
586    init_symbol, before_symbol, after_symbol, destroy_symbol,
587    promise_resolve_symbol, owner_symbol,
588  },
589  constants: {
590    kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
591  },
592  enableHooks,
593  disableHooks,
594  updatePromiseHookMode,
595  clearDefaultTriggerAsyncId,
596  clearAsyncIdStack,
597  hasAsyncIdStack,
598  executionAsyncResource,
599  // Internal Embedder API
600  newAsyncId,
601  getOrSetAsyncId,
602  getDefaultTriggerAsyncId,
603  defaultTriggerAsyncIdScope,
604  enabledHooksExist,
605  initHooksExist,
606  afterHooksExist,
607  destroyHooksExist,
608  emitInit: emitInitScript,
609  emitBefore: emitBeforeScript,
610  emitAfter: emitAfterScript,
611  emitDestroy: emitDestroyScript,
612  pushAsyncContext,
613  popAsyncContext,
614  registerDestroyHook,
615  useDomainTrampoline,
616  nativeHooks: {
617    init: emitInitNative,
618    before: emitBeforeNative,
619    after: emitAfterNative,
620    destroy: emitDestroyNative,
621    promise_resolve: emitPromiseResolveNative,
622  },
623  asyncWrap: {
624    Providers: async_wrap.Providers,
625  },
626};
627