File Descriptors & ulimit
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The tools listed (ulimit, lsof, datadog, prometheus-node-exporter) can catch FD exhaustion, but only if monitoring is explicitly set up — a common_mistake noted is 'No monitoring of FD usage — exhaustion typically happens gradually, not suddenly.' The error typically surfaces only under load in production, not during development or standard CI.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix involves changes at multiple layers: sysctl.conf (kernel), systemd unit files (LimitNOFILE), and application-level code changes to close file handles properly. This is not a single-line patch but also not a full architectural rework — it spans OS configuration and potentially multiple long-running scripts.
Closest to 'persistent productivity tax' (b5). The concept applies to both web and CLI contexts in PHP, meaning any long-running PHP process (FPM workers, queue workers) must account for FD management. It is not purely localised (b3) because it affects multiple runtime contexts and requires ongoing operational discipline, but it does not reshape the entire system architecture (b7).
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The misconception field states that 'too many open files' errors are assumed to mean legitimately high concurrency, when they are commonly caused by FD leaks — not closing handles. Additionally, a documented gotcha is that systemd ignores /etc/security/limits.conf, so developers who correctly set ulimit in the OS profile are surprised it has no effect on services — this contradicts the expected behaviour of OS-level limits.
Also Known As
TL;DR
Explanation
Every open file, socket, pipe, or device is a file descriptor (FD). FD 0 = stdin, 1 = stdout, 2 = stderr. The soft limit (ulimit -n) is the default per-process maximum (typically 1024); the hard limit is the kernel maximum. Symptoms of FD exhaustion: 'Too many open files' errors, inability to accept new connections, failed file opens. For PHP web servers and queue workers: increase limits in /etc/security/limits.conf (system) or in the systemd service file (LimitNOFILE). Monitoring: /proc/PID/fd shows open FDs for a process; lsof -p PID lists them with paths.
Common Misconception
Why It Matters
Common Mistakes
- Not closing file handles in PHP after use — FDs accumulate over time in long-running scripts.
- Low ulimit for high-concurrency services — Nginx default (1024) is insufficient for busy servers.
- Not setting LimitNOFILE in systemd service files — systemd ignores /etc/security/limits.conf for services.
- No monitoring of FD usage — exhaustion typically happens gradually, not suddenly.
Code Examples
// PHP FD leak — file handle not closed:
function processLogs(array $paths): void {
foreach ($paths as $path) {
$handle = fopen($path, 'r'); // Opens FD
while (!feof($handle)) processLine(fgets($handle));
// Missing: fclose($handle) — FD leaked!
}
// 1000 log files = 1000 leaked FDs — ulimit hit
}
// Close handles explicitly:
function processLogs(array $paths): void {
foreach ($paths as $path) {
$handle = fopen($path, 'r');
try {
while (!feof($handle)) processLine(fgets($handle));
} finally {
fclose($handle); // Always closed
}
}
}
# Increase FD limit for PHP-FPM in systemd:
# /etc/systemd/system/php8.3-fpm.service.d/override.conf:
[Service]
LimitNOFILE=65536
# Check current FD usage:
lsof -p $(pgrep php-fpm | head -1) | wc -l