@@ -25,44 +25,70 @@ export function setInMemoryCacheMaxSizeFromNextConfig(size: unknown) {
25
25
}
26
26
}
27
27
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 => {
29
38
// very approximate size calculation to avoid expensive exact size calculation
30
39
// inspired by https://github.com/vercel/next.js/blob/ed10f7ed0246fcc763194197eb9beebcbd063162/packages/next/src/server/lib/incremental-cache/file-system-cache.ts#L60-L79
31
40
if ( valueToStore === null || isPromise ( valueToStore ) || isTagManifest ( valueToStore ) ) {
32
- return 25
41
+ return BASE_BLOB_SIZE
33
42
}
34
43
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
+ )
36
61
}
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 estimatedKnownTypeSize : number | undefined
69
+ let estimateBlobKnownTypeSizeError : unknown
38
70
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 )
71
+ estimatedKnownTypeSize = estimateBlobKnownTypeSize ( valueToStore )
72
+ if ( isPositiveNumber ( estimatedKnownTypeSize ) ) {
73
+ return estimatedKnownTypeSize
44
74
}
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
50
- }
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
75
+ } catch ( error ) {
76
+ estimateBlobKnownTypeSizeError = error
54
77
}
55
78
56
- // fallback for not known kinds or known kinds that did fail to calculate size
79
+ // fallback for not known kinds or known kinds that did fail to calculate positive size
80
+ const calculatedSize = JSON . stringify ( valueToStore ) . length
81
+
57
82
// we should also monitor cases when fallback is used because it's not the most efficient way to calculate/estimate size
58
83
// and might indicate need to make adjustments or additions to the size calculation
59
84
recordWarning (
60
85
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. EstimatedKnownTypeSize: ${ estimatedKnownTypeSize } , CalculatedSize: ${ calculatedSize } , ValueToStore: ${ JSON . stringify ( valueToStore ) } ` ,
87
+ estimateBlobKnownTypeSizeError ? { cause : estimateBlobKnownTypeSizeError } : undefined ,
62
88
) ,
63
89
)
64
90
65
- return JSON . stringify ( valueToStore ) . length
91
+ return isPositiveNumber ( calculatedSize ) ? calculatedSize : BASE_BLOB_SIZE
66
92
}
67
93
68
94
function getInMemoryLRUCache ( ) {
@@ -98,12 +124,26 @@ export const getRequestScopedInMemoryCache = (): RequestScopedInMemoryCache => {
98
124
return {
99
125
get ( key ) {
100
126
if ( ! requestContext ) return
101
- const value = inMemoryLRUCache ?. get ( `${ requestContext . requestID } :${ key } ` )
102
- return value === NullValue ? null : value
127
+ try {
128
+ const value = inMemoryLRUCache ?. get ( `${ requestContext . requestID } :${ key } ` )
129
+ return value === NullValue ? null : value
130
+ } catch ( error ) {
131
+ // using in-memory store is perf optimization not requirement
132
+ // trying to use optimization should NOT cause crashes
133
+ // so we just record warning and return undefined
134
+ recordWarning ( new Error ( 'Failed to get value from memory cache' , { cause : error } ) )
135
+ }
103
136
} ,
104
137
set ( key , value ) {
105
138
if ( ! requestContext ) return
106
- inMemoryLRUCache ?. set ( `${ requestContext ?. requestID } :${ key } ` , value ?? NullValue )
139
+ try {
140
+ inMemoryLRUCache ?. set ( `${ requestContext ?. requestID } :${ key } ` , value ?? NullValue )
141
+ } catch ( error ) {
142
+ // using in-memory store is perf optimization not requirement
143
+ // trying to use optimization should NOT cause crashes
144
+ // so we just record warning and return undefined
145
+ recordWarning ( new Error ( 'Failed to store value in memory cache' , { cause : error } ) )
146
+ }
107
147
} ,
108
148
}
109
149
}
0 commit comments