Skip to content

epoll

epoll is Linux's scalable file descriptor event notification mechanism. It is commonly used for servers, device event loops, and applications that need to monitor many file descriptors.

What Problem It Solves

Compared with select() and poll(), epoll is designed for larger fd sets and event-driven applications.

many event sources
epoll_wait()
dispatch by fd or context pointer

Key APIs

API Purpose
epoll_create1() Create an epoll instance
epoll_ctl() Add, modify, or remove monitored fds
epoll_wait() Wait for ready events

Typical Workflow

epoll_create1()
epoll_ctl(ADD listen_fd)
epoll_ctl(ADD timer_fd)
epoll_ctl(ADD signal_fd)
epoll_wait()
dispatch event

Minimal Pattern

int epfd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event ev;

/* Register fd. */
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

while (1) {
    struct epoll_event events[16];
    int n = epoll_wait(epfd, events, 16, -1);

    if (n < 0) {
        if (errno == EINTR)
            continue;
        break;
    }

    for (int i = 0; i < n; i++) {
        if (events[i].events & EPOLLIN) {
            /* Read and handle the event. */
        }
    }
}

Context-Based Dispatch

For larger programs, it is usually cleaner to store a pointer in event.data.ptr instead of using only event.data.fd.

struct epoll_item {
    int fd;
    enum epoll_item_type type;
    void *ctx;
};

This allows one event loop to handle:

  • server socket
  • client socket
  • timerfd
  • signalfd
  • eventfd
  • device fd

Level-Triggered Behavior

The default epoll mode is level-triggered.

If an fd remains readable, epoll_wait() may keep reporting it as ready. The application should read until the event is consumed or until non-blocking read() returns EAGAIN.

Common Pitfalls

Pitfall Result
Not using non-blocking sockets Event loop may block inside read() or send()
Forgetting to remove closed fds Stale fd references or confusing behavior
Not handling EPOLLHUP / EPOLLERR Broken connections are not cleaned up correctly
Not draining readable fd Repeated wakeups
Mixing fd ownership incorrectly Double close or use-after-close bugs