Skip to content
Optimal Lab

Operating systems · interactive

Blocking I/O vs epoll

A server has five open connections. Each client sends its request at a different moment. How quickly each gets served depends on how the server waits for data.

  • conn 12 ms
  • conn 254 ms
  • conn 346 ms
  • conn 42 ms
  • conn 554 ms

Line: request arrives. Bar: time until the server handles it.

1 thread · ~8 MB of stacks

The thread blocks reading connection 1, which sends nothing until 60 ms. Connections 2, 3 and 5 sit waiting with data ready: up to 54 ms.

Server model

Step 1 of 4

  1. 1

    A blocking read waits

    read() on a socket with no data puts the thread to sleep until data arrives. Fine for one connection.

    One slow client stalls everyone.

  2. 2

    Thread per connection

    Simple code, and it works for hundreds. Each thread costs memory and scheduling, so it falls over at tens of thousands.

    The C10K problem.

  3. 3

    epoll / kqueue

    Register sockets once, then epoll_wait returns only those with data. Non-blocking reads never stall.

    epoll_wait(fd, events, 64, -1)

  4. 4

    The event loop is built on this

    Node.js (libuv), Nginx, Redis and Go's netpoller all sit on epoll. Keep each handler short, or the one thread blocks everyone again.

    io_uring is the newer Linux interface.

In production

Tools and technologies

Every high-concurrency server waits on sockets this way.

Not tied to one language — use these names when you search docs or design a system.