Skip to content

Commit 4708153

Browse files
committed
add backgroundFetchSize option
1 parent 9bed6db commit 4708153

5 files changed

Lines changed: 91 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# cringe lorg
22

3+
## 11.5
4+
5+
- Add `backgroundFetchSize` option, defaulting to 1, to set an
6+
effective size for provisional background fetch objects while
7+
in flight, if they do not shadow an existing stale entry.
8+
39
## 11.4
410

511
- Add `cache` property to status objects, in order to

src/index.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,21 @@ export namespace LRUCache {
929929
*/
930930
maxSize?: Size
931931

932+
/**
933+
* The effective size for background fetch promises.
934+
*
935+
* This has no effect unless `maxSize` and `sizeCalculation` are used,
936+
* and a {@link LRUCache.OptionsBase.fetchMethod} is provided to
937+
* support {@link LRUCache#fetch}.
938+
*
939+
* If a stale value is present in the cache, then the effective size of
940+
* the background fetch is the size of the stale item it will eventually
941+
* replace. If not, then this value is used as its effective size.
942+
*
943+
* @default 1
944+
*/
945+
backgroundFetchSize?: number
946+
932947
/**
933948
* The maximum allowed size for any single item in the cache.
934949
*
@@ -1274,6 +1289,9 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
12741289
*/
12751290
ignoreFetchAbort: boolean
12761291

1292+
/** {@link LRUCache.OptionsBase.backgroundFetchSize} */
1293+
backgroundFetchSize: number
1294+
12771295
// computed properties
12781296
#size: LRUCache.Count
12791297
#calculatedSize: LRUCache.Size
@@ -1428,9 +1446,12 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
14281446
allowStaleOnFetchRejection,
14291447
allowStaleOnFetchAbort,
14301448
ignoreFetchAbort,
1449+
backgroundFetchSize = 1,
14311450
perf,
14321451
} = options
14331452

1453+
this.backgroundFetchSize = backgroundFetchSize
1454+
14341455
if (perf !== undefined) {
14351456
if (typeof perf?.now !== 'function') {
14361457
throw new TypeError(
@@ -1707,12 +1728,15 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
17071728
sizes[index] = 0
17081729
}
17091730
this.#requireSize = (k, v, size, sizeCalculation) => {
1710-
// provisionally accept background fetches.
1711-
// actual value size will be checked when they return.
1712-
if (this.#isBackgroundFetch(v)) {
1713-
return 0
1714-
}
17151731
if (!isPosInt(size)) {
1732+
// provisionally accept background fetches.
1733+
// actual value size will be checked when they return.
1734+
if (this.#isBackgroundFetch(v)) {
1735+
// NB: this cannot occur if v.__staleWhileFetching is set,
1736+
// because in that case, it would take on the size of the
1737+
// existing entry that it temporarily replaces.
1738+
return this.backgroundFetchSize
1739+
}
17161740
if (sizeCalculation) {
17171741
if (typeof sizeCalculation !== 'function') {
17181742
throw new TypeError('sizeCalculation must be a function')
@@ -2161,14 +2185,14 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
21612185
status,
21622186
} = setOptions
21632187

2188+
const isBF = this.#isBackgroundFetch(v)
21642189
if (v === undefined) {
21652190
if (status) status.set = 'deleted'
21662191
this.delete(k)
21672192
return this
21682193
}
21692194
let { noUpdateTTL = this.noUpdateTTL } = setOptions
21702195

2171-
const isBF = this.#isBackgroundFetch(v)
21722196
if (status && !isBF) status.value = v
21732197

21742198
const size = this.#requireSize(
@@ -2598,6 +2622,10 @@ export class LRUCache<K extends {}, V extends {}, FC = unknown> {
25982622
this.#set(k, bf, { ...fetchOpts.options, status: undefined })
25992623
index = this.#keyMap.get(k)
26002624
} else {
2625+
// do not call #set, because we do not want to adjust its place
2626+
// in the lru queue, as it has not yet been "used". Also, we don't
2627+
// need to worry about evicting for size, because a background fetch
2628+
// over a stale value is treated as the same size as its stale value.
26012629
this.#valList[index] = bf
26022630
}
26032631
return bf

tap-snapshots/test/tracing.ts.test.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15369,7 +15369,7 @@ Map {
1536915369
"op": "fetch",
1537015370
"result": 4,
1537115371
"set": "add",
15372-
"totalCalculatedSize": 4,
15372+
"totalCalculatedSize": 6,
1537315373
"trace": true,
1537415374
"value": 4,
1537515375
} => Array [
@@ -15398,7 +15398,7 @@ Map {
1539815398
"op": "fetch",
1539915399
"result": 5,
1540015400
"set": "add",
15401-
"totalCalculatedSize": 9,
15401+
"totalCalculatedSize": 10,
1540215402
"trace": true,
1540315403
"value": 5,
1540415404
} => Array [

test/background-fetch-size.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import t from 'tap'
2+
import { LRUCache } from '../dist/esm/node/index.js'
3+
4+
const clock = t.clock
5+
clock.advance(1)
6+
clock.enter()
7+
8+
t.test('background fetch size tests', async t => {
9+
const res: Record<number, (n: number) => void> = {}
10+
const c = new LRUCache<number, number>({
11+
maxSize: 10,
12+
sizeCalculation: () => 5,
13+
allowStale: true,
14+
ttl: 10,
15+
// never returns on purpose
16+
fetchMethod: k =>
17+
new Promise<number>(r => {
18+
res[k] = r
19+
}),
20+
})
21+
t.equal(c.calculatedSize, 0)
22+
const p1 = c.fetch(1).catch(er => er)
23+
t.equal(c.calculatedSize, 1)
24+
c.set(1, 1)
25+
t.match(await p1, new Error('replaced'))
26+
t.equal(c.calculatedSize, 5)
27+
clock.advance(100)
28+
t.equal(c.getRemainingTTL(1), -90)
29+
// verify correct behavior of a fetch that shadows a stale value
30+
const p = c.fetch(1)
31+
t.equal(c.calculatedSize, 5)
32+
const p2 = c.fetch(2)
33+
t.equal(c.calculatedSize, 6)
34+
const p3 = c.fetch(3)
35+
t.equal(c.calculatedSize, 7)
36+
res[1]?.(1)
37+
await p
38+
// no change, that one had a stale value
39+
t.equal(c.calculatedSize, 7)
40+
res[2]?.(2)
41+
await p2
42+
t.equal(c.calculatedSize, 10)
43+
await t.rejects(p3, new Error('evicted'))
44+
})

test/onInsert.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,12 @@ t.test('no onInsert for background fetch promises', async t => {
8484
ttl: 10,
8585
})
8686
c.set('y', 'Y')
87-
t.strictSame(inserted, [['Y', 'y' ,'add']])
87+
t.strictSame(inserted, [['Y', 'y', 'add']])
8888
c.set('z', 'Z')
89-
t.strictSame(inserted, [['Y', 'y' ,'add'], ['Z', 'z', 'add']])
89+
t.strictSame(inserted, [
90+
['Y', 'y', 'add'],
91+
['Z', 'z', 'add'],
92+
])
9093
const x = await c.fetch('x')
9194
const y = await c.fetch('y')
9295
await new Promise(res => setTimeout(res, 50))

0 commit comments

Comments
 (0)