Skip to content

Commit 589f090

Browse files
authored
memory: fix 2 memory leaks in next-dev (#43859)
This PR fixes two memory leaks I found debugging with @sokra. ## 1) Leak in `next-server.ts` The first leak was caused by the fact that the `require.cache` associated to the `next-server` module was not being cleared up properly, so we leaked context from modules required in that page, like API routes. ## 2) Leak with React Fetch When evaluating a route, we also evaluated the `react.shared-subset.development.js` module where React patches the `fetch` function. The problem is that when re-evaluating a route as part of hot reloading, we were patching over the previously patched `fetch` function. The result of this operation meant that we were keeping a reference to the context of the previous `fetch` and thus to the previous route context, thus creating a memory leak, since we only needed the new context. ## Test plan Checked manually the heap snapshots of a test app. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
1 parent 8a17198 commit 589f090

File tree

2 files changed

+17
-0
lines changed

2 files changed

+17
-0
lines changed

packages/next/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ type WebpackPluginInstance = webpack.WebpackPluginInstance
1010
const originModules = [
1111
require.resolve('../../../server/require'),
1212
require.resolve('../../../server/load-components'),
13+
require.resolve('../../../server/next-server'),
14+
require.resolve('../../../compiled/react-server-dom-webpack/client'),
1315
]
1416

1517
const RUNTIME_NAMES = ['webpack-runtime', 'webpack-api-runtime']

packages/next/server/dev/next-dev-server.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export default class DevServer extends Server {
109109
private edgeFunctions?: RoutingItem[]
110110
private verifyingTypeScript?: boolean
111111
private usingTypeScript?: boolean
112+
private originalFetch?: typeof fetch
112113

113114
protected staticPathsWorker?: { [key: string]: any } & {
114115
loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths
@@ -149,6 +150,7 @@ export default class DevServer extends Server {
149150

150151
constructor(options: Options) {
151152
super({ ...options, dev: true })
153+
this.persistPatchedGlobals()
152154
this.renderOpts.dev = true
153155
;(this.renderOpts as any).ErrorDebug = ReactDevOverlay
154156
this.devReady = new Promise((resolve) => {
@@ -1385,6 +1387,14 @@ export default class DevServer extends Server {
13851387
return this.hotReloader!.ensurePage({ page: pathname, clientOnly: false })
13861388
}
13871389

1390+
private persistPatchedGlobals(): void {
1391+
this.originalFetch = global.fetch
1392+
}
1393+
1394+
private restorePatchedGlobals(): void {
1395+
global.fetch = this.originalFetch!
1396+
}
1397+
13881398
protected async findPageComponents({
13891399
pathname,
13901400
query,
@@ -1418,6 +1428,11 @@ export default class DevServer extends Server {
14181428
this.serverCSSManifest = super.getServerCSSManifest()
14191429
}
14201430
this.fontLoaderManifest = super.getFontLoaderManifest()
1431+
// before we re-evaluate a route module, we want to restore globals that might
1432+
// have been patched previously to their original state so that we don't
1433+
// patch on top of the previous patch, which would keep the context of the previous
1434+
// patched global in memory, creating a memory leak.
1435+
this.restorePatchedGlobals()
14211436

14221437
return super.findPageComponents({ pathname, query, params, isAppPath })
14231438
} catch (err) {

0 commit comments

Comments
 (0)