fix(fs): retry bounded Windows atomic rename sharing failures

This commit is contained in:
haelyra
2026-10-10 18:58:54 -04:00
parent a0dacd2282
commit 24de70a507
2 changed files with 181 additions and 3 deletions
+25 -3
View File
@@ -4,6 +4,30 @@ const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
function renameAtomic(tempPath, resolvedPath, options) {
const deadline = performance.now() + 750;
let attempts = 0;
let sleeper;
while (true) {
// A sharing violation may outlast the original validation. Recheck both
// the parent and destination ownership before every publication attempt.
if (options.validateParent) options.validateParent();
if (options.beforeRename) options.beforeRename();
try {
fs.renameSync(tempPath, resolvedPath);
return;
} catch (error) {
attempts++;
const remaining = deadline - performance.now();
if (process.platform !== 'win32'
|| !['EPERM', 'EACCES', 'EBUSY'].includes(error.code)
|| attempts >= 21 || remaining <= 0) throw error;
sleeper ||= new Int32Array(new SharedArrayBuffer(4));
Atomics.wait(sleeper, 0, 0, Math.min(25, remaining));
}
}
}
function writeFileAtomic(filePath, content, options = {}) {
const resolvedPath = path.resolve(filePath);
const parentDir = path.dirname(resolvedPath);
@@ -25,9 +49,7 @@ function writeFileAtomic(filePath, content, options = {}) {
fs.fsyncSync(descriptor);
fs.closeSync(descriptor);
descriptor = undefined;
if (options.validateParent) options.validateParent();
if (options.beforeRename) options.beforeRename();
fs.renameSync(tempPath, resolvedPath);
renameAtomic(tempPath, resolvedPath, options);
} catch (error) {
if (descriptor !== undefined) {
fs.closeSync(descriptor);