Skip to content

Commit 5b3ba20

Browse files
authored
[build-sourcemaps] Allow inspecting prerender worker (#79098)
If `--inspect` or `--inspect-brk` is specified, prerender will no longer time out since that's most likely not wanted (e.g. you'd always get timeouts if you set breakpoints). Inspecting prerender workers is best done with `experimental.cpus: 1`. Only the first prerender worker can be inspected since we can't set `NODE_OPTIONS` based off of the worker index. This PR also sets up infra for inspecting arbitrary workers. Their debug port would need to be coordinated within `getNextBuildDebuggerPortOffset`. This doesn't work if worker creation is shared between `next build` and `next dev` which I haven't checked if that's the case.
1 parent cf33e05 commit 5b3ba20

File tree

9 files changed

+92
-41
lines changed

9 files changed

+92
-41
lines changed

packages/next/src/build/index.ts

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,6 @@ import {
194194
import { FallbackMode, fallbackModeToFallbackField } from '../lib/fallback'
195195
import { RenderingMode } from './rendering-mode'
196196
import { getParamKeys } from '../server/request/fallback-params'
197-
import {
198-
formatNodeOptions,
199-
getParsedNodeOptionsWithoutInspect,
200-
} from '../server/lib/utils'
201197
import { InvariantError } from '../shared/lib/invariant-error'
202198
import { HTML_LIMITED_BOT_UA_RE_STRING } from '../shared/lib/router/utils/is-bot'
203199
import type { UseCacheTrackerKey } from './webpack/plugins/telemetry-plugin/use-cache-tracker-utils'
@@ -743,17 +739,15 @@ const staticWorkerExposedMethods = [
743739
type StaticWorker = typeof import('./worker') & Worker
744740
export function createStaticWorker(
745741
config: NextConfigComplete,
746-
progress?: {
747-
run: () => void
748-
clear: () => void
742+
options: {
743+
debuggerPortOffset: number
744+
progress?: {
745+
run: () => void
746+
clear: () => void
747+
}
749748
}
750749
): StaticWorker {
751-
// Get the node options without inspect and also remove the
752-
// --max-old-space-size flag as it can cause memory issues.
753-
const nodeOptions = getParsedNodeOptionsWithoutInspect()
754-
delete nodeOptions['max-old-space-size']
755-
delete nodeOptions['max_old_space_size']
756-
750+
const { debuggerPortOffset, progress } = options
757751
return new Worker(staticWorkerPath, {
758752
logger: Log,
759753
numWorkers: getNumberOfWorkers(config),
@@ -763,11 +757,9 @@ export function createStaticWorker(
763757
onActivityAbort: () => {
764758
progress?.clear()
765759
},
766-
forkOptions: {
767-
env: {
768-
NODE_OPTIONS: formatNodeOptions(nodeOptions),
769-
},
770-
},
760+
debuggerPortOffset,
761+
// remove --max-old-space-size flag as it can cause memory issues.
762+
isolatedMemory: true,
771763
enableWorkerThreads: config.experimental.workerThreads,
772764
exposedMethods: staticWorkerExposedMethods,
773765
}) as StaticWorker
@@ -1496,6 +1488,8 @@ export default async function build(
14961488
const buildTraceWorker = new Worker(
14971489
require.resolve('./collect-build-traces'),
14981490
{
1491+
debuggerPortOffset: -1,
1492+
isolatedMemory: false,
14991493
numWorkers: 1,
15001494
exposedMethods: ['collectBuildTraces'],
15011495
}
@@ -1652,7 +1646,7 @@ export default async function build(
16521646

16531647
process.env.NEXT_PHASE = PHASE_PRODUCTION_BUILD
16541648

1655-
const worker = createStaticWorker(config)
1649+
const worker = createStaticWorker(config, { debuggerPortOffset: -1 })
16561650

16571651
const analysisBegin = process.hrtime()
16581652
const staticCheckSpan = nextBuildSpan.traceChild('static-check')

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import path from 'path'
2-
import {
3-
formatNodeOptions,
4-
getParsedNodeOptionsWithoutInspect,
5-
} from '../../server/lib/utils'
2+
63
import { Worker } from '../../lib/worker'
74
import { NextBuildContext } from '../build-context'
85

96
async function turbopackBuildWithWorker() {
10-
const nodeOptions = getParsedNodeOptionsWithoutInspect()
11-
127
try {
138
const worker = new Worker(path.join(__dirname, 'impl.js'), {
149
exposedMethods: ['workerMain', 'waitForShutdown'],
10+
debuggerPortOffset: -1,
11+
isolatedMemory: false,
1512
numWorkers: 1,
1613
maxRetries: 0,
1714
forkOptions: {
1815
env: {
1916
NEXT_PRIVATE_BUILD_WORKER: '1',
20-
NODE_OPTIONS: formatNodeOptions(nodeOptions),
2117
},
2218
},
2319
}) as Worker & typeof import('./impl')

packages/next/src/build/type-check.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ function verifyTypeScriptSetup(
3434
require.resolve('../lib/verify-typescript-setup'),
3535
{
3636
exposedMethods: ['verifyTypeScriptSetup'],
37+
debuggerPortOffset: -1,
38+
isolatedMemory: false,
3739
numWorkers: 1,
3840
enableWorkerThreads,
3941
maxRetries: 0,

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ import { Worker } from '../../lib/worker'
66
import origDebug from 'next/dist/compiled/debug'
77
import path from 'path'
88
import { exportTraceState, recordTraceEvents } from '../../trace'
9-
import {
10-
formatNodeOptions,
11-
getParsedNodeOptionsWithoutInspect,
12-
} from '../../server/lib/utils'
139
import { mergeUseCacheTrackers } from '../webpack/plugins/telemetry-plugin/use-cache-tracker-utils'
1410
import { durationToString } from '../duration-to-string'
1511

@@ -48,17 +44,16 @@ async function webpackBuildWithWorker(
4844
buildTraceContext: {} as BuildTraceContext,
4945
}
5046

51-
const nodeOptions = getParsedNodeOptionsWithoutInspect()
52-
5347
for (const compilerName of compilerNames) {
5448
const worker = new Worker(path.join(__dirname, 'impl.js'), {
5549
exposedMethods: ['workerMain'],
50+
debuggerPortOffset: -1,
51+
isolatedMemory: false,
5652
numWorkers: 1,
5753
maxRetries: 0,
5854
forkOptions: {
5955
env: {
6056
NEXT_PRIVATE_BUILD_WORKER: '1',
61-
NODE_OPTIONS: formatNodeOptions(nodeOptions),
6257
},
6358
},
6459
}) as Worker & typeof import('./impl')

packages/next/src/export/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import { isInterceptionRouteRewrite } from '../lib/generate-interception-routes-
6363
import type { ActionManifest } from '../build/webpack/plugins/flight-client-entry-plugin'
6464
import { extractInfoFromServerReferenceId } from '../shared/lib/server-reference-info'
6565
import { convertSegmentPathToStaticExportFilename } from '../shared/lib/segment-cache/segment-value-encoding'
66+
import { getNextBuildDebuggerPortOffset } from '../lib/worker'
6667

6768
export class ExportError extends Error {
6869
code = 'NEXT_EXPORT_ERROR'
@@ -581,7 +582,10 @@ async function exportAppImpl(
581582
options.statusMessage || 'Exporting'
582583
)
583584

584-
const worker = createStaticWorker(nextConfig, progress)
585+
const worker = createStaticWorker(nextConfig, {
586+
debuggerPortOffset: getNextBuildDebuggerPortOffset({ kind: 'export-page' }),
587+
progress,
588+
})
585589

586590
const results = (
587591
await Promise.all(

packages/next/src/export/worker.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ export async function exportPages(
401401
let attempt = 0
402402
let result
403403

404+
const hasDebuggerAttached =
405+
// Also tests for `inspect-brk`
406+
process.env.NODE_OPTIONS?.includes('--inspect')
407+
404408
while (attempt < maxAttempts) {
405409
try {
406410
result = await Promise.race<ExportPageResult | undefined>([
@@ -427,12 +431,15 @@ export async function exportPages(
427431
sriEnabled: Boolean(nextConfig.experimental.sri?.algorithm),
428432
buildId: input.buildId,
429433
}),
430-
// If exporting the page takes longer than the timeout, reject the promise.
431-
new Promise((_, reject) => {
432-
setTimeout(() => {
433-
reject(new TimeoutError())
434-
}, nextConfig.staticPageGenerationTimeout * 1000)
435-
}),
434+
hasDebuggerAttached
435+
? // With a debugger attached, exporting can take infinitely if we paused script execution.
436+
new Promise(() => {})
437+
: // If exporting the page takes longer than the timeout, reject the promise.
438+
new Promise((_, reject) => {
439+
setTimeout(() => {
440+
reject(new TimeoutError())
441+
}, nextConfig.staticPageGenerationTimeout * 1000)
442+
}),
436443
])
437444

438445
// If there was an error in the export, throw it immediately. In the catch block, we might retry the export,

packages/next/src/lib/verifyAndLint.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export async function verifyAndLint(
2424
try {
2525
lintWorkers = new Worker(require.resolve('./eslint/runLintCheck'), {
2626
exposedMethods: ['runLintCheck'],
27+
debuggerPortOffset: -1,
28+
isolatedMemory: false,
2729
numWorkers: 1,
2830
enableWorkerThreads,
2931
maxRetries: 0,

packages/next/src/lib/worker.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import type { ChildProcess } from 'child_process'
22
import { Worker as JestWorker } from 'next/dist/compiled/jest-worker'
33
import { Transform } from 'stream'
4+
import {
5+
formatDebugAddress,
6+
formatNodeOptions,
7+
getNodeDebugType,
8+
getParsedDebugAddress,
9+
getParsedNodeOptionsWithoutInspect,
10+
} from '../server/lib/utils'
411

512
type FarmOptions = NonNullable<ConstructorParameters<typeof JestWorker>[1]>
613

@@ -14,6 +21,13 @@ const cleanupWorkers = (worker: JestWorker) => {
1421
}
1522
}
1623

24+
export function getNextBuildDebuggerPortOffset(_: {
25+
kind: 'export-page'
26+
}): number {
27+
// 0: export worker
28+
return 0
29+
}
30+
1731
export class Worker {
1832
private _worker: JestWorker | undefined
1933

@@ -25,6 +39,14 @@ export class Worker {
2539
env?: Partial<NodeJS.ProcessEnv> | undefined
2640
})
2741
| undefined
42+
/**
43+
* `-1` if not inspectable
44+
*/
45+
debuggerPortOffset: number
46+
/**
47+
* True if `--max-old-space-size` should not be forwarded to the worker.
48+
*/
49+
isolatedMemory: boolean
2850
timeout?: number
2951
onActivity?: () => void
3052
onActivityAbort?: () => void
@@ -34,7 +56,14 @@ export class Worker {
3456
enableWorkerThreads?: boolean
3557
}
3658
) {
37-
let { timeout, onRestart, logger = console, ...farmOptions } = options
59+
let {
60+
timeout,
61+
onRestart,
62+
logger = console,
63+
debuggerPortOffset,
64+
isolatedMemory,
65+
...farmOptions
66+
} = options
3867

3968
let restartPromise: Promise<typeof RESTARTED>
4069
let resolveRestartPromise: (arg: typeof RESTARTED) => void
@@ -47,6 +76,26 @@ export class Worker {
4776
this.close()
4877
})
4978

79+
const nodeOptions = getParsedNodeOptionsWithoutInspect()
80+
81+
if (debuggerPortOffset !== -1) {
82+
const nodeDebugType = getNodeDebugType()
83+
if (nodeDebugType) {
84+
const address = getParsedDebugAddress()
85+
address.port =
86+
address.port +
87+
// current process runs on `address.port`
88+
1 +
89+
debuggerPortOffset
90+
nodeOptions[nodeDebugType] = formatDebugAddress(address)
91+
}
92+
}
93+
94+
if (isolatedMemory) {
95+
delete nodeOptions['max-old-space-size']
96+
delete nodeOptions['max_old_space_size']
97+
}
98+
5099
const createWorker = () => {
51100
this._worker = new JestWorker(workerPath, {
52101
...farmOptions,
@@ -56,6 +105,7 @@ export class Worker {
56105
...process.env,
57106
...((farmOptions.forkOptions?.env || {}) as any),
58107
IS_NEXT_WORKER: 'true',
108+
NODE_OPTIONS: formatNodeOptions(nodeOptions),
59109
} as any,
60110
},
61111
maxRetries: 0,

test/e2e/app-dir/server-source-maps/fixtures/default/next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44
const nextConfig = {
55
experimental: {
6+
cpus: 1,
67
dynamicIO: true,
78
serverSourceMaps: true,
89
},

0 commit comments

Comments
 (0)