Skip to content

[devtools] Add a very minimal API for restarting the dev server #79265

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

Merged
merged 1 commit into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ServerResponse, IncomingMessage } from 'http'
import type { Telemetry } from '../../../../telemetry/storage'
import { RESTART_EXIT_CODE } from '../../../../server/lib/utils'
import { middlewareResponse } from './middleware-response'

const EVENT_DEV_OVERLAY_RESTART_SERVER = 'DEV_OVERLAY_RESTART_SERVER'

export function getRestartDevServerMiddleware(telemetry: Telemetry) {
return async function (
req: IncomingMessage,
res: ServerResponse,
next: () => void
): Promise<void> {
const { pathname } = new URL(`http://n${req.url}`)
if (pathname !== '/__nextjs_restart_dev' || req.method !== 'POST') {
return next()
}

telemetry.record({
eventName: EVENT_DEV_OVERLAY_RESTART_SERVER,
payload: {},
})

// TODO: Use flushDetached
await telemetry.flush()
Comment on lines +24 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's flush detached? Sounds like we do want to ensure everything is flushed before we exit and detached makes it sound like we wouldn't care if it doesn't flush?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telemetry is best-effort. We shouldn't generate an error if we fail to submit it. However, this is different than "we wouldn't care if it doesn't flush".

Normally when the next dev server exits, we don't want to block on submitting telemetry (the network request can be slow), so we write telemetry to a file and spawn a detached process that we can continue submitting telemetry in the background and exit the main process quickly.

This sort of thing is a common trick for CLIs that have slow exits, e.g.: https://github.com/rui314/mold/blob/main/docs/design.md#details

If we mmap a lot of files, _exit(2) is not instantaneous but takes a few hundred milliseconds because the kernel has to clean up a lot of resources. As a workaround, we should organize the linker command as two processes; the first process forks the second process, and the second process does the actual work. As soon as the second process writes a result file to a filesystem, it notifies the first process, and the first process exits. The second process can take time to exit, because it is not an interactive process.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So flush detached actually means it's more likely that we submit telemetry. Thanks for clarifying.


// do this async to try to give the response a chance to send
// it's not really important if it doesn't though
setTimeout(() => {
process.exit(RESTART_EXIT_CODE)
}, 0)

return middlewareResponse.noContent(res)
}
}
2 changes: 2 additions & 0 deletions packages/next/src/server/dev/hot-reloader-turbopack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import {
import { getDevOverlayFontMiddleware } from '../../client/components/react-dev-overlay/font/get-dev-overlay-font-middleware'
import { devIndicatorServerState } from './dev-indicator-server-state'
import { getDisableDevIndicatorMiddleware } from './dev-indicator-middleware'
import { getRestartDevServerMiddleware } from '../../client/components/react-dev-overlay/server/restart-dev-server-middleware'
// import { getSupportedBrowsers } from '../../build/utils'

const wsServer = new ws.Server({ noServer: true })
Expand Down Expand Up @@ -649,6 +650,7 @@ export async function createHotReloaderTurbopack(
getNextErrorFeedbackMiddleware(opts.telemetry),
getDevOverlayFontMiddleware(),
getDisableDevIndicatorMiddleware(),
getRestartDevServerMiddleware(opts.telemetry),
]

const versionInfoPromise = getVersionInfo()
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/server/dev/hot-reloader-webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import { getNextErrorFeedbackMiddleware } from '../../client/components/react-de
import { getDevOverlayFontMiddleware } from '../../client/components/react-dev-overlay/font/get-dev-overlay-font-middleware'
import { getDisableDevIndicatorMiddleware } from './dev-indicator-middleware'
import getWebpackBundler from '../../shared/lib/get-webpack-bundler'
import { getRestartDevServerMiddleware } from '../../client/components/react-dev-overlay/server/restart-dev-server-middleware'

const MILLISECONDS_IN_NANOSECOND = BigInt(1_000_000)

Expand Down Expand Up @@ -1569,6 +1570,7 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface {
getNextErrorFeedbackMiddleware(this.telemetry),
getDevOverlayFontMiddleware(),
getDisableDevIndicatorMiddleware(),
getRestartDevServerMiddleware(this.telemetry),
]
}

Expand Down
Loading