• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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