Readiness APIs: epoll and kqueue
Readiness APIs report which file descriptors are ready for I/O without blocking, letting one thread supervise thousands of sockets — the kernel foundation of event-driven servers.
Definition
epoll (Linux) and kqueue (BSD, macOS) register interest in file descriptor events, then return only the ready subset. Compared to poll and select, cost is proportional to active descriptors rather than total registered, enabling single-threaded servers managing hundreds of thousands of connections. They report readiness, not completion — non-blocking reads and writes still follow.
Context and scope
The substrate under Node.js libuv, nginx, Redis, and every event loop. Level-triggered versus edge-triggered modes drive application loop design.
Common misconceptions
- epoll makes I/O asynchronous — it makes it readiness-driven; the read or write is still your synchronous call on a ready descriptor
- Edge-triggered is strictly better — it can miss events unless you drain to EAGAIN; level-triggered is the forgiving default most servers use
- One thread can do all the work — one thread can supervise all the sockets; the work still needs pools or processes to run in parallel
Anti-patterns
- Registering descriptors once with epoll and assuming readiness semantics carry no starvation policy — starved descriptors need fairness awareness
- Reading once per edge-triggered event and stalling on data that arrived in the same burst
- Assuming readiness means the operation succeeds — readiness means try now; the call can still return partial or EAGAIN
Related concepts
Authoritative source
What it is
Blocking I/O dedicates a thread per connection. Readiness APIs invert the model: register every socket once, ask which are ready, and act only on those. The kernel maintains the interest set; the application loops over a compact ready list.
How it works
epoll_ctl adds descriptors to the interest set; epoll_wait returns the ready subset with event flags. Level-triggered repeats readiness until consumed; edge-triggered notifies once per transition, requiring the app to read or write until EAGAIN. Registering with EPOLLONESHOT re-arms per event for multithreaded workers.
When to care
Understanding event loop mechanics, diagnosing starvation under many slow clients, and choosing loop libraries. Most developers consume this through runtimes, but its semantics explain fairness bugs and CPU spin behaviors.
Pitfalls
Edge-triggered without draining to EAGAIN loses events permanently. File descriptor exhaustion and inherited descriptors across forks bite at scale. Multithreaded event loops need ONESHOT or explicit handoff to avoid two workers acting on one socket simultaneously.