Skip to content

Commit 0c69ec1

Browse files
AndreasMadsenaddaleax
authored andcommitted
async_hooks: fix nested hooks mutation
In some cases restoreTmpHooks is called too early, this causes active_hooks_array to change during execution of the init hooks. PR-URL: #14143 Ref: #14054 (comment) Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 3211eff commit 0c69ec1

File tree

3 files changed

+65
-12
lines changed

3 files changed

+65
-12
lines changed

lib/async_hooks.js

+20-12
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ const { pushAsyncIds, popAsyncIds } = async_wrap;
2626
// Using var instead of (preferably const) in order to assign
2727
// tmp_active_hooks_array if a hook is enabled/disabled during hook execution.
2828
var active_hooks_array = [];
29-
// Track whether a hook callback is currently being processed. Used to make
30-
// sure active_hooks_array isn't altered in mid execution if another hook is
31-
// added or removed.
32-
var processing_hook = false;
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;
3333
// Use to temporarily store and updated active_hooks_array if the user enables
3434
// or disables a hook while hooks are being processed.
3535
var tmp_active_hooks_array = null;
@@ -151,7 +151,7 @@ class AsyncHook {
151151

152152

153153
function getHookArrays() {
154-
if (!processing_hook)
154+
if (processing_hook === 0)
155155
return [active_hooks_array, async_hook_fields];
156156
// If this hook is being enabled while in the middle of processing the array
157157
// of currently active hooks then duplicate the current set of active hooks
@@ -342,7 +342,7 @@ function emitHookFactory(symbol, name) {
342342
// before this is called.
343343
// eslint-disable-next-line func-style
344344
const fn = function(asyncId) {
345-
processing_hook = true;
345+
processing_hook += 1;
346346
// Use a single try/catch for all hook to avoid setting up one per
347347
// iteration.
348348
try {
@@ -353,10 +353,11 @@ function emitHookFactory(symbol, name) {
353353
}
354354
} catch (e) {
355355
fatalError(e);
356+
} finally {
357+
processing_hook -= 1;
356358
}
357-
processing_hook = false;
358359

359-
if (tmp_active_hooks_array !== null) {
360+
if (processing_hook === 0 && tmp_active_hooks_array !== null) {
360361
restoreTmpHooks();
361362
}
362363
};
@@ -422,7 +423,7 @@ function emitDestroyS(asyncId) {
422423
// slim chance of the application remaining stable after handling one of these
423424
// exceptions.
424425
function init(asyncId, type, triggerAsyncId, resource) {
425-
processing_hook = true;
426+
processing_hook += 1;
426427
// Use a single try/catch for all hook to avoid setting up one per iteration.
427428
try {
428429
for (var i = 0; i < active_hooks_array.length; i++) {
@@ -435,11 +436,18 @@ function init(asyncId, type, triggerAsyncId, resource) {
435436
}
436437
} catch (e) {
437438
fatalError(e);
439+
} finally {
440+
processing_hook -= 1;
438441
}
439-
processing_hook = false;
440442

441-
// Isn't null if hooks were added/removed while the hooks were running.
442-
if (tmp_active_hooks_array !== null) {
443+
// * `tmp_active_hooks_array` is null if no hooks were added/removed while
444+
// the hooks were running. In that case no restoration is needed.
445+
// * In the case where another hook was added/removed while the hooks were
446+
// running and a handle was created causing the `init` hooks to fire again,
447+
// then `restoreTmpHooks` should not be called for the nested `hooks`.
448+
// Otherwise `active_hooks_array` can change during execution of the
449+
// `hooks`.
450+
if (processing_hook === 0 && tmp_active_hooks_array !== null) {
443451
restoreTmpHooks();
444452
}
445453
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const async_hooks = require('async_hooks');
5+
const fs = require('fs');
6+
7+
let nestedCall = false;
8+
9+
async_hooks.createHook({
10+
init: common.mustCall(function(id, type) {
11+
nestedHook.disable();
12+
if (!nestedCall) {
13+
nestedCall = true;
14+
fs.access(__filename, common.mustCall());
15+
}
16+
}, 2)
17+
}).enable();
18+
19+
const nestedHook = async_hooks.createHook({
20+
init: common.mustCall(2)
21+
}).enable();
22+
23+
fs.access(__filename, common.mustCall());
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const async_hooks = require('async_hooks');
5+
const fs = require('fs');
6+
7+
const nestedHook = async_hooks.createHook({
8+
init: common.mustNotCall()
9+
});
10+
let nestedCall = false;
11+
12+
async_hooks.createHook({
13+
init: common.mustCall(function(id, type) {
14+
nestedHook.enable();
15+
if (!nestedCall) {
16+
nestedCall = true;
17+
fs.access(__filename, common.mustCall());
18+
}
19+
}, 2)
20+
}).enable();
21+
22+
fs.access(__filename, common.mustCall());

0 commit comments

Comments
 (0)