Skip to content

Commit f29fceb

Browse files
committed
autopurge: also clear timers for eviction, clear()
1 parent 05d673b commit f29fceb

File tree

2 files changed

+119
-37
lines changed

2 files changed

+119
-37
lines changed

src/index.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,7 +1555,12 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
15551555
const starts = new ZeroArray(this.#max)
15561556
this.#ttls = ttls
15571557
this.#starts = starts
1558-
const purgeTimers = this.ttlAutopurge ? new Array<undefined | ReturnType<typeof setTimeout>>(this.#max) : undefined
1558+
const purgeTimers =
1559+
this.ttlAutopurge ?
1560+
new Array<undefined | ReturnType<typeof setTimeout>>(
1561+
this.#max,
1562+
)
1563+
: undefined
15591564
this.#autopurgeTimers = purgeTimers
15601565

15611566
this.#setItemTTL = (index, ttl, start = this.#perf.now()) => {
@@ -2270,6 +2275,10 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
22702275
}
22712276
}
22722277
this.#removeItemSize(head)
2278+
if (this.#autopurgeTimers?.[head]) {
2279+
clearTimeout(this.#autopurgeTimers[head])
2280+
this.#autopurgeTimers[head] = undefined
2281+
}
22732282
// if we aren't about to use the index, then null these out
22742283
if (free) {
22752284
this.#keyList[head] = undefined
@@ -2403,7 +2412,10 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
24032412
// cache and ignore the abort, or if it's still pending on this specific
24042413
// background request, then write it to the cache.
24052414
const vl = this.#valList[index as Index]
2406-
if (vl === p || ignoreAbort && updateCache && vl === undefined) {
2415+
if (
2416+
vl === p ||
2417+
(ignoreAbort && updateCache && vl === undefined)
2418+
) {
24072419
if (v === undefined) {
24082420
if (bf.__staleWhileFetching !== undefined) {
24092421
this.#valList[index as Index] = bf.__staleWhileFetching
@@ -2985,6 +2997,10 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
29852997
if (this.#ttls && this.#starts) {
29862998
this.#ttls.fill(0)
29872999
this.#starts.fill(0)
3000+
for (const t of this.#autopurgeTimers ?? []) {
3001+
if (t !== undefined) clearTimeout(t)
3002+
}
3003+
this.#autopurgeTimers?.fill(undefined)
29883004
}
29893005
if (this.#sizes) {
29903006
this.#sizes.fill(0)

test/purge-stale-resource-management.ts

Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ clock.advance(1)
1515

1616
let timeouts = 0
1717
const origST = global.setTimeout
18-
const newST = function (this: any, ...args: Parameters<typeof setTimeout>) {
18+
const newST = function (
19+
this: any,
20+
...args: Parameters<typeof setTimeout>
21+
) {
1922
++timeouts
2023
return origST.apply(this, args)
2124
}
2225
let clears = 0
2326
const origCT = global.clearTimeout
24-
const newCT = function (this: any, ...args: Parameters<typeof clearTimeout>) {
27+
const newCT = function (
28+
this: any,
29+
...args: Parameters<typeof clearTimeout>
30+
) {
2531
++clears
2632
return origCT.apply(this, args)
2733
}
@@ -31,40 +37,100 @@ global.setTimeout = newST
3137
//@ts-ignore
3238
global.clearTimeout = newCT
3339

34-
3540
const { LRUCache: LRU } = await import('../dist/esm/index.js')
3641

37-
const cache = new LRU<string, number>({ ttl: 10, ttlAutopurge: true })
42+
t.test('a cache that overwrites a hot key many times', async t => {
43+
const cache = new LRU<string, number>({
44+
ttl: 10,
45+
ttlAutopurge: true,
46+
})
47+
48+
const N = 10 //_000
49+
for (let i = 0; i < N; i++) {
50+
cache.set('hot-key', i)
51+
}
52+
t.equal(timeouts, N)
53+
t.equal(clears, N - 1)
3854

55+
timeouts = 0
56+
clears = 0
57+
cache.set('hot-key', 99, { ttl: 0 })
58+
const clearsAfterSetTTL0 = clears
59+
const timeoutsAfterSetTTL0 = timeouts
3960

40-
const N = 10//_000
41-
for (let i = 0; i < N; i++) {
42-
cache.set('hot-key', i)
43-
}
44-
t.equal(timeouts, N)
45-
t.equal(clears, N + 1)
46-
47-
timeouts = 0
48-
clears = 0
49-
cache.set('hot-key', 99, { ttl: 0 })
50-
const clearsAfterSetTTL0 = clears
51-
const timeoutsAfterSetTTL0 = timeouts
52-
53-
t.equal(timeoutsAfterSetTTL0, 0)
54-
t.equal(clearsAfterSetTTL0, 1)
55-
56-
timeouts = 0
57-
clears = 0
58-
cache.set('hot-key', 100)
59-
const clearsAfterSetTTLDef = clears
60-
const timeoutsAfterSetTTLDef = timeouts
61-
t.equal(clearsAfterSetTTLDef, 0)
62-
t.equal(timeoutsAfterSetTTLDef, 1)
63-
64-
timeouts = 0
65-
clears = 0
66-
cache.delete('hot-key')
67-
const clearsAfterDelete = clears
68-
const timeoutsAfterDelete = timeouts
69-
t.equal(clearsAfterDelete, 1)
70-
t.equal(timeoutsAfterDelete, 0)
61+
t.equal(timeoutsAfterSetTTL0, 0)
62+
t.equal(clearsAfterSetTTL0, 1)
63+
64+
timeouts = 0
65+
clears = 0
66+
cache.set('hot-key', 100)
67+
const clearsAfterSetTTLDef = clears
68+
const timeoutsAfterSetTTLDef = timeouts
69+
t.equal(clearsAfterSetTTLDef, 0)
70+
t.equal(timeoutsAfterSetTTLDef, 1)
71+
72+
timeouts = 0
73+
clears = 0
74+
cache.delete('hot-key')
75+
const clearsAfterDelete = clears
76+
const timeoutsAfterDelete = timeouts
77+
t.equal(clearsAfterDelete, 1)
78+
t.equal(timeoutsAfterDelete, 0)
79+
})
80+
81+
t.test('evicting an item means no need for autopurge', async t => {
82+
const cache = new LRU<string, number>({
83+
ttl: 10,
84+
max: 5,
85+
ttlAutopurge: true,
86+
})
87+
88+
timeouts = 0
89+
clears = 0
90+
cache.set('a', 1)
91+
const clearsAfterSet = clears
92+
const timeoutsAfterSet = timeouts
93+
t.equal(clearsAfterSet, 0)
94+
t.equal(timeoutsAfterSet, 1)
95+
96+
timeouts = 0
97+
clears = 0
98+
cache.set('b', 1, { ttl: 0 })
99+
cache.set('c', 1, { ttl: 0 })
100+
cache.set('d', 1, { ttl: 0 })
101+
cache.set('e', 1, { ttl: 0 })
102+
cache.set('f', 1, { ttl: 0 })
103+
104+
const clearsAfterEvict = clears
105+
const timeoutsAfterEvict = timeouts
106+
t.equal(clearsAfterEvict, 1)
107+
t.equal(timeoutsAfterEvict, 0)
108+
})
109+
110+
t.test('clearing list clears autopurge timers', async t => {
111+
const cache = new LRU<string, number>({
112+
ttl: 10,
113+
max: 5,
114+
ttlAutopurge: true,
115+
})
116+
117+
timeouts = 0
118+
clears = 0
119+
cache.set('a', 1)
120+
cache.set('b', 1)
121+
cache.set('c', 1)
122+
cache.set('d', 1)
123+
const clearsAfterSet = clears
124+
const timeoutsAfterSet = timeouts
125+
t.equal(clearsAfterSet, 0)
126+
t.equal(timeoutsAfterSet, 4)
127+
128+
timeouts = 0
129+
clears = 0
130+
cache.clear()
131+
132+
const clearsAfterClear = clears
133+
const timeoutsAfterClear = timeouts
134+
t.equal(clearsAfterClear, 4)
135+
t.equal(timeoutsAfterClear, 0)
136+
})

0 commit comments

Comments
 (0)