Skip to content

Commit 1630dc9

Browse files
authored
Backport: ensure worker exits bubble to parent process (#73433)
Backports: - #70997 - #73138 To avoid introducing the larger worker refactor in #68546, this PR introduces additional handling needed to special case the "restarting" flow to not exit the parent process.
1 parent 16ec7d3 commit 1630dc9

File tree

12 files changed

+68
-41
lines changed

12 files changed

+68
-41
lines changed

packages/next/src/build/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ import { createProgress } from './progress'
186186
import { traceMemoryUsage } from '../lib/memory/trace'
187187
import { generateEncryptionKeyBase64 } from '../server/app-render/encryption-utils'
188188
import type { DeepReadonly } from '../shared/lib/deep-readonly'
189+
import { getNodeOptionsWithoutInspect } from '../server/lib/utils'
189190

190191
interface ExperimentalBypassForInfo {
191192
experimentalBypassFor?: RouteHas[]
@@ -564,7 +565,6 @@ function createStaticWorker(
564565
): StaticWorker {
565566
let infoPrinted = false
566567
const timeout = config.staticPageGenerationTimeout || 0
567-
568568
return new Worker(staticWorkerPath, {
569569
timeout: timeout * 1000,
570570
logger: Log,
@@ -607,6 +607,11 @@ function createStaticWorker(
607607
? incrementalCacheIpcPort + ''
608608
: undefined,
609609
__NEXT_INCREMENTAL_CACHE_IPC_KEY: incrementalCacheIpcValidationKey,
610+
// we don't pass down NODE_OPTIONS as it can
611+
// extra memory usage
612+
NODE_OPTIONS: getNodeOptionsWithoutInspect()
613+
.replace(/--max-old-space-size=[\d]{1,}/, '')
614+
.trim(),
610615
},
611616
},
612617
enableWorkerThreads: config.experimental.workerThreads,

packages/next/src/build/webpack-build/index.ts

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import type { COMPILER_INDEXES } from '../../shared/lib/constants'
22
import * as Log from '../output/log'
33
import { NextBuildContext } from '../build-context'
44
import type { BuildTraceContext } from '../webpack/plugins/next-trace-entrypoints-plugin'
5-
import { Worker } from 'next/dist/compiled/jest-worker'
5+
import { Worker } from '../../lib/worker'
66
import origDebug from 'next/dist/compiled/debug'
7-
import type { ChildProcess } from 'child_process'
87
import path from 'path'
98
import { exportTraceState, recordTraceEvents } from '../../trace'
109

@@ -38,8 +37,13 @@ async function webpackBuildWithWorker(
3837

3938
prunedBuildContext.pluginState = pluginState
4039

41-
const getWorker = (compilerName: string) => {
42-
const _worker = new Worker(path.join(__dirname, 'impl.js'), {
40+
const combinedResult = {
41+
duration: 0,
42+
buildTraceContext: {} as BuildTraceContext,
43+
}
44+
45+
for (const compilerName of compilerNames) {
46+
const worker = new Worker(path.join(__dirname, 'impl.js'), {
4347
exposedMethods: ['workerMain'],
4448
numWorkers: 1,
4549
maxRetries: 0,
@@ -50,31 +54,6 @@ async function webpackBuildWithWorker(
5054
},
5155
},
5256
}) as Worker & typeof import('./impl')
53-
_worker.getStderr().pipe(process.stderr)
54-
_worker.getStdout().pipe(process.stdout)
55-
56-
for (const worker of ((_worker as any)._workerPool?._workers || []) as {
57-
_child: ChildProcess
58-
}[]) {
59-
worker._child.on('exit', (code, signal) => {
60-
if (code || (signal && signal !== 'SIGINT')) {
61-
debug(
62-
`Compiler ${compilerName} unexpectedly exited with code: ${code} and signal: ${signal}`
63-
)
64-
}
65-
})
66-
}
67-
68-
return _worker
69-
}
70-
71-
const combinedResult = {
72-
duration: 0,
73-
buildTraceContext: {} as BuildTraceContext,
74-
}
75-
76-
for (const compilerName of compilerNames) {
77-
const worker = getWorker(compilerName)
7857

7958
const curResult = await worker.workerMain({
8059
buildContext: prunedBuildContext,

packages/next/src/lib/worker.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ChildProcess } from 'child_process'
22
import { Worker as JestWorker } from 'next/dist/compiled/jest-worker'
3-
import { getNodeOptionsWithoutInspect } from '../server/lib/utils'
3+
44
type FarmOptions = ConstructorParameters<typeof JestWorker>[1]
55

66
const RESTARTED = Symbol('restarted')
@@ -15,6 +15,7 @@ const cleanupWorkers = (worker: JestWorker) => {
1515

1616
export class Worker {
1717
private _worker: JestWorker | undefined
18+
private _restarting: boolean = false
1819

1920
constructor(
2021
workerPath: string,
@@ -42,14 +43,10 @@ export class Worker {
4243
env: {
4344
...((farmOptions.forkOptions?.env || {}) as any),
4445
...process.env,
45-
// we don't pass down NODE_OPTIONS as it can
46-
// extra memory usage
47-
NODE_OPTIONS: getNodeOptionsWithoutInspect()
48-
.replace(/--max-old-space-size=[\d]{1,}/, '')
49-
.trim(),
5046
} as any,
5147
},
5248
}) as JestWorker
49+
this._restarting = false
5350
restartPromise = new Promise(
5451
(resolve) => (resolveRestartPromise = resolve)
5552
)
@@ -73,6 +70,12 @@ export class Worker {
7370
logger.error(
7471
`Static worker exited with code: ${code} and signal: ${signal}`
7572
)
73+
74+
// We're restarting the worker, so we don't want to exit the parent process
75+
if (!this._restarting) {
76+
// if a child process doesn't exit gracefully, we want to bubble up the exit code to the parent process
77+
process.exit(code ?? 1)
78+
}
7679
}
7780
})
7881
}
@@ -87,14 +90,17 @@ export class Worker {
8790
const worker = this._worker
8891
if (!worker) return
8992
const resolve = resolveRestartPromise
90-
createWorker()
9193
logger.warn(
9294
`Sending SIGTERM signal to static worker due to timeout${
9395
timeout ? ` of ${timeout / 1000} seconds` : ''
9496
}. Subsequent errors may be a result of the worker exiting.`
9597
)
98+
99+
this._restarting = true
100+
96101
worker.end().then(() => {
97102
resolve(RESTARTED)
103+
createWorker()
98104
})
99105
}
100106

@@ -119,6 +125,7 @@ export class Worker {
119125
(this._worker as any)[method](...args),
120126
restartPromise,
121127
])
128+
122129
if (result !== RESTARTED) return result
123130
if (onRestart) onRestart(method, args, ++attempts)
124131
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function Page() {
2+
process.kill(process.pid, 'SIGKILL')
3+
4+
return <div>Kaboom</div>
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function Layout({ children }) {
2+
return (
3+
<html>
4+
<body>{children}</body>
5+
</html>
6+
)
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <div>Hello World</div>
3+
}

0 commit comments

Comments
 (0)