diff --git a/packages/next/src/server/lib/incremental-cache/index.ts b/packages/next/src/server/lib/incremental-cache/index.ts index 03e1bb42e808..4bf18ed0d221 100644 --- a/packages/next/src/server/lib/incremental-cache/index.ts +++ b/packages/next/src/server/lib/incremental-cache/index.ts @@ -34,6 +34,7 @@ import { InvariantError } from '../../../shared/lib/invariant-error' import type { Revalidate } from '../cache-control' import { getPreviouslyRevalidatedTags } from '../../server-utils' import { workAsyncStorage } from '../../app-render/work-async-storage.external' +import { DetachedPromise } from '../../../lib/detached-promise' export interface CacheHandlerContext { fs?: CacheFs @@ -91,6 +92,8 @@ export class IncrementalCache implements IncrementalCacheType { readonly revalidatedTags?: string[] readonly isOnDemandRevalidate?: boolean + private static readonly debug: boolean = + !!process.env.NEXT_PRIVATE_DEBUG_CACHE private readonly locks = new Map>() /** @@ -124,7 +127,6 @@ export class IncrementalCache implements IncrementalCacheType { fetchCacheKeyPrefix?: string CurCacheHandler?: typeof CacheHandler }) { - const debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE this.hasCustomCacheHandler = Boolean(CurCacheHandler) const cacheHandlersSymbol = Symbol.for('@next/cache-handlers') @@ -142,13 +144,13 @@ export class IncrementalCache implements IncrementalCacheType { CurCacheHandler = globalCacheHandler.FetchCache } else { if (fs && serverDistDir) { - if (debug) { + if (IncrementalCache.debug) { console.log('using filesystem cache handler') } CurCacheHandler = FileSystemCache } } - } else if (debug) { + } else if (IncrementalCache.debug) { console.log('using custom cache handler', CurCacheHandler.name) } @@ -234,23 +236,42 @@ export class IncrementalCache implements IncrementalCacheType { this.cacheHandler?.resetRequestCache?.() } - async lock(cacheKey: string) { - let unlockNext: () => Promise = () => Promise.resolve() - const existingLock = this.locks.get(cacheKey) + async lock(cacheKey: string): Promise<() => Promise | void> { + // Wait for any existing lock on this cache key to be released + // This implements a simple queue-based locking mechanism + while (true) { + const lock = this.locks.get(cacheKey) - if (existingLock) { - await existingLock + if (IncrementalCache.debug) { + console.log('lock get', cacheKey, !!lock) + } + + // If no lock exists, we can proceed to acquire it + if (!lock) break + + // Wait for the existing lock to be released before trying again + await lock } - const newLock = new Promise((resolve) => { - unlockNext = async () => { - resolve() - this.locks.delete(cacheKey) // Remove the lock upon release - } - }) + // Create a new detached promise that will represent this lock + // The resolve function (unlock) will be returned to the caller + const { resolve, promise } = new DetachedPromise() - this.locks.set(cacheKey, newLock) - return unlockNext + if (IncrementalCache.debug) { + console.log('successfully locked', cacheKey) + } + + // Store the lock promise in the locks map + this.locks.set(cacheKey, promise) + + return () => { + // Resolve the promise to release the lock. + resolve() + + // Remove the lock from the map once it's released so that future gets + // can acquire the lock. + this.locks.delete(cacheKey) + } } async revalidateTag(tags: string | string[]): Promise { diff --git a/packages/next/src/server/lib/patch-fetch.test.ts b/packages/next/src/server/lib/patch-fetch.test.ts index fa5f11cd7462..1f7ad7f52ddf 100644 --- a/packages/next/src/server/lib/patch-fetch.test.ts +++ b/packages/next/src/server/lib/patch-fetch.test.ts @@ -41,7 +41,7 @@ describe('createPatchedFetcher', () => { get: jest.fn(), set: jest.fn(() => resolveIncrementalCacheSet()), generateCacheKey: jest.fn(() => 'test-cache-key'), - lock: jest.fn(() => resolveIncrementalCacheSet), + lock: jest.fn(() => () => {}), } as unknown as IncrementalCache // We only need to provide a few of the WorkStore properties. diff --git a/packages/next/src/server/lib/patch-fetch.ts b/packages/next/src/server/lib/patch-fetch.ts index 81b033cce02c..9990da439a9d 100644 --- a/packages/next/src/server/lib/patch-fetch.ts +++ b/packages/next/src/server/lib/patch-fetch.ts @@ -573,7 +573,7 @@ export function createPatchedFetcher( const fetchIdx = workStore.nextFetchId ?? 1 workStore.nextFetchId = fetchIdx + 1 - let handleUnlock = () => Promise.resolve() + let handleUnlock: () => Promise | void = () => {} const doOriginalFetch = async ( isStale?: boolean,