The EAGAIN error indicates that a requested I/O operation cannot be completed immediately because the resource is temporarily unavailable. This is a recoverable error that signals Node.js to retry the operation later rather than failing permanently.
The EAGAIN (Error AGAIn) error is a POSIX system-level error code that occurs when attempting to perform I/O operations on resources that are temporarily unavailable. In Node.js, this error commonly appears during file operations, reading from stdin, spawning child processes, or working with non-blocking I/O operations. Unlike permanent errors, EAGAIN indicates a transient condition - the operation failed not because of incorrect parameters or permissions, but because the system temporarily lacks the resources to complete it. This typically occurs when working with non-blocking file descriptors where data isn't immediately available for reading, or when system resource limits (like open file descriptors or process counts) are temporarily exhausted. The error is particularly common in high-throughput applications that perform many concurrent I/O operations, spawn numerous child processes, or work with streams and file descriptors extensively. Understanding that EAGAIN is temporary and recoverable is crucial for implementing proper error handling strategies.
Verify your current ulimit settings to identify if you're hitting system constraints:
# Check maximum open file descriptors
ulimit -n
# Check maximum user processes
ulimit -u
# View all limits
ulimit -aOn Linux, you can also check current file descriptor usage:
# Count open file descriptors for current process
ls /proc/$$/fd | wc -l
# For a specific Node.js process
ls /proc/<PID>/fd | wc -lHandle EAGAIN errors gracefully by retrying the operation after a short delay:
const fs = require('fs');
function readWithRetry(fd, buffer, offset, length, position, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
return fs.readSync(fd, buffer, offset, length, position);
} catch (error) {
if (error.code === 'EAGAIN' && retries < maxRetries - 1) {
retries++;
// Brief delay before retry
const start = Date.now();
while (Date.now() - start < 10) {} // 10ms blocking wait
continue;
}
throw error;
}
}
}For async operations, use a more elegant retry approach:
async function readWithRetryAsync(fd, buffer, offset, length, position, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fs.promises.read(fd, buffer, offset, length, position);
} catch (error) {
if (error.code === 'EAGAIN' && i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 10));
continue;
}
throw error;
}
}
}Temporarily increase limits for your current session:
# Increase file descriptor limit
ulimit -n 4096
# Increase process limit
ulimit -u 2048For permanent changes on Linux, edit /etc/security/limits.conf:
* soft nofile 4096
* hard nofile 8192
* soft nproc 2048
* hard nproc 4096For systemd services, add to your service file:
[Service]
LimitNOFILE=4096
LimitNPROC=2048On macOS, edit /Library/LaunchDaemons/limit.maxfiles.plist or use launchctl.
Ensure all file descriptors and child processes are properly closed:
const fs = require('fs');
const { spawn } = require('child_process');
// Always close file descriptors
const fd = fs.openSync('file.txt', 'r');
try {
// perform operations
} finally {
fs.closeSync(fd);
}
// Use graceful-fs for automatic retry handling
const gracefulFs = require('graceful-fs');
gracefulFs.readFile('file.txt', 'utf8', (err, data) => {
// graceful-fs automatically retries on EAGAIN
});
// Properly manage child processes
const child = spawn('command', ['arg1', 'arg2']);
child.on('exit', (code) => {
console.log(`Process exited with code ${code}`);
});
child.on('error', (error) => {
if (error.code === 'EAGAIN') {
console.error('Resource temporarily unavailable, retry later');
}
});
// Limit concurrent child processes
const MAX_CONCURRENT = 10;
let activeProcesses = 0;
const queue = [];
function spawnWithLimit(command, args) {
if (activeProcesses < MAX_CONCURRENT) {
activeProcesses++;
const child = spawn(command, args);
child.on('exit', () => {
activeProcesses--;
if (queue.length > 0) {
const next = queue.shift();
spawnWithLimit(next.command, next.args);
}
});
return child;
} else {
queue.push({ command, args });
}
}Install and use graceful-fs, which automatically handles EAGAIN and EMFILE errors:
npm install graceful-fsReplace native fs with graceful-fs:
// Instead of:
// const fs = require('fs');
// Use:
const fs = require('graceful-fs');
// All fs operations now automatically retry on EAGAIN
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error after retries:', err);
return;
}
console.log(data);
});Or patch fs globally at application startup:
const gracefulFs = require('graceful-fs');
gracefulFs.gracefulify(require('fs'));Understanding Non-Blocking I/O: EAGAIN is primarily associated with non-blocking file descriptors. When you read from a non-blocking fd and no data is available, the system returns EAGAIN instead of blocking the thread. Node.js internally handles this for streams and async operations, but sync operations or direct file descriptor access can expose this error.
Process Spawning Limits: When spawning child processes, EAGAIN can occur if the system hits the per-user process limit. On Linux, this is typically controlled by RLIMIT_NPROC. Applications that spawn many short-lived processes (like build tools or test runners) should implement process pooling to avoid this limit.
Stdin Reading Edge Cases: Reading from stdin using fs.readSync(0) can produce EAGAIN if stdin is non-blocking and no input is available. This is especially common when using stdin in TTY mode or after other libraries have modified stdin's flags. Consider using readline or process.stdin.read() which handle this automatically.
Container Environments: In Docker containers or Kubernetes pods, default ulimits may be lower than the host system. Always configure appropriate limits in your container specifications:
# Kubernetes example
spec:
containers:
- name: app
resources:
limits:
cpu: "1"
memory: "512Mi"
securityContext:
allowPrivilegeEscalation: false
capabilities:
add: ["SYS_RESOURCE"]File Descriptor Monitoring: For production applications, monitor file descriptor usage using process metrics. Many APM tools can track this, or use custom monitoring:
setInterval(() => {
const used = process._getActiveHandles().length + process._getActiveRequests().length;
console.log(`Active handles: ${used}`);
}, 60000);Kernel Parameters: On Linux, system-wide limits are set in /proc/sys/fs/file-max (total file descriptors) and /proc/sys/kernel/pid_max (total processes). These kernel parameters may need adjustment on high-load servers.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams