Skip to content

Commit

Permalink
Track timestamp and expire time on CacheEntry (#71227)
Browse files Browse the repository at this point in the history
Compute whether it's expired or stale based on those and the current
time.
  • Loading branch information
sebmarkbage authored Oct 13, 2024
1 parent 605ba61 commit e09d5b0
Showing 1 changed file with 31 additions and 7 deletions.
38 changes: 31 additions & 7 deletions packages/next/src/server/use-cache/use-cache-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge'

type CacheEntry = {
value: ReadableStream
timestamp: number
// In-memory caches are fragile and should not use stale-while-revalidate
// semantics on the caches because it's not worth warming up an entry that's
// likely going to get evicted before we get to use it anyway. However,
// we also don't want to reuse a stale entry for too long so stale entries
// should be considered expired/missing in such CacheHandlers.
stale: boolean
tags: string[]
revalidate: number
expire: number
stale: number
tags: string[]
}

interface CacheHandler {
Expand All @@ -60,12 +62,22 @@ cacheHandlerMap.set('default', {
// TODO: Implement proper caching.
const entry = await defaultCacheStorage.get(cacheKey)
if (entry !== undefined) {
if (
performance.timeOrigin + performance.now() >
entry.timestamp + entry.revalidate * 1000
) {
// In memory caches should expire after revalidate time because it is unlikely that
// a new entry will be able to be used before it is dropped from the cache.
return undefined
}
const [returnStream, newSaved] = entry.value.tee()
entry.value = newSaved
return {
value: returnStream,
stale: false,
timestamp: entry.timestamp,
revalidate: entry.revalidate,
expire: entry.revalidate,
stale: entry.stale,
tags: entry.tags,
}
}
Expand Down Expand Up @@ -217,6 +229,7 @@ async function collectResult(
savedStream: ReadableStream,
outerWorkUnitStore: WorkUnitStore | undefined,
innerCacheStore: UseCacheStore,
startTime: number,
errors: Array<unknown> // This is a live array that gets pushed into.
): Promise<CacheEntry> {
// We create a buffered stream that collects all chunks until the end to
Expand Down Expand Up @@ -263,9 +276,11 @@ async function collectResult(

const entry = {
value: bufferStream,
stale: false, // TODO: rm
tags: collectedTags === null ? [] : collectedTags,
timestamp: startTime,
revalidate: collectedRevalidate,
expire: Infinity,
stale: 0,
tags: collectedTags === null ? [] : collectedTags,
}
// Propagate tags/revalidate to the parent context.
propagateCacheLifeAndTags(outerWorkUnitStore, entry)
Expand Down Expand Up @@ -301,6 +316,8 @@ async function generateCacheEntryImpl(
}
)

// Track the timestamp when we started copmuting the result.
const startTime = performance.timeOrigin + performance.now()
// Invoke the inner function to load a new result.
const result = fn.apply(null, args)

Expand All @@ -326,6 +343,7 @@ async function generateCacheEntryImpl(
savedStream,
outerWorkUnitStore,
innerCacheStore,
startTime,
errors
)

Expand Down Expand Up @@ -372,7 +390,13 @@ async function loadCacheEntry(
implicitTags
)

if (entry === undefined || (entry.stale && workStore.isStaticGeneration)) {
const currentTime = performance.timeOrigin + performance.now()
if (
entry === undefined ||
currentTime > entry.timestamp + entry.expire * 1000 ||
(workStore.isStaticGeneration &&
currentTime > entry.timestamp + entry.revalidate * 1000)
) {
// Miss. Generate a new result.

// If the cache entry is stale and we're prerendering, we don't want to use the
Expand All @@ -398,7 +422,7 @@ async function loadCacheEntry(
} else {
propagateCacheLifeAndTags(workUnitStore, entry)

if (entry.stale) {
if (currentTime > entry.timestamp + entry.revalidate) {
// If this is stale, and we're not in a prerender (i.e. this is dynamic render),
// then we should warm up the cache with a fresh revalidated entry.
const ignoredStream = await generateCacheEntry(
Expand Down

0 comments on commit e09d5b0

Please sign in to comment.