11'use strict' ;
22
33const async_wrap = process . binding ( 'async_wrap' ) ;
4- /* Both these arrays are used to communicate between JS and C++ with as little
5- * overhead as possible.
4+ /* async_hook_fields is a Uint32Array wrapping the uint32_t array of
5+ * Environment::AsyncHooks::fields_[]. Each index tracks the number of active
6+ * hooks for each type.
67 *
7- * async_hook_fields is a Uint32Array() that communicates the number of each
8- * type of active hooks of each type and wraps the uin32_t array of
9- * node::Environment::AsyncHooks::fields_.
10- *
11- * async_uid_fields is a Float64Array() that contains the async/trigger ids for
12- * several operations. These fields are as follows:
13- * kCurrentAsyncId: The async id of the current execution stack.
14- * kCurrentTriggerId: The trigger id of the current execution stack.
15- * kAsyncUidCntr: Counter that tracks the unique ids given to new resources.
16- * kInitTriggerId: Written to just before creating a new resource, so the
17- * constructor knows what other resource is responsible for its init().
18- * Used this way so the trigger id doesn't need to be passed to every
19- * resource's constructor.
8+ * async_uid_fields is a Float64Array wrapping the double array of
9+ * Environment::AsyncHooks::uid_fields_[]. Each index contains the ids for the
10+ * various asynchronous states of the application. These are:
11+ * kCurrentAsyncId: The async_id assigned to the resource responsible for the
12+ * current execution stack.
13+ * kCurrentTriggerId: The trigger_async_id of the resource responsible for the
14+ * current execution stack.
15+ * kAsyncUidCntr: Incremental counter tracking the next assigned async_id.
16+ * kInitTriggerId: Written immediately before a resource's constructor that
17+ * sets the value of the init()'s triggerAsyncId. The order of retrieving
18+ * the triggerAsyncId value is passing directly to the constructor -> value
19+ * set in kInitTriggerId -> executionAsyncId of the current resource.
2020 */
2121const { async_hook_fields, async_uid_fields } = async_wrap ;
22- // Used to change the state of the async id stack.
22+ // Store the pair executionAsyncId and triggerAsyncId in a std::stack on
23+ // Environment::AsyncHooks::ids_stack_ tracks the resource responsible for the
24+ // current execution stack. This is unwound as each resource exits. In the case
25+ // of a fatal exception this stack is emptied after calling each hook's after()
26+ // callback.
2327const { pushAsyncIds, popAsyncIds } = async_wrap ;
24- // Array of all AsyncHooks that will be iterated whenever an async event fires.
25- // Using var instead of (preferably const) in order to assign
26- // tmp_active_hooks_array if a hook is enabled/disabled during hook execution.
27- var active_hooks_array = [ ] ;
28- // Use a counter to track whether a hook callback is currently being processed.
29- // Used to make sure active_hooks_array isn't altered in mid execution if
30- // another hook is added or removed. A counter is used to track nested calls.
31- var processing_hook = 0 ;
32- // Use to temporarily store and updated active_hooks_array if the user enables
33- // or disables a hook while hooks are being processed.
34- var tmp_active_hooks_array = null ;
35- // Keep track of the field counts held in tmp_active_hooks_array.
36- var tmp_async_hook_fields = null ;
28+ // For performance reasons, only track Proimses when a hook is enabled.
29+ const { enablePromiseHook, disablePromiseHook } = async_wrap ;
30+ // Properties in active_hooks are used to keep track of the set of hooks being
31+ // executed in case another hook is enabled/disabled. The new set of hooks is
32+ // then restored once the active set of hooks is finished executing.
33+ const active_hooks = {
34+ // Array of all AsyncHooks that will be iterated whenever an async event
35+ // fires. Using var instead of (preferably const) in order to assign
36+ // active_hooks.tmp_array if a hook is enabled/disabled during hook
37+ // execution.
38+ array : [ ] ,
39+ // Use a counter to track nested calls of async hook callbacks and make sure
40+ // the active_hooks.array isn't altered mid execution.
41+ call_depth : 0 ,
42+ // Use to temporarily store and updated active_hooks.array if the user
43+ // enables or disables a hook while hooks are being processed. If a hook is
44+ // enabled() or disabled() during hook execution then the current set of
45+ // active hooks is duplicated and set equal to active_hooks.tmp_array. Any
46+ // subsequent changes are on the duplicated array. When all hooks have
47+ // completed executing active_hooks.tmp_array is assigned to
48+ // active_hooks.array.
49+ tmp_array : null ,
50+ // Keep track of the field counts held in active_hooks.tmp_array. Because the
51+ // async_hook_fields can't be reassigned, store each uint32 in an array that
52+ // is written back to async_hook_fields when active_hooks.array is restored.
53+ tmp_fields : null
54+ } ;
55+
3756
3857// Each constant tracks how many callbacks there are for any given step of
3958// async execution. These are tracked so if the user didn't include callbacks
@@ -42,6 +61,9 @@ const { kInit, kBefore, kAfter, kDestroy, kTotals, kCurrentAsyncId,
4261 kCurrentTriggerId, kAsyncUidCntr,
4362 kInitTriggerId } = async_wrap . constants ;
4463
64+ // Symbols used to store the respective ids on both AsyncResource instances and
65+ // internal resources. They will also be assigned to arbitrary objects passed
66+ // in by the user that take place of internally constructed objects.
4567const { async_id_symbol, trigger_id_symbol } = async_wrap ;
4668
4769// Used in AsyncHook and AsyncResource.
@@ -53,6 +75,9 @@ const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
5375const emitAfterNative = emitHookFactory ( after_symbol , 'emitAfterNative' ) ;
5476const emitDestroyNative = emitHookFactory ( destroy_symbol , 'emitDestroyNative' ) ;
5577
78+ // TODO(refack): move to node-config.cc
79+ 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 $ / ;
80+
5681// Setup the callbacks that node::AsyncWrap will call when there are hooks to
5782// process. They use the same functions as the JS embedder API. These callbacks
5883// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
@@ -71,7 +96,7 @@ function fatalError(e) {
7196 Error . captureStackTrace ( o , fatalError ) ;
7297 process . _rawDebug ( o . stack ) ;
7398 }
74- 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 ) ) ) {
99+ if ( process . execArgv . some ( ( e ) => abort_regex . test ( e ) ) ) {
75100 process . abort ( ) ;
76101 }
77102 process . exit ( 1 ) ;
@@ -121,7 +146,7 @@ class AsyncHook {
121146 hooks_array . push ( this ) ;
122147
123148 if ( prev_kTotals === 0 && hook_fields [ kTotals ] > 0 )
124- async_wrap . enablePromiseHook ( ) ;
149+ enablePromiseHook ( ) ;
125150
126151 return this ;
127152 }
@@ -143,49 +168,49 @@ class AsyncHook {
143168 hooks_array . splice ( index , 1 ) ;
144169
145170 if ( prev_kTotals > 0 && hook_fields [ kTotals ] === 0 )
146- async_wrap . disablePromiseHook ( ) ;
171+ disablePromiseHook ( ) ;
147172
148173 return this ;
149174 }
150175}
151176
152177
153178function getHookArrays ( ) {
154- if ( processing_hook === 0 )
155- return [ active_hooks_array , async_hook_fields ] ;
179+ if ( active_hooks . call_depth === 0 )
180+ return [ active_hooks . array , async_hook_fields ] ;
156181 // If this hook is being enabled while in the middle of processing the array
157182 // of currently active hooks then duplicate the current set of active hooks
158183 // and store this there. This shouldn't fire until the next time hooks are
159184 // processed.
160- if ( tmp_active_hooks_array === null )
185+ if ( active_hooks . tmp_array === null )
161186 storeActiveHooks ( ) ;
162- return [ tmp_active_hooks_array , tmp_async_hook_fields ] ;
187+ return [ active_hooks . tmp_array , active_hooks . tmp_fields ] ;
163188}
164189
165190
166191function storeActiveHooks ( ) {
167- tmp_active_hooks_array = active_hooks_array . slice ( ) ;
192+ active_hooks . tmp_array = active_hooks . array . slice ( ) ;
168193 // Don't want to make the assumption that kInit to kDestroy are indexes 0 to
169194 // 4. So do this the long way.
170- tmp_async_hook_fields = [ ] ;
171- tmp_async_hook_fields [ kInit ] = async_hook_fields [ kInit ] ;
172- tmp_async_hook_fields [ kBefore ] = async_hook_fields [ kBefore ] ;
173- tmp_async_hook_fields [ kAfter ] = async_hook_fields [ kAfter ] ;
174- tmp_async_hook_fields [ kDestroy ] = async_hook_fields [ kDestroy ] ;
195+ active_hooks . tmp_fields = [ ] ;
196+ active_hooks . tmp_fields [ kInit ] = async_hook_fields [ kInit ] ;
197+ active_hooks . tmp_fields [ kBefore ] = async_hook_fields [ kBefore ] ;
198+ active_hooks . tmp_fields [ kAfter ] = async_hook_fields [ kAfter ] ;
199+ active_hooks . tmp_fields [ kDestroy ] = async_hook_fields [ kDestroy ] ;
175200}
176201
177202
178203// Then restore the correct hooks array in case any hooks were added/removed
179204// during hook callback execution.
180- function restoreTmpHooks ( ) {
181- active_hooks_array = tmp_active_hooks_array ;
182- async_hook_fields [ kInit ] = tmp_async_hook_fields [ kInit ] ;
183- async_hook_fields [ kBefore ] = tmp_async_hook_fields [ kBefore ] ;
184- async_hook_fields [ kAfter ] = tmp_async_hook_fields [ kAfter ] ;
185- async_hook_fields [ kDestroy ] = tmp_async_hook_fields [ kDestroy ] ;
186-
187- tmp_active_hooks_array = null ;
188- tmp_async_hook_fields = null ;
205+ function restoreActiveHooks ( ) {
206+ active_hooks . array = active_hooks . tmp_array ;
207+ async_hook_fields [ kInit ] = active_hooks . tmp_fields [ kInit ] ;
208+ async_hook_fields [ kBefore ] = active_hooks . tmp_fields [ kBefore ] ;
209+ async_hook_fields [ kAfter ] = active_hooks . tmp_fields [ kAfter ] ;
210+ async_hook_fields [ kDestroy ] = active_hooks . tmp_fields [ kDestroy ] ;
211+
212+ active_hooks . tmp_array = null ;
213+ active_hooks . tmp_fields = null ;
189214}
190215
191216
@@ -322,25 +347,30 @@ function emitHookFactory(symbol, name) {
322347 // before this is called.
323348 // eslint-disable-next-line func-style
324349 const fn = function ( asyncId ) {
325- processing_hook += 1 ;
350+ active_hooks . call_depth += 1 ;
326351 // Use a single try/catch for all hook to avoid setting up one per
327352 // iteration.
328353 try {
329- for ( var i = 0 ; i < active_hooks_array . length ; i ++ ) {
330- if ( typeof active_hooks_array [ i ] [ symbol ] === 'function' ) {
331- active_hooks_array [ i ] [ symbol ] ( asyncId ) ;
354+ for ( var i = 0 ; i < active_hooks . array . length ; i ++ ) {
355+ if ( typeof active_hooks . array [ i ] [ symbol ] === 'function' ) {
356+ active_hooks . array [ i ] [ symbol ] ( asyncId ) ;
332357 }
333358 }
334359 } catch ( e ) {
335360 fatalError ( e ) ;
336361 } finally {
337- processing_hook -= 1 ;
362+ active_hooks . call_depth -= 1 ;
338363 }
339364
340- if ( processing_hook === 0 && tmp_active_hooks_array !== null ) {
341- restoreTmpHooks ( ) ;
365+ // Hooks can only be restored if there have been no recursive hook calls.
366+ // Also the active hooks do not need to be restored if enable()/disable()
367+ // weren't called during hook execution, in which case
368+ // active_hooks.tmp_array will be null.
369+ if ( active_hooks . call_depth === 0 && active_hooks . tmp_array !== null ) {
370+ restoreActiveHooks ( ) ;
342371 }
343372 } ;
373+
344374 // Set the name property of the anonymous function as it looks good in the
345375 // stack trace.
346376 Object . defineProperty ( fn , 'name' , {
@@ -367,9 +397,6 @@ function emitBeforeScript(asyncId, triggerAsyncId) {
367397}
368398
369399
370- // TODO(trevnorris): Calling emitBefore/emitAfter from native can't adjust the
371- // kIdStackIndex. But what happens if the user doesn't have both before and
372- // after callbacks.
373400function emitAfterScript ( asyncId ) {
374401 if ( async_hook_fields [ kAfter ] > 0 )
375402 emitAfterNative ( asyncId ) ;
@@ -387,26 +414,16 @@ function emitDestroyScript(asyncId) {
387414}
388415
389416
390- // Emit callbacks for native calls. Since some state can be setup directly from
391- // C++ there's no need to perform all the work here.
392-
393- // This should only be called if hooks_array has kInit > 0. There are no global
394- // values to setup. Though hooks_array will be cloned if C++ needed to call
395- // init().
396- // TODO(trevnorris): Perhaps have MakeCallback call a single JS function that
397- // does the before/callback/after calls to remove two additional calls to JS.
398-
399- // Force the application to shutdown if one of the callbacks throws. This may
400- // change in the future depending on whether it can be determined if there's a
401- // slim chance of the application remaining stable after handling one of these
402- // exceptions.
417+ // Used by C++ to call all init() callbacks. Because some state can be setup
418+ // from C++ there's no need to perform all the same operations as in
419+ // emitInitScript.
403420function emitInitNative ( asyncId , type , triggerAsyncId , resource ) {
404- processing_hook += 1 ;
421+ active_hooks . call_depth += 1 ;
405422 // Use a single try/catch for all hook to avoid setting up one per iteration.
406423 try {
407- for ( var i = 0 ; i < active_hooks_array . length ; i ++ ) {
408- if ( typeof active_hooks_array [ i ] [ init_symbol ] === 'function' ) {
409- active_hooks_array [ i ] [ init_symbol ] (
424+ for ( var i = 0 ; i < active_hooks . array . length ; i ++ ) {
425+ if ( typeof active_hooks . array [ i ] [ init_symbol ] === 'function' ) {
426+ active_hooks . array [ i ] [ init_symbol ] (
410427 asyncId , type , triggerAsyncId ,
411428 resource
412429 ) ;
@@ -415,18 +432,15 @@ function emitInitNative(asyncId, type, triggerAsyncId, resource) {
415432 } catch ( e ) {
416433 fatalError ( e ) ;
417434 } finally {
418- processing_hook -= 1 ;
435+ active_hooks . call_depth -= 1 ;
419436 }
420437
421- // * `tmp_active_hooks_array` is null if no hooks were added/removed while
422- // the hooks were running. In that case no restoration is needed.
423- // * In the case where another hook was added/removed while the hooks were
424- // running and a handle was created causing the `init` hooks to fire again,
425- // then `restoreTmpHooks` should not be called for the nested `hooks`.
426- // Otherwise `active_hooks_array` can change during execution of the
427- // `hooks`.
428- if ( processing_hook === 0 && tmp_active_hooks_array !== null ) {
429- restoreTmpHooks ( ) ;
438+ // Hooks can only be restored if there have been no recursive hook calls.
439+ // Also the active hooks do not need to be restored if enable()/disable()
440+ // weren't called during hook execution, in which case active_hooks.tmp_array
441+ // will be null.
442+ if ( active_hooks . call_depth === 0 && active_hooks . tmp_array !== null ) {
443+ restoreActiveHooks ( ) ;
430444 }
431445}
432446
0 commit comments