2
2
3
3
const internalUtil = require ( 'internal/util' ) ;
4
4
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.
7
8
*
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.
21
21
*/
22
22
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.
24
28
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
+
38
57
39
58
// Each constant tracks how many callbacks there are for any given step of
40
59
// async execution. These are tracked so if the user didn't include callbacks
@@ -43,6 +62,9 @@ const { kInit, kBefore, kAfter, kDestroy, kTotals, kCurrentAsyncId,
43
62
kCurrentTriggerId, kAsyncUidCntr,
44
63
kInitTriggerId } = async_wrap . constants ;
45
64
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.
46
68
const { async_id_symbol, trigger_id_symbol } = async_wrap ;
47
69
48
70
// Used in AsyncHook and AsyncResource.
@@ -54,6 +76,9 @@ const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
54
76
const emitAfterNative = emitHookFactory ( after_symbol , 'emitAfterNative' ) ;
55
77
const emitDestroyNative = emitHookFactory ( destroy_symbol , 'emitDestroyNative' ) ;
56
78
79
+ // TODO(refack): move to node-config.cc
80
+ const abort_regex = / ^ - - a b o r t [ _ - ] o n [ _ - ] u n c a u g h t [ _ - ] e x c e p t i o n $ / ;
81
+
57
82
// Setup the callbacks that node::AsyncWrap will call when there are hooks to
58
83
// process. They use the same functions as the JS embedder API. These callbacks
59
84
// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
@@ -72,7 +97,7 @@ function fatalError(e) {
72
97
Error . captureStackTrace ( o , fatalError ) ;
73
98
process . _rawDebug ( o . stack ) ;
74
99
}
75
- if ( process . execArgv . some ( ( e ) => / ^ - - a b o r t [ _ - ] o n [ _ - ] u n c a u g h t [ _ - ] e x c e p t i o n $ / . test ( e ) ) ) {
100
+ if ( process . execArgv . some ( ( e ) => abort_regex . test ( e ) ) ) {
76
101
process . abort ( ) ;
77
102
}
78
103
process . exit ( 1 ) ;
@@ -122,7 +147,7 @@ class AsyncHook {
122
147
hooks_array . push ( this ) ;
123
148
124
149
if ( prev_kTotals === 0 && hook_fields [ kTotals ] > 0 )
125
- async_wrap . enablePromiseHook ( ) ;
150
+ enablePromiseHook ( ) ;
126
151
127
152
return this ;
128
153
}
@@ -144,49 +169,49 @@ class AsyncHook {
144
169
hooks_array . splice ( index , 1 ) ;
145
170
146
171
if ( prev_kTotals > 0 && hook_fields [ kTotals ] === 0 )
147
- async_wrap . disablePromiseHook ( ) ;
172
+ disablePromiseHook ( ) ;
148
173
149
174
return this ;
150
175
}
151
176
}
152
177
153
178
154
179
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 ] ;
157
182
// If this hook is being enabled while in the middle of processing the array
158
183
// of currently active hooks then duplicate the current set of active hooks
159
184
// and store this there. This shouldn't fire until the next time hooks are
160
185
// processed.
161
- if ( tmp_active_hooks_array === null )
186
+ if ( active_hooks . tmp_array === null )
162
187
storeActiveHooks ( ) ;
163
- return [ tmp_active_hooks_array , tmp_async_hook_fields ] ;
188
+ return [ active_hooks . tmp_array , active_hooks . tmp_fields ] ;
164
189
}
165
190
166
191
167
192
function storeActiveHooks ( ) {
168
- tmp_active_hooks_array = active_hooks_array . slice ( ) ;
193
+ active_hooks . tmp_array = active_hooks . array . slice ( ) ;
169
194
// Don't want to make the assumption that kInit to kDestroy are indexes 0 to
170
195
// 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 ] ;
176
201
}
177
202
178
203
179
204
// Then restore the correct hooks array in case any hooks were added/removed
180
205
// 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 ;
190
215
}
191
216
192
217
@@ -334,25 +359,30 @@ function emitHookFactory(symbol, name) {
334
359
// before this is called.
335
360
// eslint-disable-next-line func-style
336
361
const fn = function ( asyncId ) {
337
- processing_hook += 1 ;
362
+ active_hooks . call_depth += 1 ;
338
363
// Use a single try/catch for all hook to avoid setting up one per
339
364
// iteration.
340
365
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 ) ;
344
369
}
345
370
}
346
371
} catch ( e ) {
347
372
fatalError ( e ) ;
348
373
} finally {
349
- processing_hook -= 1 ;
374
+ active_hooks . call_depth -= 1 ;
350
375
}
351
376
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 ( ) ;
354
383
}
355
384
} ;
385
+
356
386
// Set the name property of the anonymous function as it looks good in the
357
387
// stack trace.
358
388
Object . defineProperty ( fn , 'name' , {
@@ -379,9 +409,6 @@ function emitBeforeScript(asyncId, triggerAsyncId) {
379
409
}
380
410
381
411
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.
385
412
function emitAfterScript ( asyncId ) {
386
413
if ( async_hook_fields [ kAfter ] > 0 )
387
414
emitAfterNative ( asyncId ) ;
@@ -399,26 +426,16 @@ function emitDestroyScript(asyncId) {
399
426
}
400
427
401
428
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.
415
432
function emitInitNative ( asyncId , type , triggerAsyncId , resource ) {
416
- processing_hook += 1 ;
433
+ active_hooks . call_depth += 1 ;
417
434
// Use a single try/catch for all hook to avoid setting up one per iteration.
418
435
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 ] (
422
439
asyncId , type , triggerAsyncId ,
423
440
resource
424
441
) ;
@@ -427,18 +444,15 @@ function emitInitNative(asyncId, type, triggerAsyncId, resource) {
427
444
} catch ( e ) {
428
445
fatalError ( e ) ;
429
446
} finally {
430
- processing_hook -= 1 ;
447
+ active_hooks . call_depth -= 1 ;
431
448
}
432
449
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 ( ) ;
442
456
}
443
457
}
444
458
0 commit comments