@@ -25,44 +25,71 @@ 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 knownTypeFailed = false
69
+ let estimatedKnownTypeSize : number | undefined
70
+ let estimateBlobKnownTypeSizeError : unknown
38
71
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
50
75
}
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
54
79
}
55
80
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
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. KnownTypeFailed: ${ knownTypeFailed } , EstimatedKnownTypeSize: ${ estimatedKnownTypeSize } , ValueToStore: ${ JSON . stringify ( valueToStore ) } ` ,
87
+ estimateBlobKnownTypeSizeError ? { cause : estimateBlobKnownTypeSizeError } : undefined ,
62
88
) ,
63
89
)
64
90
65
- return JSON . stringify ( valueToStore ) . length
91
+ const calculatedSize = JSON . stringify ( valueToStore ) . length
92
+ return isPositiveNumber ( calculatedSize ) ? calculatedSize : BASE_BLOB_SIZE
66
93
}
67
94
68
95
function getInMemoryLRUCache ( ) {
@@ -98,12 +125,26 @@ export const getRequestScopedInMemoryCache = (): RequestScopedInMemoryCache => {
98
125
return {
99
126
get ( key ) {
100
127
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
+ }
103
137
} ,
104
138
set ( key , value ) {
105
139
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
+ }
107
148
} ,
108
149
}
109
150
}
0 commit comments