Skip to content

Commit 20f5a79

Browse files
committed
fix: don't memory-leak promises passed to waitUntil (#75041)
### Overview `next start` stores pending promises passed to `waitUntil` so that we can await them before a (graceful) server shutdown and avoid interrupting work that's still running. The problem here was that this set lives a long time (the whole lifetime of the server), but we weren't removing resolved promises from this set, so we'd end up holding onto them forever and leaking memory. This bug would be triggered by any code using `waitUntil` in `next start` (notably, a recent change: #74164) ### Details The bug here is that `AwaiterMulti` would hold onto resolved promises indefinitely. `AwaiterMulti` ends up being used by `NextNodeServer.getInternalWaitUntil`, which is what provides `waitUntil` in `next start`. The code was already attempting to clean up promises to avoid leaks. But the problem was that we weren't adding `promise` to `this.promises`, we were adding `promise.then(...)` which is a completely different object. So the `this.promises.delete(promise)` that `cleanup` did was effectively doing nothing, and `this.promises` would keep growing.
1 parent 47102ca commit 20f5a79

File tree

1 file changed

+9
-8
lines changed

1 file changed

+9
-8
lines changed

packages/next/src/server/after/awaiter.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,25 @@ export class AwaiterMulti {
1515
}
1616

1717
public waitUntil = (promise: Promise<unknown>): void => {
18-
// if a promise settles before we await it, we can drop it.
18+
// if a promise settles before we await it, we should drop it --
19+
// storing them indefinitely could result in a memory leak.
1920
const cleanup = () => {
2021
this.promises.delete(promise)
2122
}
2223

23-
this.promises.add(
24-
promise.then(cleanup, (err) => {
25-
cleanup()
26-
this.onError(err)
27-
})
28-
)
24+
promise.then(cleanup, (err) => {
25+
cleanup()
26+
this.onError(err)
27+
})
28+
29+
this.promises.add(promise)
2930
}
3031

3132
public async awaiting(): Promise<void> {
3233
while (this.promises.size > 0) {
3334
const promises = Array.from(this.promises)
3435
this.promises.clear()
35-
await Promise.all(promises)
36+
await Promise.allSettled(promises)
3637
}
3738
}
3839
}

0 commit comments

Comments
 (0)