-
Notifications
You must be signed in to change notification settings - Fork 29.9k
Description
Link to the code that reproduces this issue
https://github.com/gaearon/next-bug-repro
To Reproduce
Current vs. Expected behavior
Current behavior: it hangs
Expected behavior: it doesn't hang
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 23.6.0: Fri Nov 15 15:12:52 PST 2024; root:xnu-10063.141.1.702.7~1/RELEASE_ARM64_T6031
Available memory (MB): 131072
Available CPU cores: 16
Binaries:
Node: 22.20.0
npm: 10.9.3
Yarn: 1.22.22
pnpm: 10.18.1
Relevant Packages:
next: 16.1.0-canary.4 // Latest available version is detected (16.1.0-canary.4).
eslint-config-next: N/A
react: 19.2.0
react-dom: 19.2.0
typescript: 5.9.3
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
cacheComponents
Which stage(s) are affected? (Select all that apply)
next dev (local)
Additional context
This is in the repo already, but I'll briefly state the problem.
First, enable Cache Components.
Then, suppose we have page.ts like this:
async function cachedFn(): Promise<void> {
"use cache";
await new Promise((resolve) => setTimeout(resolve, 100));
}
export default async function Page() {
await cachedFn();
return <p>page content</p>;
}And layout.ts like this:
const pending = new Map<string, Promise<any>>();
async function dedup(key: string): Promise<any> {
let existingPromise;
while ((existingPromise = pending.get(key))) {
console.log("await existing");
return await existingPromise;
}
console.log("starting");
const promise = fetch("https://httpbin.org/get")
.then((res) => {
return res.json();
})
.finally(() => {
console.log("done");
pending.delete(key);
});
pending.set(key, promise);
return promise;
}
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
await dedup("test");
return (
<html>
<body>{children}</body>
</html>
);
}The Map for pending Promises is something I extracted from a third-party OAuth client I'm using so I don't really have control over it. It seems designed to ensure serial execution. I suspect this is a reasonable pattern that might show up in different places so I wouldn't expect Next to break on it. If I'm not mistaken, the original piece of code (from which Claude extracted this minimal repro) was here (cc @matthieusieben in case he can provide more context on the requirement of OAuth flow).
The logs I see on start:
starting
await existing
So basically:
- The initial
fetchruns but never completes (why?) - The pending Promise stays in the map
- Any future attempt (maybe the first one was a prerender?) waits for that Promise forever
The most frustrating/surprising thing is that removing 'use cache' from the layout.ts (which intuitively feels completely unrelated to this code!) "fixes" the issue in page.ts.
My questions are:
- How to make it work with the original code (surely we can't expect the ecosystem to remove these types of locks from some flows)
- Is it a bug that mere presence of
"use cache"on a completely different function causes this to break?