22
33const internalUtil = require ( 'internal/util' ) ;
44const 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 */
2222const { 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.
2428const { 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.
4668const { 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');
5476const emitAfterNative = emitHookFactory ( after_symbol , 'emitAfterNative' ) ;
5577const emitDestroyNative = emitHookFactory ( destroy_symbol , 'emitDestroyNative' ) ;
5678
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+
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 ) => / ^ - - 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 ) ) ) {
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
154179function 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
167192function 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.
385412function 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.
415432function 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
0 commit comments