History

fix(server): tolerate the missing directory fsync on Windows (#9569)

Julius Marminge committed 2 months ago

Commit f083f521 parent c251e412 files · +17 −9Browse files

Unified · Split

2 files
  1. apps/server/src/cloud/bootService.ts +9 −2
  2. apps/server/src/serviceLauncher.ts +8 −7
apps/server/src/cloud/bootService.ts+9−2
@@ -555,9 +555,16 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { · 554 unmodified lines
555 yield* fs.makeDirectory(directory, { recursive: true });555 yield* fs.makeDirectory(directory, { recursive: true });
556 const tempPath = yield* fs.makeTempFileScoped({ directory, prefix: ".service-write-" });556 const tempPath = yield* fs.makeTempFileScoped({ directory, prefix: ".service-write-" });
557 yield* fs.writeFileString(tempPath, contents, { mode: 0o600 });557 yield* fs.writeFileString(tempPath, contents, { mode: 0o600 });
558 yield* (yield* fs.open(tempPath, { flag: "r" })).sync;558 // Opened read-write: Windows refuses to flush a handle without write access.
559 yield* (yield* fs.open(tempPath, { flag: "r+" })).sync;
559 yield* fs.rename(tempPath, filePath);560 yield* fs.rename(tempPath, filePath);
560 yield* (yield* fs.open(directory, { flag: "r" })).sync;561 // Windows has no directory fsync (EPERM); NTFS journals the rename.
562 yield* (yield* fs.open(directory, { flag: "r" })).sync.pipe(
563 Effect.catchIf(
564 (error) => (error.reason.cause as NodeJS.ErrnoException | undefined)?.code === "EPERM",
565 () => Effect.void,
566 ),
567 );
561 }),568 }),
562 ).pipe(Effect.mapError((cause) => new BootServiceInstallError({ cause })));569 ).pipe(Effect.mapError((cause) => new BootServiceInstallError({ cause })));
563 const plan: BootServicePlan = {570 const plan: BootServicePlan = {
apps/server/src/serviceLauncher.ts+8−7
@@ -70,8 +70,9 @@ async function pathExists(target: string): Promise<boolean> { · 69 unmodified lines
70 }70 }
71}71}
72 72
73// Opened read-write: Windows refuses to flush a handle without write access.
73async function syncFile(filePath: string): Promise<void> {74async function syncFile(filePath: string): Promise<void> {
74 const handle = await NodeFSP.open(filePath, "r");75 const handle = await NodeFSP.open(filePath, "r+");
75 try {76 try {
76 await handle.sync();77 await handle.sync();
77 } finally {78 } finally {
@@ -79,10 +80,15 @@ async function syncFile(filePath: string): Promise<void> { · 1 unmodified line
79 }80 }
80}81}
81 82
83// Flushes a directory entry so a rename into it survives power loss. Windows
84// has no directory fsync: the handle opens but sync fails with EPERM, and
85// NTFS journals the rename on its own.
82async function syncDirectory(directory: string): Promise<void> {86async function syncDirectory(directory: string): Promise<void> {
83 const handle = await NodeFSP.open(directory, "r");87 const handle = await NodeFSP.open(directory, "r");
84 try {88 try {
85 await handle.sync();89 await handle.sync();
90 } catch (error) {
91 if ((error as NodeJS.ErrnoException).code !== "EPERM") throw error;
86 } finally {92 } finally {
87 await handle.close();93 await handle.close();
88 }94 }
@@ -188,12 +194,7 @@ export async function writeServiceState(filePath: string, state: ServiceState): · 99 unmodified lines
188 await handle.close();194 await handle.close();
189 handle = undefined;195 handle = undefined;
190 await NodeFSP.rename(tempPath, filePath);196 await NodeFSP.rename(tempPath, filePath);
191 const directoryHandle = await NodeFSP.open(directory, "r");197 await syncDirectory(directory);
192 try {
193 await directoryHandle.sync();
194 } finally {
195 await directoryHandle.close();
196 }
197 } finally {198 } finally {
198 await handle?.close().catch(() => undefined);199 await handle?.close().catch(() => undefined);
199 await NodeFSP.rm(tempPath, { force: true }).catch(() => undefined);200 await NodeFSP.rm(tempPath, { force: true }).catch(() => undefined);