Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

next-dev: restart dev server exceeds the memory limits #43958

Merged
merged 2 commits into from Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
73 changes: 44 additions & 29 deletions packages/next/cli/next-dev.ts
Expand Up @@ -13,6 +13,7 @@ import path from 'path'
import type { NextConfig } from '../types'
import type { NextConfigComplete } from '../server/config-shared'
import { traceGlobals } from '../trace/shared'
import cluster from 'cluster'

let isTurboSession = false
let sessionStopHandled = false
Expand Down Expand Up @@ -416,39 +417,53 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit
await telemetry.flush()
return server
} else {
startServer(devServerOptions)
.then(async (app) => {
const appUrl = `http://${app.hostname}:${app.port}`
startedDevelopmentServer(appUrl, `${host || '0.0.0.0'}:${app.port}`)
// Start preflight after server is listening and ignore errors:
preflight().catch(() => {})
// Finalize server bootup:
await app.prepare()
// we're using a sub worker to avoid memory leaks. When memory usage exceeds 90%, we kill the worker and restart it.
// this is a temporary solution until we can fix the memory leaks.
// the logic for the worker killing itself is in `packages/next/server/lib/start-server.ts`
if (!process.env.__NEXT_DISABLE_MEMORY_WATCHER && cluster.isMaster) {
cluster.fork()
cluster.on('exit', (worker) => {
if (worker.exitedAfterDisconnect) {
cluster.fork()
} else {
process.exit(1)
}
})
.catch((err) => {
if (err.code === 'EADDRINUSE') {
let errorMessage = `Port ${port} is already in use.`
const pkgAppPath = require('next/dist/compiled/find-up').sync(
'package.json',
{
cwd: dir,
}
)
const appPackage = require(pkgAppPath)
if (appPackage.scripts) {
const nextScript = Object.entries(appPackage.scripts).find(
(scriptLine) => scriptLine[1] === 'next'
} else {
startServer(devServerOptions)
.then(async (app) => {
const appUrl = `http://${app.hostname}:${app.port}`
startedDevelopmentServer(appUrl, `${host || '0.0.0.0'}:${app.port}`)
// Start preflight after server is listening and ignore errors:
preflight().catch(() => {})
// Finalize server bootup:
await app.prepare()
})
.catch((err) => {
if (err.code === 'EADDRINUSE') {
let errorMessage = `Port ${port} is already in use.`
const pkgAppPath = require('next/dist/compiled/find-up').sync(
'package.json',
{
cwd: dir,
}
)
if (nextScript) {
errorMessage += `\nUse \`npm run ${nextScript[0]} -- -p <some other port>\`.`
const appPackage = require(pkgAppPath)
if (appPackage.scripts) {
const nextScript = Object.entries(appPackage.scripts).find(
(scriptLine) => scriptLine[1] === 'next'
)
if (nextScript) {
errorMessage += `\nUse \`npm run ${nextScript[0]} -- -p <some other port>\`.`
}
}
console.error(errorMessage)
} else {
console.error(err)
}
console.error(errorMessage)
} else {
console.error(err)
}
process.nextTick(() => process.exit(1))
})
process.nextTick(() => process.exit(1))
})
}
}

for (const CONFIG_FILE of CONFIG_FILES) {
Expand Down
15 changes: 13 additions & 2 deletions packages/next/server/lib/start-server.ts
Expand Up @@ -2,17 +2,28 @@ import type { NextServerOptions, NextServer, RequestHandler } from '../next'
import { warn } from '../../build/output/log'
import http from 'http'
import next from '../next'

import cluster from 'cluster'
import v8 from 'v8'
interface StartServerOptions extends NextServerOptions {
allowRetry?: boolean
keepAliveTimeout?: number
}

const MAXIMUM_HEAP_SIZE_ALLOWED =
(v8.getHeapStatistics().heap_size_limit / 1024 / 1024) * 0.9

export function startServer(opts: StartServerOptions) {
let requestHandler: RequestHandler

const server = http.createServer((req, res) => {
return requestHandler(req, res)
return requestHandler(req, res).finally(() => {
if (
cluster.worker &&
process.memoryUsage().heapUsed / 1024 / 1024 > MAXIMUM_HEAP_SIZE_ALLOWED
) {
cluster.worker.kill()
}
})
})

if (opts.keepAliveTimeout) {
Expand Down