diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index 07e2c7a489c4a..02e920d6971cc 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -34,20 +34,17 @@ import { } from '../lib/helpers/get-reserved-port' import os from 'os' -type Child = ReturnType -type ExitCode = Parameters[0] - let dir: string -let child: undefined | Child +let child: undefined | ReturnType let config: NextConfigComplete let isTurboSession = false let traceUploadUrl: string let sessionStopHandled = false let sessionStarted = Date.now() -const handleSessionStop = async (signal: ExitCode | null) => { +const handleSessionStop = async (signal: string | null) => { if (child) { - child.kill(signal ?? 0) + child.kill((signal as any) || 0) } if (sessionStopHandled) return sessionStopHandled = true @@ -111,11 +108,8 @@ const handleSessionStop = async (signal: ExitCode | null) => { process.exit(0) } -process.on('SIGINT', () => handleSessionStop('SIGKILL')) -process.on('SIGTERM', () => handleSessionStop('SIGKILL')) - -// exit event must be synchronous -process.on('exit', () => child?.kill('SIGKILL')) +process.on('SIGINT', () => handleSessionStop('SIGINT')) +process.on('SIGTERM', () => handleSessionStop('SIGTERM')) const nextDev: CliCommand = async (args) => { if (args['--help']) { @@ -339,4 +333,16 @@ const nextDev: CliCommand = async (args) => { await runDevServer(false) } +function cleanup() { + if (!child) { + return + } + + child.kill('SIGTERM') +} + +process.on('exit', cleanup) +process.on('SIGINT', cleanup) +process.on('SIGTERM', cleanup) + export { nextDev } diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 59ef2362d3972..84610325b0632 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -264,9 +264,10 @@ export async function startServer( }) try { - const cleanup = () => { + const cleanup = (code: number | null) => { debug('start-server process cleanup') - server.close(() => process.exit(0)) + server.close() + process.exit(code ?? 0) } const exception = (err: Error) => { if (isPostpone(err)) { @@ -278,11 +279,11 @@ export async function startServer( // This is the render worker, we keep the process alive console.error(err) } - // Make sure commands gracefully respect termination signals (e.g. from Docker) - // Allow the graceful termination to be manually configurable + process.on('exit', (code) => cleanup(code)) if (!process.env.NEXT_MANUAL_SIG_HANDLE) { - process.on('SIGINT', cleanup) - process.on('SIGTERM', cleanup) + // callback value is signal string, exit with 0 + process.on('SIGINT', () => cleanup(0)) + process.on('SIGTERM', () => cleanup(0)) } process.on('rejectionHandled', () => { // It is ok to await a Promise late in Next.js as it allows for better diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts index 234a2d093b8e6..f764949059706 100644 --- a/test/lib/next-test-utils.ts +++ b/test/lib/next-test-utils.ts @@ -526,7 +526,7 @@ export async function killProcess( // Kill a launched app export async function killApp(instance: ChildProcess) { if (instance && instance.pid) { - await killProcess(instance.pid, 'SIGKILL') + await killProcess(instance.pid) } }