Skip to content

Commit 00931d1

Browse files
committed
timers: do not retain a reference to the async store after firing
Signed-off-by: Matteo Collina <[email protected]>
1 parent 786464d commit 00931d1

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

lib/async_hooks.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,7 @@ module.exports = {
381381
asyncWrapProviders: ObjectFreeze({ __proto__: null, ...asyncWrap.Providers }),
382382
// Embedder API
383383
AsyncResource,
384+
getActiveStores () {
385+
return [...storageList];
386+
}
384387
};

lib/internal/timers.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ let debug = require('internal/util/debuglog').debuglog('timer', (fn) => {
121121
debug = fn;
122122
});
123123

124+
let asyncHooks = null;
125+
124126
// *Must* match Environment::ImmediateInfo::Fields in src/env.h.
125127
const kCount = 0;
126128
const kRefCount = 1;
@@ -164,6 +166,7 @@ function initAsyncResource(resource, type) {
164166
if (initHooksExist())
165167
emitInit(asyncId, type, triggerAsyncId, resource);
166168
}
169+
167170
class Timeout {
168171
// Timer constructor function.
169172
// The entire prototype is defined in lib/timers.js
@@ -429,6 +432,20 @@ function setPosition(node, pos) {
429432
node.priorityQueuePosition = pos;
430433
}
431434

435+
function removeAllStores (timer) {
436+
// TODO(mcollina): move the list of stores to private
437+
asyncHooks ??= require('async_hooks');
438+
439+
// TODO(mcollina): this does a copy for safety, we
440+
// should be fast and unsafe.
441+
const activeStores = asyncHooks.getActiveStores();
442+
443+
// Use for loop for speed
444+
for (let i = 0; i < activeStores.length; i++) {
445+
timer[activeStores[i].kResourceStore] = undefined;
446+
}
447+
}
448+
432449
function getTimerCallbacks(runNextTicks) {
433450
// If an uncaught exception was thrown during execution of immediateQueue,
434451
// this queue will store all remaining Immediates that need to run upon
@@ -594,6 +611,9 @@ function getTimerCallbacks(runNextTicks) {
594611
if (timer[kRefed])
595612
timeoutInfo[0]--;
596613

614+
removeAllStores(timer);
615+
timer._onTimeout = undefined;
616+
597617
if (destroyHooksExist())
598618
emitDestroy(asyncId);
599619
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
const assert = require('assert');
6+
7+
const asyncLocalStorage = new AsyncLocalStorage();
8+
asyncLocalStorage.run({}, common.mustCall(() => {
9+
const timeout = setTimeout(common.mustCall(() => {
10+
setImmediate(common.mustCall(() => {
11+
assert.strictEqual(timeout[asyncLocalStorage.kResourceStore], undefined);
12+
assert.strictEqual(timeout._onTimeout, undefined);
13+
}))
14+
}));
15+
}));

0 commit comments

Comments
 (0)