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
555555 yield* fs.makeDirectory(directory, { recursive: true });
556556 const tempPath = yield* fs.makeTempFileScoped({ directory, prefix: ".service-write-" });
557557 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;
559560 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+ );
561568 }),
562569 ).pipe(Effect.mapError((cause) => new BootServiceInstallError({ cause })));
563570 const plan: BootServicePlan = {
apps/server/src/serviceLauncher.ts+8−7
@@ -70,8 +70,9 @@ async function pathExists(target: string): Promise<boolean> { · 69 unmodified lines
7070 }
7171 }
7272
73+// Opened read-write: Windows refuses to flush a handle without write access.
7374 async function syncFile(filePath: string): Promise<void> {
74− const handle = await NodeFSP.open(filePath, "r");
75+ const handle = await NodeFSP.open(filePath, "r+");
7576 try {
7677 await handle.sync();
7778 } finally {
@@ -79,10 +80,15 @@ async function syncFile(filePath: string): Promise<void> { · 1 unmodified line
7980 }
8081 }
8182
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.
8286 async function syncDirectory(directory: string): Promise<void> {
8387 const handle = await NodeFSP.open(directory, "r");
8488 try {
8589 await handle.sync();
90+ } catch (error) {
91+ if ((error as NodeJS.ErrnoException).code !== "EPERM") throw error;
8692 } finally {
8793 await handle.close();
8894 }
@@ -188,12 +194,7 @@ export async function writeServiceState(filePath: string, state: ServiceState): · 99 unmodified lines
188194 await handle.close();
189195 handle = undefined;
190196 await NodeFSP.rename(tempPath, filePath);
191− const directoryHandle = await NodeFSP.open(directory, "r");
192− try {
193− await directoryHandle.sync();
194− } finally {
195− await directoryHandle.close();
196− }
197+ await syncDirectory(directory);
197198 } finally {
198199 await handle?.close().catch(() => undefined);
199200 await NodeFSP.rm(tempPath, { force: true }).catch(() => undefined);