Skip to content

Commit b02946c

Browse files
authored
use atomic writes to avoid seeing incomplete files (#55424)
### What? ### Why? multiple ensurePage calls are made concurrently and currently there is a race condition causing next.js seeing an empty e. g. middleware-manifest. ### How? Closes WEB-1577
1 parent ad79325 commit b02946c

File tree

1 file changed

+37
-31
lines changed

1 file changed

+37
-31
lines changed

packages/next/src/server/lib/router-utils/setup-dev.ts

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import {
8585
parseStack,
8686
} from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware'
8787
import { BuildManifest } from '../../get-page-files'
88-
import { mkdir, readFile, writeFile } from 'fs/promises'
88+
import { mkdir, readFile, writeFile, rename, unlink } from 'fs/promises'
8989
import { PagesManifest } from '../../../build/webpack/plugins/pages-manifest-plugin'
9090
import { AppBuildManifest } from '../../../build/webpack/plugins/app-build-manifest-plugin'
9191
import { PageNotFoundError } from '../../../shared/lib/utils'
@@ -692,14 +692,31 @@ async function startWatcher(opts: SetupOpts) {
692692
return manifest
693693
}
694694

695+
async function writeFileAtomic(
696+
filePath: string,
697+
content: string
698+
): Promise<void> {
699+
const tempPath = filePath + '.tmp.' + Math.random().toString(36).slice(2)
700+
try {
701+
await writeFile(tempPath, content, 'utf-8')
702+
await rename(tempPath, filePath)
703+
} catch (e) {
704+
try {
705+
await unlink(tempPath)
706+
} catch {
707+
// ignore
708+
}
709+
throw e
710+
}
711+
}
712+
695713
async function writeBuildManifest(): Promise<void> {
696714
const buildManifest = mergeBuildManifests(buildManifests.values())
697715
const buildManifestPath = path.join(distDir, BUILD_MANIFEST)
698716
deleteCache(buildManifestPath)
699-
await writeFile(
717+
await writeFileAtomic(
700718
buildManifestPath,
701-
JSON.stringify(buildManifest, null, 2),
702-
'utf-8'
719+
JSON.stringify(buildManifest, null, 2)
703720
)
704721
const content = {
705722
__rewrites: { afterFiles: [], beforeFiles: [], fallback: [] },
@@ -714,15 +731,13 @@ async function startWatcher(opts: SetupOpts) {
714731
const buildManifestJs = `self.__BUILD_MANIFEST = ${JSON.stringify(
715732
content
716733
)};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()`
717-
await writeFile(
734+
await writeFileAtomic(
718735
path.join(distDir, 'static', 'development', '_buildManifest.js'),
719-
buildManifestJs,
720-
'utf-8'
736+
buildManifestJs
721737
)
722-
await writeFile(
738+
await writeFileAtomic(
723739
path.join(distDir, 'static', 'development', '_ssgManifest.js'),
724-
srcEmptySsgManifest,
725-
'utf-8'
740+
srcEmptySsgManifest
726741
)
727742
}
728743

@@ -737,10 +752,9 @@ async function startWatcher(opts: SetupOpts) {
737752
`fallback-${BUILD_MANIFEST}`
738753
)
739754
deleteCache(fallbackBuildManifestPath)
740-
await writeFile(
755+
await writeFileAtomic(
741756
fallbackBuildManifestPath,
742-
JSON.stringify(fallbackBuildManifest, null, 2),
743-
'utf-8'
757+
JSON.stringify(fallbackBuildManifest, null, 2)
744758
)
745759
}
746760

@@ -750,21 +764,19 @@ async function startWatcher(opts: SetupOpts) {
750764
)
751765
const appBuildManifestPath = path.join(distDir, APP_BUILD_MANIFEST)
752766
deleteCache(appBuildManifestPath)
753-
await writeFile(
767+
await writeFileAtomic(
754768
appBuildManifestPath,
755-
JSON.stringify(appBuildManifest, null, 2),
756-
'utf-8'
769+
JSON.stringify(appBuildManifest, null, 2)
757770
)
758771
}
759772

760773
async function writePagesManifest(): Promise<void> {
761774
const pagesManifest = mergePagesManifests(pagesManifests.values())
762775
const pagesManifestPath = path.join(distDir, 'server', PAGES_MANIFEST)
763776
deleteCache(pagesManifestPath)
764-
await writeFile(
777+
await writeFileAtomic(
765778
pagesManifestPath,
766-
JSON.stringify(pagesManifest, null, 2),
767-
'utf-8'
779+
JSON.stringify(pagesManifest, null, 2)
768780
)
769781
}
770782

@@ -776,10 +788,9 @@ async function startWatcher(opts: SetupOpts) {
776788
APP_PATHS_MANIFEST
777789
)
778790
deleteCache(appPathsManifestPath)
779-
await writeFile(
791+
await writeFileAtomic(
780792
appPathsManifestPath,
781-
JSON.stringify(appPathsManifest, null, 2),
782-
'utf-8'
793+
JSON.stringify(appPathsManifest, null, 2)
783794
)
784795
}
785796

@@ -792,10 +803,9 @@ async function startWatcher(opts: SetupOpts) {
792803
'server/middleware-manifest.json'
793804
)
794805
deleteCache(middlewareManifestPath)
795-
await writeFile(
806+
await writeFileAtomic(
796807
middlewareManifestPath,
797-
JSON.stringify(middlewareManifest, null, 2),
798-
'utf-8'
808+
JSON.stringify(middlewareManifest, null, 2)
799809
)
800810
}
801811

@@ -808,7 +818,7 @@ async function startWatcher(opts: SetupOpts) {
808818
NEXT_FONT_MANIFEST + '.json'
809819
)
810820
deleteCache(fontManifestPath)
811-
await writeFile(
821+
await writeFileAtomic(
812822
fontManifestPath,
813823
JSON.stringify(
814824
{
@@ -829,11 +839,7 @@ async function startWatcher(opts: SetupOpts) {
829839
'react-loadable-manifest.json'
830840
)
831841
deleteCache(loadableManifestPath)
832-
await writeFile(
833-
loadableManifestPath,
834-
JSON.stringify({}, null, 2),
835-
'utf-8'
836-
)
842+
await writeFileAtomic(loadableManifestPath, JSON.stringify({}, null, 2))
837843
}
838844

839845
async function subscribeToHmrEvents(id: string, client: ws) {

0 commit comments

Comments
 (0)