Course contentsShow
← Node.js
Lesson 341 of 1,9804. I/O and File System OperationsFree lesson

File Descriptor Limits and Resource Exhaustion

Optimize file handle usage to avoid exceeding operating system limits during high-throughput I/O.

In this lesson, you will learn how to prevent your Node.js application from crashing by proactively managing the number of open file descriptors (FDs).

The "Too Many Open Files" Problem

Every time you open a file, initiate a network socket, or create a pipe, the operating system assigns a unique integer called a File Descriptor. Your OS enforces a strict limit on how many FDs a single process can hold at once. When your application exceeds this limit—often due to high-throughput I/O or failing to close resources—it will throw an EMFILE error, effectively grinding your I/O operations to a halt.

Optimization through Resource Lifecycle

The most common cause of exhaustion is "leaking" file handles. If you open a file inside a loop or a recursive function but fail to explicitly close it, those descriptors stay "in use" until the garbage collector eventually cleans them up (which is never fast enough under load).

Consider this pattern for high-throughput scenarios:

const fs = require('fs/promises');

async function processFiles(fileList) {
  // DON'T: Open all files simultaneously
  // DO: Process in controlled batches to keep FD usage low
  const batchSize = 10;
  for (let i = 0; i < fileList.length; i += batchSize) {
    const batch = fileList.slice(i, i + batchSize);
    await Promise.all(batch.map(async (path) => {
      let fileHandle;
      try {
        fileHandle = await fs.open(path, 'r');
        // Perform I/O operations...
      } finally {
        // ALWAYS close the handle in a finally block
        if (fileHandle) await fileHandle.close();
      }
    }));
  }
}

Strategic Management

Instead of opening every file needed for a massive operation at once, implement concurrency limiting. By using a pool or a batching strategy, you ensure that the number of active FDs remains well below the system limit (ulimit -n).

Furthermore, always treat file handles like temporary guest residents in your memory; open them only when you are ready to read or write, and ensure they are closed immediately once the task is complete. If you are handling thousands of files, prioritize sequential processing or controlled queues over parallel bursts to ensure your application remains stable under heavy I/O pressure.

Key Takeaway: To avoid EMFILE errors, treat file descriptors as a scarce, finite resource by explicitly closing handles and using concurrency control to cap the number of simultaneous open files.