fix(shared): tolerate EPERM during fsync in writeFileAtomically
Replaces fsyncSync(tempFileDescriptor) with tolerantFsyncSync, allowing EPERM/EACCES/ENOTSUP/EINVAL during fsync while still propagating real errors. Adds an optional deps.fsyncSync injection point used solely by the new EPERM tolerance regression tests. Without this fix, plugin startup itself can fail on synced folders because writeFileAtomically is used by config migrations and posthog activity state — the same EPERM-on-fsync failure pattern reported for team_create.
This commit is contained in:
@@ -51,4 +51,39 @@ describe("writeFileAtomically", () => {
|
||||
// when/then
|
||||
expect(() => writeFileAtomically(filePath, "content")).toThrow()
|
||||
})
|
||||
|
||||
it("#given fsync fails with EPERM (synced folder) #when writeFileAtomically called #then write succeeds", () => {
|
||||
// given
|
||||
const filePath = join(testDir, "synced-folder.txt")
|
||||
const content = "content from a synced folder where fsync is rejected"
|
||||
|
||||
// when
|
||||
writeFileAtomically(filePath, content, {
|
||||
fsyncSync: () => {
|
||||
const error = new Error("EPERM: operation not permitted, fsync") as NodeJS.ErrnoException
|
||||
error.code = "EPERM"
|
||||
throw error
|
||||
},
|
||||
})
|
||||
|
||||
// then
|
||||
expect(existsSync(filePath)).toBe(true)
|
||||
expect(readFileSync(filePath, "utf-8")).toBe(content)
|
||||
})
|
||||
|
||||
it("#given fsync fails with EIO (real I/O error) #when writeFileAtomically called #then propagates the error", () => {
|
||||
// given
|
||||
const filePath = join(testDir, "io-error.txt")
|
||||
|
||||
// when/then
|
||||
expect(() =>
|
||||
writeFileAtomically(filePath, "content", {
|
||||
fsyncSync: () => {
|
||||
const error = new Error("EIO: input/output error") as NodeJS.ErrnoException
|
||||
error.code = "EIO"
|
||||
throw error
|
||||
},
|
||||
}),
|
||||
).toThrow("EIO")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs"
|
||||
import {
|
||||
closeSync,
|
||||
type fsyncSync as FsyncSync,
|
||||
openSync,
|
||||
renameSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs"
|
||||
|
||||
export function writeFileAtomically(filePath: string, content: string): void {
|
||||
const tempPath = `${filePath}.tmp`
|
||||
writeFileSync(tempPath, content, "utf-8")
|
||||
import { tolerantFsyncSync } from "./tolerant-fsync"
|
||||
|
||||
export function writeFileAtomically(
|
||||
filePath: string,
|
||||
content: string,
|
||||
deps: { fsyncSync?: typeof FsyncSync } = {},
|
||||
): void {
|
||||
const tempPath = `${filePath}.tmp`
|
||||
writeFileSync(tempPath, content, "utf-8")
|
||||
const tempFileDescriptor = openSync(tempPath, "r")
|
||||
try {
|
||||
fsyncSync(tempFileDescriptor)
|
||||
tolerantFsyncSync(tempFileDescriptor, `writeFileAtomically:${filePath}`, deps.fsyncSync)
|
||||
} finally {
|
||||
closeSync(tempFileDescriptor)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user