Event Loops, Async I/O and Backpressure
Event-driven servers multiplex many connections onto few threads by waiting for readiness (epoll, kqueue) or completion (IOCP, io_uring) and running short, non-blocking handlers. They only scale if nothing blocks the loop, buffers are bounded, and cancellation and timeouts flow through the whole call tree.
Key points
- 1
Thread-per-connection pays stack memory and context switches for every idle connection; event loops spend work only on ready sockets (the C10K problem).
- 2
Readiness APIs (select, poll, epoll, kqueue) say an operation can start; completion APIs (IOCP, io_uring) perform it and report the result. Reactor vs proactor.
- 3
Edge-triggered epoll needs non-blocking descriptors and draining until EAGAIN; level-triggered EPOLLOUT must be enabled only while output is queued.
- 4
A loop runs one callback at a time: CPU work, synchronous DNS or file I/O inside a handler stalls every client. Offload it to a bounded worker pool.
- 5
async/await compiles to resumable state machines; it gives concurrency at await points, not parallelism, and async-ness spreads to callers (coloured functions).
- 6
Backpressure keeps memory bounded: bounded queues, pausing reads, TCP flow control, and pull-based request(n) protocols make the slowest stage set the pace.
- 7
Structured concurrency and deadline propagation make task lifetimes, errors and cancellation explicit, so shutdown can drain work and failures are never lost.
Common traps
Marking CPU-bound code async does not make it run in parallel; it still blocks the loop until it awaits.
Cancelling an await on a thread-pool call does not stop the thread; the blocking work keeps running and keeps its side effects.
A non-blocking send() can accept only part of the data; unsent bytes must be buffered and retried when the socket becomes writable.
Read the source
Test yourself on Event Loops, Async I/O and Backpressure
Ten questions, with the answer and explanation after each one.