Operating systems and concurrency
File descriptors
The small integer your program holds for each open file, socket or pipe, and why "too many open files" is either a low limit or a leak.
A file descriptor (fd) is a small integer that stands for an open file, socket or pipe. Your code never holds the file itself. It holds a number, and the kernel looks that number up in a per-process table to find the real object. Every network connection your service accepts uses one.
Concept
System call: a request your program makes to the kernel to do something only the kernel may do, such as open a file or send a packet. Opening a file, open(), is a system call that returns an fd.
The big idea
Unix calls this "everything is a file". The useful part is that the same read() and write() calls work on very different things. Each row below is created by a different call, but all of them are accessed through an fd.
| Object | Created by |
|---|---|
| Regular file | open() |
| TCP or UDP socket | socket(), accept() |
| Pipe | pipe() |
| Event notification | epoll_create(), eventfd(), timerfd_create() |
| Directory | open() with O_DIRECTORY |
Every process starts with three fds: 0 (stdin), 1 (stdout) and 2 (stderr). This is why ls > out.txt works. The shell opens out.txt, gets an fd, and points fd 1 at it before the program starts.
Walking through the limit
Each process has a cap on open fds, set by ulimit -n. The default soft limit is often 1024. Here is a server that holds 1,000 client connections.
client connections = 1,000
stdin, stdout, stderr = 3
-----
used = 1,003
soft limit = 1,024
room left for logs and database sockets = 21Only 21 fds are left for everything else. A few more connections, or a bigger database pool, and accept() fails with EMFILE: Too many open files. The fix depends on the cause. Check the limit and the count:
ulimit -n # soft limit for this shell
cat /proc/<pid>/limits | grep "open files"
ls /proc/<pid>/fd | wc -l # how many it has open right nowTwo situations look the same in the logs:
- The limit is too low for real load. Raise it in your service manager, such as
LimitNOFILE=in systemd. Values like 65,536 are normal for servers. - You are leaking fds. The count in
/proc/<pid>/fdclimbs forever. Raising the limit only delays the outage.
Tip
Graph open fds per process next to the limit. Prometheus client libraries export process_open_fds and process_max_fds. A line that only goes up is a leak.
Common mistakes
- Raising the limit to fix a leak. A bigger limit only moves the outage later. Check whether the count keeps climbing.
- Opening files in a loop without closing them. Use
within Python ordeferin Go so the fd closes on every path. - Forgetting that sockets count. HTTP client response bodies that are never closed hold their connections open.
Check yourself
- What does the integer 5 in a process mean? It is an index into the process's fd table. Entry 5 points to a kernel object, such as a file or socket.
- The fd count climbs forever, even after you double the limit. What is likely? A leak. Find the code that opens without closing.
Key takeaways
- An fd is an integer handle to a kernel object: a file, socket, pipe or event source.
- Stdin, stdout and stderr are fds 0, 1 and 2. Redirection just changes what those point to.
- "Too many open files" is either a low limit or a leak. Graph the count to tell which.
Further learning
Next, read processes and threads for the wider model. The /proc directory is the fastest way to see a live process's fd table, so run ls -l /proc/<pid>/fd on something you own.
Related
Processes, threads and the kernel
What the OS gives every program, and how to see it on a live Linux box. Start here for the process model.
Process lifecycle
How a process is born with fork, takes on a new program with exec, and exits with a code. Why zombies build up when a parent never calls wait.
Linux for engineers
The parts of Linux you touch in production, from shell pipelines and systemd services to permissions, disks, network debugging commands and safe remote access.