Skip to content

Commit 006ce40

Browse files
committed
fix: ensure size calculation returns positive numbers and make in-memory cache failures not fatal
1 parent 05e328c commit 006ce40

File tree

1 file changed

+65
-24
lines changed

1 file changed

+65
-24
lines changed

src/run/storage/request-scoped-in-memory-cache.cts

+65-24
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,71 @@ export function setInMemoryCacheMaxSizeFromNextConfig(size: unknown) {
2525
}
2626
}
2727

28-
const estimateBlobSize = (valueToStore: BlobType | null | Promise<unknown>): number => {
28+
type PositiveNumber = number & { __positive: true }
29+
const isPositiveNumber = (value: unknown): value is PositiveNumber => {
30+
return typeof value === 'number' && value > 0
31+
}
32+
33+
const BASE_BLOB_SIZE = 25 as PositiveNumber
34+
35+
const estimateBlobKnownTypeSize = (
36+
valueToStore: BlobType | null | Promise<unknown>,
37+
): number | undefined => {
2938
// very approximate size calculation to avoid expensive exact size calculation
3039
// inspired by https://github.com/vercel/next.js/blob/ed10f7ed0246fcc763194197eb9beebcbd063162/packages/next/src/server/lib/incremental-cache/file-system-cache.ts#L60-L79
3140
if (valueToStore === null || isPromise(valueToStore) || isTagManifest(valueToStore)) {
32-
return 25
41+
return BASE_BLOB_SIZE
3342
}
3443
if (isHtmlBlob(valueToStore)) {
35-
return valueToStore.html.length
44+
return BASE_BLOB_SIZE + valueToStore.html.length
45+
}
46+
47+
if (valueToStore.value?.kind === 'FETCH') {
48+
return BASE_BLOB_SIZE + valueToStore.value.data.body.length
49+
}
50+
if (valueToStore.value?.kind === 'APP_PAGE') {
51+
return (
52+
BASE_BLOB_SIZE + valueToStore.value.html.length + (valueToStore.value.rscData?.length ?? 0)
53+
)
54+
}
55+
if (valueToStore.value?.kind === 'PAGE' || valueToStore.value?.kind === 'PAGES') {
56+
return (
57+
BASE_BLOB_SIZE +
58+
valueToStore.value.html.length +
59+
JSON.stringify(valueToStore.value.pageData).length
60+
)
3661
}
37-
let knownKindFailed = false
62+
if (valueToStore.value?.kind === 'ROUTE' || valueToStore.value?.kind === 'APP_ROUTE') {
63+
return BASE_BLOB_SIZE + valueToStore.value.body.length
64+
}
65+
}
66+
67+
const estimateBlobSize = (valueToStore: BlobType | null | Promise<unknown>): PositiveNumber => {
68+
let knownTypeFailed = false
69+
let estimatedKnownTypeSize: number | undefined
70+
let estimateBlobKnownTypeSizeError: unknown
3871
try {
39-
if (valueToStore.value?.kind === 'FETCH') {
40-
return valueToStore.value.data.body.length
41-
}
42-
if (valueToStore.value?.kind === 'APP_PAGE') {
43-
return valueToStore.value.html.length + (valueToStore.value.rscData?.length ?? 0)
44-
}
45-
if (valueToStore.value?.kind === 'PAGE' || valueToStore.value?.kind === 'PAGES') {
46-
return valueToStore.value.html.length + JSON.stringify(valueToStore.value.pageData).length
47-
}
48-
if (valueToStore.value?.kind === 'ROUTE' || valueToStore.value?.kind === 'APP_ROUTE') {
49-
return valueToStore.value.body.length
72+
estimatedKnownTypeSize = estimateBlobKnownTypeSize(valueToStore)
73+
if (isPositiveNumber(estimatedKnownTypeSize)) {
74+
return estimatedKnownTypeSize
5075
}
51-
} catch {
52-
// size calculation rely on the shape of the value, so if it's not what we expect, we fallback to JSON.stringify
53-
knownKindFailed = true
76+
} catch (error) {
77+
knownTypeFailed = true
78+
estimateBlobKnownTypeSizeError = error
5479
}
5580

56-
// fallback for not known kinds or known kinds that did fail to calculate size
81+
// fallback for not known kinds or known kinds that did fail to calculate positive size
5782
// we should also monitor cases when fallback is used because it's not the most efficient way to calculate/estimate size
5883
// and might indicate need to make adjustments or additions to the size calculation
5984
recordWarning(
6085
new Error(
61-
`Blob size calculation did fallback to JSON.stringify. Kind: KnownKindFailed: ${knownKindFailed}, ${valueToStore.value?.kind ?? 'undefined'}`,
86+
`Blob size calculation did fallback to JSON.stringify. KnownTypeFailed: ${knownTypeFailed}, EstimatedKnownTypeSize: ${estimatedKnownTypeSize}, ValueToStore: ${JSON.stringify(valueToStore)}`,
87+
estimateBlobKnownTypeSizeError ? { cause: estimateBlobKnownTypeSizeError } : undefined,
6288
),
6389
)
6490

65-
return JSON.stringify(valueToStore).length
91+
const calculatedSize = JSON.stringify(valueToStore).length
92+
return isPositiveNumber(calculatedSize) ? calculatedSize : BASE_BLOB_SIZE
6693
}
6794

6895
function getInMemoryLRUCache() {
@@ -98,12 +125,26 @@ export const getRequestScopedInMemoryCache = (): RequestScopedInMemoryCache => {
98125
return {
99126
get(key) {
100127
if (!requestContext) return
101-
const value = inMemoryLRUCache?.get(`${requestContext.requestID}:${key}`)
102-
return value === NullValue ? null : value
128+
try {
129+
const value = inMemoryLRUCache?.get(`${requestContext.requestID}:${key}`)
130+
return value === NullValue ? null : value
131+
} catch (error) {
132+
// using in-memory store is perf optimization not requirement
133+
// trying to use optimization should NOT cause crashes
134+
// so we just record warning and return undefined
135+
recordWarning(new Error('Failed to get value from memory cache', { cause: error }))
136+
}
103137
},
104138
set(key, value) {
105139
if (!requestContext) return
106-
inMemoryLRUCache?.set(`${requestContext?.requestID}:${key}`, value ?? NullValue)
140+
try {
141+
inMemoryLRUCache?.set(`${requestContext?.requestID}:${key}`, value ?? NullValue)
142+
} catch (error) {
143+
// using in-memory store is perf optimization not requirement
144+
// trying to use optimization should NOT cause crashes
145+
// so we just record warning and return undefined
146+
recordWarning(new Error('Failed to store value in memory cache', { cause: error }))
147+
}
107148
},
108149
}
109150
}

0 commit comments

Comments
 (0)