Skip to content

Commit 3e73ea8

Browse files
trevnorrisMylesBorins
authored andcommitted
async_hooks: improve comments and function names
* Reword several of the comments to be more descriptive. * Rename functions to better indicate what they are doing. * Change AsyncHooks::uid_fields_ to be a fixed array instead of a pointer. * Define regex early so line ends before 80 columns. * Remove obsolete comments. * Rename AsyncHooks::InitScope::uid_fields_ to uid_fields_ptr_ because using the same name as AsyncHooks::uid_fields_ was confusing. * Place variables that are used to store the new set of hooks if another hook is enabled/disabled during hook execution into an object to act as a namespace. PR-URL: #14722 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
1 parent fadccba commit 3e73ea8

File tree

3 files changed

+109
-97
lines changed

3 files changed

+109
-97
lines changed

lib/async_hooks.js

+102-88
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,58 @@
22

33
const internalUtil = require('internal/util');
44
const async_wrap = process.binding('async_wrap');
5-
/* Both these arrays are used to communicate between JS and C++ with as little
6-
* overhead as possible.
5+
/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
6+
* Environment::AsyncHooks::fields_[]. Each index tracks the number of active
7+
* hooks for each type.
78
*
8-
* async_hook_fields is a Uint32Array() that communicates the number of each
9-
* type of active hooks of each type and wraps the uin32_t array of
10-
* node::Environment::AsyncHooks::fields_.
11-
*
12-
* async_uid_fields is a Float64Array() that contains the async/trigger ids for
13-
* several operations. These fields are as follows:
14-
* kCurrentAsyncId: The async id of the current execution stack.
15-
* kCurrentTriggerId: The trigger id of the current execution stack.
16-
* kAsyncUidCntr: Counter that tracks the unique ids given to new resources.
17-
* kInitTriggerId: Written to just before creating a new resource, so the
18-
* constructor knows what other resource is responsible for its init().
19-
* Used this way so the trigger id doesn't need to be passed to every
20-
* resource's constructor.
9+
* async_uid_fields is a Float64Array wrapping the double array of
10+
* Environment::AsyncHooks::uid_fields_[]. Each index contains the ids for the
11+
* various asynchronous states of the application. These are:
12+
* kCurrentAsyncId: The async_id assigned to the resource responsible for the
13+
* current execution stack.
14+
* kCurrentTriggerId: The trigger_async_id of the resource responsible for the
15+
* current execution stack.
16+
* kAsyncUidCntr: Incremental counter tracking the next assigned async_id.
17+
* kInitTriggerId: Written immediately before a resource's constructor that
18+
* sets the value of the init()'s triggerAsyncId. The order of retrieving
19+
* the triggerAsyncId value is passing directly to the constructor -> value
20+
* set in kInitTriggerId -> executionAsyncId of the current resource.
2121
*/
2222
const { async_hook_fields, async_uid_fields } = async_wrap;
23-
// Used to change the state of the async id stack.
23+
// Store the pair executionAsyncId and triggerAsyncId in a std::stack on
24+
// Environment::AsyncHooks::ids_stack_ tracks the resource responsible for the
25+
// current execution stack. This is unwound as each resource exits. In the case
26+
// of a fatal exception this stack is emptied after calling each hook's after()
27+
// callback.
2428
const { pushAsyncIds, popAsyncIds } = async_wrap;
25-
// Array of all AsyncHooks that will be iterated whenever an async event fires.
26-
// Using var instead of (preferably const) in order to assign
27-
// tmp_active_hooks_array if a hook is enabled/disabled during hook execution.
28-
var active_hooks_array = [];
29-
// Use a counter to track whether a hook callback is currently being processed.
30-
// Used to make sure active_hooks_array isn't altered in mid execution if
31-
// another hook is added or removed. A counter is used to track nested calls.
32-
var processing_hook = 0;
33-
// Use to temporarily store and updated active_hooks_array if the user enables
34-
// or disables a hook while hooks are being processed.
35-
var tmp_active_hooks_array = null;
36-
// Keep track of the field counts held in tmp_active_hooks_array.
37-
var tmp_async_hook_fields = null;
29+
// For performance reasons, only track Proimses when a hook is enabled.
30+
const { enablePromiseHook, disablePromiseHook } = async_wrap;
31+
// Properties in active_hooks are used to keep track of the set of hooks being
32+
// executed in case another hook is enabled/disabled. The new set of hooks is
33+
// then restored once the active set of hooks is finished executing.
34+
const active_hooks = {
35+
// Array of all AsyncHooks that will be iterated whenever an async event
36+
// fires. Using var instead of (preferably const) in order to assign
37+
// active_hooks.tmp_array if a hook is enabled/disabled during hook
38+
// execution.
39+
array: [],
40+
// Use a counter to track nested calls of async hook callbacks and make sure
41+
// the active_hooks.array isn't altered mid execution.
42+
call_depth: 0,
43+
// Use to temporarily store and updated active_hooks.array if the user
44+
// enables or disables a hook while hooks are being processed. If a hook is
45+
// enabled() or disabled() during hook execution then the current set of
46+
// active hooks is duplicated and set equal to active_hooks.tmp_array. Any
47+
// subsequent changes are on the duplicated array. When all hooks have
48+
// completed executing active_hooks.tmp_array is assigned to
49+
// active_hooks.array.
50+
tmp_array: null,
51+
// Keep track of the field counts held in active_hooks.tmp_array. Because the
52+
// async_hook_fields can't be reassigned, store each uint32 in an array that
53+
// is written back to async_hook_fields when active_hooks.array is restored.
54+
tmp_fields: null
55+
};
56+
3857

3958
// Each constant tracks how many callbacks there are for any given step of
4059
// async execution. These are tracked so if the user didn't include callbacks
@@ -43,6 +62,9 @@ const { kInit, kBefore, kAfter, kDestroy, kTotals, kCurrentAsyncId,
4362
kCurrentTriggerId, kAsyncUidCntr,
4463
kInitTriggerId } = async_wrap.constants;
4564

65+
// Symbols used to store the respective ids on both AsyncResource instances and
66+
// internal resources. They will also be assigned to arbitrary objects passed
67+
// in by the user that take place of internally constructed objects.
4668
const { async_id_symbol, trigger_id_symbol } = async_wrap;
4769

4870
// Used in AsyncHook and AsyncResource.
@@ -54,6 +76,9 @@ const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
5476
const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
5577
const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
5678

79+
// TODO(refack): move to node-config.cc
80+
const abort_regex = /^--abort[_-]on[_-]uncaught[_-]exception$/;
81+
5782
// Setup the callbacks that node::AsyncWrap will call when there are hooks to
5883
// process. They use the same functions as the JS embedder API. These callbacks
5984
// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
@@ -72,7 +97,7 @@ function fatalError(e) {
7297
Error.captureStackTrace(o, fatalError);
7398
process._rawDebug(o.stack);
7499
}
75-
if (process.execArgv.some((e) => /^--abort[_-]on[_-]uncaught[_-]exception$/.test(e))) {
100+
if (process.execArgv.some((e) => abort_regex.test(e))) {
76101
process.abort();
77102
}
78103
process.exit(1);
@@ -122,7 +147,7 @@ class AsyncHook {
122147
hooks_array.push(this);
123148

124149
if (prev_kTotals === 0 && hook_fields[kTotals] > 0)
125-
async_wrap.enablePromiseHook();
150+
enablePromiseHook();
126151

127152
return this;
128153
}
@@ -144,49 +169,49 @@ class AsyncHook {
144169
hooks_array.splice(index, 1);
145170

146171
if (prev_kTotals > 0 && hook_fields[kTotals] === 0)
147-
async_wrap.disablePromiseHook();
172+
disablePromiseHook();
148173

149174
return this;
150175
}
151176
}
152177

153178

154179
function getHookArrays() {
155-
if (processing_hook === 0)
156-
return [active_hooks_array, async_hook_fields];
180+
if (active_hooks.call_depth === 0)
181+
return [active_hooks.array, async_hook_fields];
157182
// If this hook is being enabled while in the middle of processing the array
158183
// of currently active hooks then duplicate the current set of active hooks
159184
// and store this there. This shouldn't fire until the next time hooks are
160185
// processed.
161-
if (tmp_active_hooks_array === null)
186+
if (active_hooks.tmp_array === null)
162187
storeActiveHooks();
163-
return [tmp_active_hooks_array, tmp_async_hook_fields];
188+
return [active_hooks.tmp_array, active_hooks.tmp_fields];
164189
}
165190

166191

167192
function storeActiveHooks() {
168-
tmp_active_hooks_array = active_hooks_array.slice();
193+
active_hooks.tmp_array = active_hooks.array.slice();
169194
// Don't want to make the assumption that kInit to kDestroy are indexes 0 to
170195
// 4. So do this the long way.
171-
tmp_async_hook_fields = [];
172-
tmp_async_hook_fields[kInit] = async_hook_fields[kInit];
173-
tmp_async_hook_fields[kBefore] = async_hook_fields[kBefore];
174-
tmp_async_hook_fields[kAfter] = async_hook_fields[kAfter];
175-
tmp_async_hook_fields[kDestroy] = async_hook_fields[kDestroy];
196+
active_hooks.tmp_fields = [];
197+
active_hooks.tmp_fields[kInit] = async_hook_fields[kInit];
198+
active_hooks.tmp_fields[kBefore] = async_hook_fields[kBefore];
199+
active_hooks.tmp_fields[kAfter] = async_hook_fields[kAfter];
200+
active_hooks.tmp_fields[kDestroy] = async_hook_fields[kDestroy];
176201
}
177202

178203

179204
// Then restore the correct hooks array in case any hooks were added/removed
180205
// during hook callback execution.
181-
function restoreTmpHooks() {
182-
active_hooks_array = tmp_active_hooks_array;
183-
async_hook_fields[kInit] = tmp_async_hook_fields[kInit];
184-
async_hook_fields[kBefore] = tmp_async_hook_fields[kBefore];
185-
async_hook_fields[kAfter] = tmp_async_hook_fields[kAfter];
186-
async_hook_fields[kDestroy] = tmp_async_hook_fields[kDestroy];
187-
188-
tmp_active_hooks_array = null;
189-
tmp_async_hook_fields = null;
206+
function restoreActiveHooks() {
207+
active_hooks.array = active_hooks.tmp_array;
208+
async_hook_fields[kInit] = active_hooks.tmp_fields[kInit];
209+
async_hook_fields[kBefore] = active_hooks.tmp_fields[kBefore];
210+
async_hook_fields[kAfter] = active_hooks.tmp_fields[kAfter];
211+
async_hook_fields[kDestroy] = active_hooks.tmp_fields[kDestroy];
212+
213+
active_hooks.tmp_array = null;
214+
active_hooks.tmp_fields = null;
190215
}
191216

192217

@@ -334,25 +359,30 @@ function emitHookFactory(symbol, name) {
334359
// before this is called.
335360
// eslint-disable-next-line func-style
336361
const fn = function(asyncId) {
337-
processing_hook += 1;
362+
active_hooks.call_depth += 1;
338363
// Use a single try/catch for all hook to avoid setting up one per
339364
// iteration.
340365
try {
341-
for (var i = 0; i < active_hooks_array.length; i++) {
342-
if (typeof active_hooks_array[i][symbol] === 'function') {
343-
active_hooks_array[i][symbol](asyncId);
366+
for (var i = 0; i < active_hooks.array.length; i++) {
367+
if (typeof active_hooks.array[i][symbol] === 'function') {
368+
active_hooks.array[i][symbol](asyncId);
344369
}
345370
}
346371
} catch (e) {
347372
fatalError(e);
348373
} finally {
349-
processing_hook -= 1;
374+
active_hooks.call_depth -= 1;
350375
}
351376

352-
if (processing_hook === 0 && tmp_active_hooks_array !== null) {
353-
restoreTmpHooks();
377+
// Hooks can only be restored if there have been no recursive hook calls.
378+
// Also the active hooks do not need to be restored if enable()/disable()
379+
// weren't called during hook execution, in which case
380+
// active_hooks.tmp_array will be null.
381+
if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
382+
restoreActiveHooks();
354383
}
355384
};
385+
356386
// Set the name property of the anonymous function as it looks good in the
357387
// stack trace.
358388
Object.defineProperty(fn, 'name', {
@@ -379,9 +409,6 @@ function emitBeforeScript(asyncId, triggerAsyncId) {
379409
}
380410

381411

382-
// TODO(trevnorris): Calling emitBefore/emitAfter from native can't adjust the
383-
// kIdStackIndex. But what happens if the user doesn't have both before and
384-
// after callbacks.
385412
function emitAfterScript(asyncId) {
386413
if (async_hook_fields[kAfter] > 0)
387414
emitAfterNative(asyncId);
@@ -399,26 +426,16 @@ function emitDestroyScript(asyncId) {
399426
}
400427

401428

402-
// Emit callbacks for native calls. Since some state can be setup directly from
403-
// C++ there's no need to perform all the work here.
404-
405-
// This should only be called if hooks_array has kInit > 0. There are no global
406-
// values to setup. Though hooks_array will be cloned if C++ needed to call
407-
// init().
408-
// TODO(trevnorris): Perhaps have MakeCallback call a single JS function that
409-
// does the before/callback/after calls to remove two additional calls to JS.
410-
411-
// Force the application to shutdown if one of the callbacks throws. This may
412-
// change in the future depending on whether it can be determined if there's a
413-
// slim chance of the application remaining stable after handling one of these
414-
// exceptions.
429+
// Used by C++ to call all init() callbacks. Because some state can be setup
430+
// from C++ there's no need to perform all the same operations as in
431+
// emitInitScript.
415432
function emitInitNative(asyncId, type, triggerAsyncId, resource) {
416-
processing_hook += 1;
433+
active_hooks.call_depth += 1;
417434
// Use a single try/catch for all hook to avoid setting up one per iteration.
418435
try {
419-
for (var i = 0; i < active_hooks_array.length; i++) {
420-
if (typeof active_hooks_array[i][init_symbol] === 'function') {
421-
active_hooks_array[i][init_symbol](
436+
for (var i = 0; i < active_hooks.array.length; i++) {
437+
if (typeof active_hooks.array[i][init_symbol] === 'function') {
438+
active_hooks.array[i][init_symbol](
422439
asyncId, type, triggerAsyncId,
423440
resource
424441
);
@@ -427,18 +444,15 @@ function emitInitNative(asyncId, type, triggerAsyncId, resource) {
427444
} catch (e) {
428445
fatalError(e);
429446
} finally {
430-
processing_hook -= 1;
447+
active_hooks.call_depth -= 1;
431448
}
432449

433-
// * `tmp_active_hooks_array` is null if no hooks were added/removed while
434-
// the hooks were running. In that case no restoration is needed.
435-
// * In the case where another hook was added/removed while the hooks were
436-
// running and a handle was created causing the `init` hooks to fire again,
437-
// then `restoreTmpHooks` should not be called for the nested `hooks`.
438-
// Otherwise `active_hooks_array` can change during execution of the
439-
// `hooks`.
440-
if (processing_hook === 0 && tmp_active_hooks_array !== null) {
441-
restoreTmpHooks();
450+
// Hooks can only be restored if there have been no recursive hook calls.
451+
// Also the active hooks do not need to be restored if enable()/disable()
452+
// weren't called during hook execution, in which case active_hooks.tmp_array
453+
// will be null.
454+
if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
455+
restoreActiveHooks();
442456
}
443457
}
444458

src/env-inl.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,13 @@ inline void Environment::AsyncHooks::clear_id_stack() {
180180
inline Environment::AsyncHooks::InitScope::InitScope(
181181
Environment* env, double init_trigger_id)
182182
: env_(env),
183-
uid_fields_(env->async_hooks()->uid_fields()) {
184-
env->async_hooks()->push_ids(uid_fields_[AsyncHooks::kCurrentAsyncId],
183+
uid_fields_ref_(env->async_hooks()->uid_fields()) {
184+
env->async_hooks()->push_ids(uid_fields_ref_[AsyncHooks::kCurrentAsyncId],
185185
init_trigger_id);
186186
}
187187

188188
inline Environment::AsyncHooks::InitScope::~InitScope() {
189-
env_->async_hooks()->pop_ids(uid_fields_[AsyncHooks::kCurrentAsyncId]);
189+
env_->async_hooks()->pop_ids(uid_fields_ref_[AsyncHooks::kCurrentAsyncId]);
190190
}
191191

192192
inline Environment::AsyncHooks::ExecScope::ExecScope(

src/env.h

+4-6
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ class Environment {
410410

411411
private:
412412
Environment* env_;
413-
double* uid_fields_;
413+
double* uid_fields_ref_;
414414

415415
DISALLOW_COPY_AND_ASSIGN(InitScope);
416416
};
@@ -443,12 +443,10 @@ class Environment {
443443
v8::Isolate* isolate_;
444444
// Stores the ids of the current execution context stack.
445445
std::stack<struct node_async_ids> ids_stack_;
446-
// Used to communicate state between C++ and JS cheaply. Is placed in an
447-
// Uint32Array() and attached to the async_wrap object.
446+
// Attached to a Uint32Array that tracks the number of active hooks for
447+
// each type.
448448
uint32_t fields_[kFieldsCount];
449-
// Used to communicate ids between C++ and JS cheaply. Placed in a
450-
// Float64Array and attached to the async_wrap object. Using a double only
451-
// gives us 2^53-1 unique ids, but that should be sufficient.
449+
// Attached to a Float64Array that tracks the state of async resources.
452450
double uid_fields_[kUidFieldsCount];
453451

454452
DISALLOW_COPY_AND_ASSIGN(AsyncHooks);

0 commit comments

Comments
 (0)