Managing Race Conditions in File I/O
In this lesson, you will learn how to prevent data corruption by ensuring that multiple asynchronous file operations don't interfere with each other when accessing the same file.
When you perform file I/O in Node.js, operations are asynchronous. If your application triggers multiple write operations at the same time, they might attempt to read, modify, and save the same file simultaneously. This creates a race condition: if two operations read the file content at the same time, they both see the same "old" state. When they finish, the second one to write will overwrite the changes made by the first, leading to lost data.
To solve this, we use two main strategies: Sequential Promise Chaining and File Locking.
Sequential Promise Chaining
Instead of triggering writes whenever an event arrives, we maintain a "task queue" using a promise chain. By holding onto a reference to the "previous" operation, we force the next operation to wait until the previous one completes:
let writeQueue = Promise.resolve();
function safeWrite(data) {
// Chain the new task to the end of the previous one
writeQueue = writeQueue.then(async () => {
const currentData = await fs.readFile('data.json', 'utf8');
const updated = JSON.parse(currentData) + data;
await fs.writeFile('data.json', JSON.stringify(updated));
});
return writeQueue;
}
File Locking
If your app runs in multiple processes or needs stronger safety, simple chaining isn't enough because different processes aren't aware of each other's memory. In these cases, we use file locking. A lock creates a temporary "lock file" on your disk. Before any operation begins, it checks for this file. If it exists, the operation waits (or retries) until the file is deleted by the process currently holding the lock. This acts as a traffic light, ensuring only one "driver" occupies the file resource at a time.
By coordinating access through either a sequential queue or an external lock, you ensure the state of your file remains consistent, regardless of how many asynchronous events attempt to modify it at once.
Key Takeaway: To prevent data loss, you must serialize file access by forcing concurrent requests to wait in a queue or by using a filesystem-level lock to ensure exclusive access.