Atomic Committer (safe-rename.js)
Understand atomic file system operations, dynamic read/write file lock overrides, and progressive linear backoff retry loops.
Key File Location: ./dinou/core/safe-rename.js💡 Overview
In production servers, updating files on disk can lead to dynamic page crashes. If a background thread overwrites a page's index.html while a user is loading that exact file, the browser may receive a partially written, corrupt document.
Dinou solves this using atomic commits. The compiler writes assets to temporary files first. Once compilation succeeds, it runs safeRename() to replace the old file instantly at the operating system level, ensuring zero downtime.
📊 Retry Backoff Flow
The chart below traces the progressive retry loop triggered when file operations encounter active locks:
🔒 The File Locking Problem
In Windows environments (and some Linux filesystems), when a file is open in a read stream, the OS places a lock on its sector. Trying to rename or delete the file throws EPERM (Operation not permitted) or EBUSY (Resource busy).
The safeRename() utility mitigates this by:
- Targeted Filtering: If the error is a normal filesystem error (e.g.
ENOENT- File not found), it stops and throws immediately. - Progressive Backoff: If the file is locked, it sleeps for a progressive linear duration (
100ms * loop_iteration) to allow active read streams to close before retrying. - Failure Threshold: Aborts and throws after 5 failed retries to prevent infinite execution hangs.
🎯 Integration & Usage (Where is it Used?)
Because safeRename() is the core mechanism that prevents serving partially-written files to active users, it is imported and executed by the three runtime engines in Dinou that perform "in-flight" page updates:
revalidating.js(Background ISR):When a stale page (past its
revalidatetimestamp) is requested, a background task generates new HTML and RSC payloads into.tmpfiles. Once finished, it invokessafeRename()to swap them into the production directory.generating-isg.js(On-Demand ISG):When a user requests a path that was not generated at startup, the server dynamically renders the RSC and HTML files into temp files first, then uses
safeRename()to promote them to static cache files.cache-revalidate.js(On-Demand Revalidation API):When a CMS webhook calls
revalidatePath(), the server forces an immediate compile of the target page into a temporary file and commits it to disk usingsafeRename().
⚙️ Complete Code Walkthrough
Below is the full, complete code of safe-rename.js:
const fs = require("fs").promises;
async function safeRename(oldPath, newPath, retries = 5, delay = 100) {
for (let i = 0; i < retries; i++) {
try {
// 1. Trigger native filesystem rename operation (Atomic)
await fs.rename(oldPath, newPath);
return;
} catch (err) {
// 2. If the error is not EPERM (Lock) or EBUSY (Busy), throw it immediately
if (err.code !== "EPERM" && err.code !== "EBUSY") {
throw err;
}
// 3. If we run out of retries, log error and throw
if (i === retries - 1) {
console.error(
`[ISR] Failed to rename locked file after ${retries} attempts: ${newPath}`
);
throw err;
}
// 4. Calculate delay with progressive linear scale: 100ms, 200ms, 300ms, etc.
await new Promise((resolve) => setTimeout(resolve, delay * (i + 1)));
}
}
}
module.exports = { safeRename };