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

fix: NODE_OPTIONS='--inspect' in next dev for debugging #48019

Merged
merged 7 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
1 change: 1 addition & 0 deletions packages/next/src/server/lib/render-server-standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const createServerHandler = async ({
hostname,
minimalMode,
workerType: 'router',
isNodeDebugging: false,
})
didInitialize = true

Expand Down
7 changes: 5 additions & 2 deletions packages/next/src/server/lib/render-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
deleteAppClientCache as _deleteAppClientCache,
} from '../../build/webpack/plugins/nextjs-require-cache-hot-reloader'
import { clearModuleContext as _clearModuleContext } from '../web/sandbox/context'
import { getFreePort } from './server-ipc'
export const WORKER_SELF_EXIT_CODE = 77

const MAXIMUM_HEAP_SIZE_ALLOWED =
Expand Down Expand Up @@ -45,6 +46,7 @@ export async function initialize(opts: {
minimalMode?: boolean
hostname?: string
workerType: 'router' | 'render'
isNodeDebugging: boolean
keepAliveTimeout?: number
}): Promise<NonNullable<typeof result>> {
// if we already setup the server return as we only need to do
Expand Down Expand Up @@ -79,7 +81,7 @@ export async function initialize(opts: {
server.keepAliveTimeout = opts.keepAliveTimeout
}

return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
server.on('error', (err: NodeJS.ErrnoException) => {
console.error(`Invariant: failed to start render worker`, err)
process.exit(1)
Expand Down Expand Up @@ -123,6 +125,7 @@ export async function initialize(opts: {
customServer: false,
httpServer: server,
port: opts.port,
isNodeDebugging: opts.isNodeDebugging,
})

requestHandler = app.getRequestHandler()
Expand All @@ -133,6 +136,6 @@ export async function initialize(opts: {
return reject(err)
}
})
server.listen(0, opts.hostname)
server.listen((await getFreePort()) + 1, opts.hostname)
})
}
31 changes: 23 additions & 8 deletions packages/next/src/server/lib/server-ipc/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type NextServer from '../../next-server'

import { genExecArgv, getNodeOptionsWithoutInspect } from '../utils'
import http from 'http'
import { getNodeOptionsWithoutInspect } from '../utils'
import { deserializeErr, errorToJSON } from '../../render'
import crypto from 'crypto'
import isError from '../../../lib/is-error'
import { genRenderExecArgv } from '../worker-utils'

// we can't use process.send as jest-worker relies on
// it already and can cause unexpected message errors
Expand Down Expand Up @@ -78,8 +80,23 @@ export async function createIpcServer(
}
}

export const createWorker = (
serverPort: number,
export const getFreePort = async (): Promise<number> => {
return new Promise((resolve, reject) => {
const server = http.createServer(() => {})
server.listen(0, () => {
const address = server.address()
server.close()

if (address && typeof address === 'object') {
resolve(address.port)
} else {
reject(new Error('invalid address from server: ' + address?.toString()))
}
})
})
}

export const createWorker = async (
ipcPort: number,
ipcValidationKey: string,
isNodeDebugging: boolean | 'brk' | undefined,
Expand All @@ -88,6 +105,7 @@ export const createWorker = (
) => {
const { initialEnv } = require('@next/env') as typeof import('@next/env')
const { Worker } = require('next/dist/compiled/jest-worker')

const worker = new Worker(require.resolve('../render-server'), {
numWorkers: 1,
// TODO: do we want to allow more than 10 OOM restarts?
Expand All @@ -97,7 +115,7 @@ export const createWorker = (
FORCE_COLOR: '1',
...initialEnv,
// we don't pass down NODE_OPTIONS as it can
// extra memory usage
// allow more memory usage than expected
NODE_OPTIONS: getNodeOptionsWithoutInspect()
.replace(/--max-old-space-size=[\d]{1,}/, '')
.trim(),
Expand All @@ -115,10 +133,7 @@ export const createWorker = (
}
: {}),
},
execArgv: genExecArgv(
isNodeDebugging === undefined ? false : isNodeDebugging,
(serverPort || 0) + 1
),
execArgv: isNodeDebugging ? genRenderExecArgv(type) : undefined,
},
exposedMethods: [
'initialize',
Expand Down
12 changes: 10 additions & 2 deletions packages/next/src/server/lib/start-server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import http from 'http'
import { isIPv6 } from 'net'
import * as Log from '../../build/output/log'
import { getNodeOptionsWithoutInspect } from './utils'
import type { IncomingMessage, ServerResponse } from 'http'
import type { ChildProcess } from 'child_process'
import { normalizeRepeatedSlashes } from '../../shared/lib/utils'
import { initialEnv } from '@next/env'
import { genExecArgv, getNodeOptionsWithoutInspect } from './utils'
import { getFreePort } from './server-ipc'

export interface StartServerOptions {
dir: string
Expand Down Expand Up @@ -184,11 +185,17 @@ export async function startServer({
// TODO: do we want to allow more than 10 OOM restarts?
maxRetries: 10,
forkOptions: {
execArgv: isNodeDebugging
? genExecArgv(
isNodeDebugging === undefined ? false : isNodeDebugging,
await getFreePort()
)
: undefined,
env: {
FORCE_COLOR: '1',
...((initialEnv || process.env) as typeof process.env),
PORT: port + '',
NODE_OPTIONS: getNodeOptionsWithoutInspect().trim(),
NODE_OPTIONS: getNodeOptionsWithoutInspect(),
},
},
exposedMethods: ['initialize'],
Expand Down Expand Up @@ -235,6 +242,7 @@ export async function startServer({
hostname,
dev: !!isDev,
workerType: 'router',
isNodeDebugging: !!isNodeDebugging,
keepAliveTimeout,
})
didInitialize = true
Expand Down
18 changes: 9 additions & 9 deletions packages/next/src/server/lib/worker-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Log from '../../build/output/log'

export const genRenderExecArgv = () => {
export const genRenderExecArgv = (type: 'pages' | 'app') => {
const isDebugging =
process.execArgv.some((localArg) => localArg.startsWith('--inspect')) ||
process.env.NODE_OPTIONS?.match?.(/--inspect(=\S+)?( |$)/)
Expand All @@ -9,7 +9,7 @@ export const genRenderExecArgv = () => {
process.execArgv.some((localArg) => localArg.startsWith('--inspect-brk')) ||
process.env.NODE_OPTIONS?.match?.(/--inspect-brk(=\S+)?( |$)/)

const debugPort = (() => {
let debugPort = (() => {
const debugPortStr =
process.execArgv
.find(
Expand All @@ -22,13 +22,15 @@ export const genRenderExecArgv = () => {
return debugPortStr ? parseInt(debugPortStr, 10) : 9229
})()

debugPort += type === 'pages' ? 2 : 3

if (isDebugging || isDebuggingWithBrk) {
Log.warn(
Log.info(
`the --inspect${
isDebuggingWithBrk ? '-brk' : ''
} option was detected, the Next.js server should be inspected at port ${
debugPort + 1
}.`
} option was detected, the Next.js server${
type === 'pages' ? ' for pages' : type === 'app' ? ' for app' : ''
} should be inspected at port ${debugPort}.`
)
}
const execArgv = process.execArgv.filter((localArg) => {
Expand All @@ -38,9 +40,7 @@ export const genRenderExecArgv = () => {
})

if (isDebugging || isDebuggingWithBrk) {
execArgv.push(
`--inspect${isDebuggingWithBrk ? '-brk' : ''}=${debugPort + 1}`
)
execArgv.push(`--inspect${isDebuggingWithBrk ? '-brk' : ''}=${debugPort}`)
}

return execArgv
Expand Down
7 changes: 3 additions & 4 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export default class NextNodeServer extends BaseServer {
hostname: this.hostname,
minimalMode: this.minimalMode,
dev: !!options.dev,
isNodeDebugging: !!options.isNodeDebugging,
}
const { createWorker, createIpcServer } =
require('./lib/server-ipc') as typeof import('./lib/server-ipc')
Expand All @@ -281,17 +282,15 @@ export default class NextNodeServer extends BaseServer {
this.renderWorkers = {}
const { ipcPort, ipcValidationKey } = await createIpcServer(this)
if (this.hasAppDir) {
this.renderWorkers.app = createWorker(
this.port || 0,
this.renderWorkers.app = await createWorker(
ipcPort,
ipcValidationKey,
options.isNodeDebugging,
'app',
this.nextConfig.experimental.serverActions
)
}
this.renderWorkers.pages = createWorker(
this.port || 0,
this.renderWorkers.pages = await createWorker(
ipcPort,
ipcValidationKey,
options.isNodeDebugging,
Expand Down