Course contentsShow
← Node.js
Lesson 258 of 1,9803. Asynchronous Patterns and Flow ControlFree lesson

Managing Race Conditions in File I/O

You will use file locking and sequential promise chaining to prevent corrupted state during concurrent file writes.

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.