Why blocking I/O breaks at scale

Real-World Analogy

Imagine you're waiting for 100 friends to text you. You could check each phone one-by-one in a loop — that's polling (slow, wastes energy). You could sit staring at one phone until it buzzes, ignoring the others — that's blocking I/O (misses messages). Or you could set up notifications so your phone rings only when a message arrives — that's event notification. epoll is the Linux system call that implements notifications for file descriptors. Your process sleeps until the kernel says "FD 7 is ready — go read it now."

In standard (blocking) I/O, a call like read(fd, buf, 1024) will freeze your process until data arrives. If you are managing 10,000 network connections and only 3 have data right now, you cannot afford to block on any one of them — you would miss the other 9,997. The operating system needs a way to watch many FDs simultaneously and wake your process only when something happens.

Three strategies have been developed over the history of Unix:

Strategy 1 — Blocking I/O
Call and wait
read(fd, buf, n) returns only when data is available. The process is suspended — uses no CPU but can't do anything else. Fine for one FD; breaks for many.
Strategy 2 — Polling (select/poll)
Check all, repeatedly
Pass all FDs to select()/poll(). The kernel scans them all and returns which are ready. O(N) scan per call — breaks for 10,000+ FDs. FD_SETSIZE cap ~1024.
Strategy 3 — epoll (event notification)
Register once, get woken up
Register FDs with the kernel once via epoll_ctl(). Call epoll_wait() to sleep. Kernel fills only the ready events — O(1) lookup. Scales to hundreds of thousands of FDs.

Why is select/poll O(N)? Every time you call select(), you must rebuild and pass the entire set of FDs you care about. The kernel then linearly scans all of them from 0 to max_fd. With 10,000 connections, that is 10,000 checks per event loop iteration — even if only 1 FD is ready.

Why is epoll O(1)? You register FDs with epoll_ctl() once. Internally, the kernel uses a red-black tree to store registered FDs and a linked list of ready events. When data arrives on FD 5, the kernel adds FD 5 to the ready list. epoll_wait() returns only that ready list — it never scans the full set. The cost is proportional to the number of active events, not the total number of watched FDs.

Non-blocking I/O with O_NONBLOCK: When using epoll, your FDs should be set to non-blocking mode. This means if you call read() on an FD that has no data, instead of blocking, it immediately returns -1 with errno set to EAGAIN (or EWOULDBLOCK). You must handle this case. In edge-triggered mode, you must drain the FD completely in a loop until you get EAGAIN.

Python asyncio (what it does under the hood)
import asyncio

async def handle(reader, writer):
    data = await reader.read(100)
    writer.write(data)
    await writer.drain()

# asyncio uses epoll (Linux) or kqueue (macOS)
# to watch all sockets — same idea as epoll_wait()
asyncio.run(asyncio.start_server(
    handle, '127.0.0.1', 8888))
C with epoll (the actual mechanism)
/* Python's asyncio is doing exactly this: */
int epfd = epoll_create1(0);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

struct epoll_event events[64];
while (1) {
    int n = epoll_wait(epfd, events, 64, -1);
    for (int i = 0; i < n; i++)
        handle(events[i].data.fd);
}

Edge-triggered vs Level-triggered: epoll supports two notification modes. Level-triggered (the default, EPOLLIN) fires as long as there is data available to read — just like select()/poll(). Edge-triggered (EPOLLIN | EPOLLET) fires only once when the state transitions from "no data" to "data available." With edge-triggered, you must read all available data in a loop until you get EAGAIN, or you will miss events. Edge-triggered is more efficient but more dangerous if you do not drain the buffer completely.

The two async approaches from Wk10A: The tutorial distinguishes interrupt-based async (hardware/kernel interrupts your program via a signal handler the moment data arrives — immediate, but complex to write safely) from epoll-based async (you register FDs and block on epoll_wait() — a unified, scalable event loop). epoll gives you a single place to handle all I/O events across any number of descriptors.

epoll is Linux-only

epoll is a Linux-specific API (kernel 2.6+). On macOS/BSD use kqueue; on Windows use IOCP. The POSIX portable equivalent is poll() (slightly better than select()) but neither approaches epoll's scalability. Python's asyncio, Node.js's libuv, and nginx all abstract over these platform-specific APIs.

Core mental model

Three calls, one loop: (1) epoll_create1(0) — create the watcher. (2) epoll_ctl(..., EPOLL_CTL_ADD, ...) — register an FD to watch. (3) epoll_wait(...) — sleep until something is ready, then process only the ready events. That's the whole pattern. Everything else is configuration detail.

epoll system calls, flags, and non-blocking I/O

The three epoll system calls

/* ── Step 1: Create an epoll instance ── */
int epfd = epoll_create1(0);
 ^   ^      ^              ^
 |   |      |              └── flags: 0 = none, or EPOLL_CLOEXEC
 |   |      └─────────────── function name (prefer over epoll_create(size))
 |   └────────────────────── epfd: the epoll file descriptor (close() when done)
 └────────────────────────── returns -1 on error, check with perror()

/* ── Step 2: Register a file descriptor ── */
struct epoll_event ev;
ev.events  = EPOLLIN;   // watch for: data available to read
ev.data.fd = sockfd;  // store the fd in the event's user-data union

int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
               ^     ^                ^       ^
               |     |                |       └── pointer to epoll_event struct
               |     |                └────────── the fd to add/modify/remove
               |     └─────────────────────────── operation: ADD / MOD / DEL
               └───────────────────────────────── the epoll instance fd

/* ── Step 3: Wait for events ── */
struct epoll_event events[MAX_EVENTS];
int nready = epoll_wait(epfd, events, MAX_EVENTS, -1);
                ^      ^       ^           ^
                |      |       |           └── timeout ms: -1=block forever, 0=return immediately
                |      |       └────────────── max events to return per call
                |      └────────────────────── caller-allocated output array
                └───────────────────────────── returns: number of ready events (0=timeout, -1=error)

/* ── Step 4: Process each ready event ── */
for (int i = 0; i < nready; i++) {
    if (events[i].events & EPOLLIN) {
        read(events[i].data.fd, buf, sizeof(buf));
    }
}
Key insight: epoll_wait() fills your events[] array with only the ready file descriptors. You never iterate over idle FDs — that is what makes it O(1) per active event.

Event flags — what to watch for

FlagMeaningWhen it fires
EPOLLIN Data available to read A read() call will not block; there are bytes in the kernel buffer
EPOLLOUT Ready for writing A write() call will not block; send buffer has space
EPOLLERR Error condition Always monitored automatically; e.g. broken pipe on write
EPOLLHUP Hang-up (peer closed) Always monitored automatically; remote side closed connection
EPOLLET Edge-triggered mode Fires once on transition. Combine: EPOLLIN | EPOLLET
EPOLLONESHOT One-shot delivery FD is disabled after first event; re-arm with EPOLL_CTL_MOD

epoll_ctl operations

OperationMeaning
EPOLL_CTL_ADD Add a new FD to the epoll instance's watch list
EPOLL_CTL_MOD Change the event flags for an already-registered FD
EPOLL_CTL_DEL Remove an FD from the watch list (pass NULL for the event pointer)

Setting O_NONBLOCK with fcntl

/* Make an existing fd non-blocking */
int flags = fcntl(fd, F_GETFL, 0);     // get current flags
fcntl(fd, F_SETFL, flags | O_NONBLOCK);  // add O_NONBLOCK flag
 ^     ^   ^        ^           ^
 |     |   |        |           └── bitwise OR preserves existing flags
 |     |   |        └───────────── set flags command
 |     |   └────────────────────── the fd to modify
 |     └────────────────────────── returns 0 on success, -1 on error
 └──────────────────────────────── needs #include <fcntl.h>

/* Or set during open() */
int fd = open("bigfile.bin", O_RDONLY | O_NONBLOCK);

/* When read() on a non-blocking fd has no data: */
ssize_t n = read(fd, buf, sz);
if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    /* no data right now — try again later */
}
Rule: Always use O_NONBLOCK with epoll edge-triggered mode. With level-triggered you can use blocking FDs, but non-blocking is safer and more common in practice.

select vs epoll — head to head

Propertyselect()epoll()
ScalabilityO(N) scan per callO(1) per active event
FD limit~1024 (FD_SETSIZE)No hard limit
PortabilityAll POSIX systemsLinux only
FD set rebuildEvery callOnce via epoll_ctl
Return infoMust iterate all FDsOnly ready FDs returned
Trigger modesLevel-triggered onlyLevel or edge-triggered
Required header<sys/select.h><sys/epoll.h>

epoll patterns you can compile and run

Example 1 — Simple epoll watcher: stdin + a pipe Wk10A Tutorial
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10

int main(void) {
    int pipefd[2];
    if (pipe(pipefd) == -1) { perror("pipe"); return 1; }

    /* Write something into the pipe so one end becomes readable */
    write(pipefd[1], "hello", 5);

    /* Step 1: Create the epoll instance */
    int epfd = epoll_create1(0);
    if (epfd == -1) { perror("epoll_create1"); return 1; }

    /* Step 2: Register stdin (fd 0) for readability */
    struct epoll_event ev;
    ev.events  = EPOLLIN;
    ev.data.fd = STDIN_FILENO;
    epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);

    /* Step 2b: Register the read end of the pipe */
    ev.events  = EPOLLIN;
    ev.data.fd = pipefd[0];
    epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[0], &ev);

    printf("Watching stdin (fd 0) and pipe read-end (fd %d)\n", pipefd[0]);
    printf("Type something, or just wait — the pipe event fires immediately.\n\n");

    /* Step 3: Event loop — timeout 3000 ms */
    struct epoll_event events[MAX_EVENTS];
    int nready = epoll_wait(epfd, events, MAX_EVENTS, 3000);

    if (nready == 0) {
        printf("Timeout — no events in 3 seconds.\n");
    } else {
        for (int i = 0; i < nready; i++) {
            int fd = events[i].data.fd;
            char buf[64] = {0};
            ssize_t n = read(fd, buf, sizeof(buf) - 1);
            if (fd == STDIN_FILENO)
                printf("stdin became ready: read %zd bytes: '%s'\n", n, buf);
            else
                printf("pipe became ready:  read %zd bytes: '%s'\n", n, buf);
        }
    }

    close(pipefd[0]);
    close(pipefd[1]);
    close(epfd);
    return 0;
}
Sample output (no stdin input — pipe fires immediately)
Watching stdin (fd 0) and pipe read-end (fd 4)
Type something, or just wait — the pipe event fires immediately.

pipe became ready: read 5 bytes: 'hello'
Example 2 — Non-blocking read with O_NONBLOCK and EAGAIN handling Wk10A + Lecture Wk8
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>

/* Helper: make any fd non-blocking */
static void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) { perror("fcntl F_GETFL"); exit(1); }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL"); exit(1);
    }
}

#define MAX_EVENTS 16
#define BUF_SIZE   256

int main(void) {
    int pipefd[2];
    pipe(pipefd);

    /* Write a burst of data — more than one read() may be needed */
    for (int i = 0; i < 5; i++)
        write(pipefd[1], "COMP2017\n", 9);
    close(pipefd[1]);  /* signal EOF on writer side */

    /* Make read-end non-blocking */
    set_nonblocking(pipefd[0]);

    /* Set up epoll in EDGE-TRIGGERED mode (EPOLLET) */
    int epfd = epoll_create1(0);
    struct epoll_event ev;
    ev.events  = EPOLLIN | EPOLLET;   /* edge-triggered */
    ev.data.fd = pipefd[0];
    epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[0], &ev);

    printf("Edge-triggered epoll — must drain all data on each event:\n\n");

    struct epoll_event events[MAX_EVENTS];
    int nready = epoll_wait(epfd, events, MAX_EVENTS, 1000);

    for (int i = 0; i < nready; i++) {
        if (events[i].events & EPOLLIN) {
            /* With edge-triggered, drain the entire buffer in a loop */
            char buf[BUF_SIZE];
            while (1) {
                ssize_t n = read(events[i].data.fd, buf, sizeof(buf));
                if (n > 0) {
                    printf("Read %zd bytes: %.*s", n, (int)n, buf);
                } else if (n == 0) {
                    printf("\nEOF reached.\n");
                    break;
                } else {
                    /* n == -1 */
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        printf("\nEAGAIN — buffer drained, no more data right now.\n");
                        break;  /* done for this event */
                    }
                    perror("read");
                    break;
                }
            }
        }
    }

    close(pipefd[0]);
    close(epfd);
    return 0;
}
Output
Edge-triggered epoll — must drain all data on each event:

Read 45 bytes: COMP2017
COMP2017
COMP2017
COMP2017
COMP2017

EOF reached.
Example 3 — select() vs epoll_wait() on the same task Wk10A Tutorial — select vs epoll comparison
/* ================================================================
   SAME TASK: watch two pipes for readability.
   First implementation: select()   — O(N) rebuild every call
   Second implementation: epoll()   — O(1) ready-event lookup
   ================================================================ */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/epoll.h>

/* --- select() version --- */
void demo_select(int fd1, int fd2) {
    fd_set readfds;
    struct timeval tv = { .tv_sec = 2, .tv_usec = 0 };

    /* Must REBUILD the set before every call — O(N) overhead */
    FD_ZERO(&readfds);
    FD_SET(fd1, &readfds);
    FD_SET(fd2, &readfds);
    int maxfd = (fd1 > fd2 ? fd1 : fd2) + 1;

    int n = select(maxfd, &readfds, NULL, NULL, &tv);
    if (n <= 0) { printf("[select] timeout or error\n"); return; }

    /* Must CHECK every fd with FD_ISSET — iterates all up to maxfd */
    if (FD_ISSET(fd1, &readfds)) {
        char buf[32] = {0};
        read(fd1, buf, sizeof(buf)-1);
        printf("[select] fd1 ready: '%s'\n", buf);
    }
    if (FD_ISSET(fd2, &readfds)) {
        char buf[32] = {0};
        read(fd2, buf, sizeof(buf)-1);
        printf("[select] fd2 ready: '%s'\n", buf);
    }
}

/* --- epoll() version --- */
void demo_epoll(int fd1, int fd2) {
    int epfd = epoll_create1(0);
    struct epoll_event ev;

    /* Register ONCE — no rebuild needed on subsequent iterations */
    ev.events = EPOLLIN; ev.data.fd = fd1;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev);
    ev.events = EPOLLIN; ev.data.fd = fd2;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev);

    struct epoll_event events[10];
    /* epoll_wait returns ONLY ready fds — no need to check all */
    int nready = epoll_wait(epfd, events, 10, 2000);
    for (int i = 0; i < nready; i++) {
        char buf[32] = {0};
        read(events[i].data.fd, buf, sizeof(buf)-1);
        printf("[epoll]  fd%d ready: '%s'\n",
               events[i].data.fd == fd1 ? 1 : 2, buf);
    }
    close(epfd);
}

int main(void) {
    int p1[2], p2[2];
    pipe(p1); pipe(p2);
    write(p1[1], "alpha", 5);
    write(p2[1], "beta",  4);

    printf("=== select() ===\n");
    demo_select(p1[0], p2[0]);

    /* Refill pipes for epoll demo */
    write(p1[1], "alpha", 5);
    write(p2[1], "beta",  4);

    printf("\n=== epoll() ===\n");
    demo_epoll(p1[0], p2[0]);

    close(p1[0]); close(p1[1]);
    close(p2[0]); close(p2[1]);
    return 0;
}
Output
=== select() ===
[select] fd1 ready: 'alpha'
[select] fd2 ready: 'beta'

=== epoll() ===
[epoll] fd1 ready: 'alpha'
[epoll] fd2 ready: 'beta'

Practice problems with solutions

P1 — Three I/O strategies: name them and give a real-world analogy for each Wk10A Tutorial

Name the three fundamental approaches to I/O that a Unix process can use. For each one, give a concrete real-world analogy, describe its CPU usage, and describe one major drawback.

1. Blocking I/O: Analogy — sitting by the phone, staring at it, doing nothing else until it rings. CPU usage: zero (process is suspended by the OS). Drawback: can only wait on one thing at a time; misses events on other FDs.

2. Polling (select/poll): Analogy — checking each of your 100 friends' phones one-by-one in a loop, every second. CPU usage: high if spin-polling, moderate with select() timeout. Drawback: O(N) cost grows with number of FDs; select() caps at ~1024 FDs; must rebuild fd_set every call.

3. Event notification (epoll / interrupt-based): Analogy — setting up notifications so your phone rings only when a message arrives. CPU usage: near zero while waiting. Register FDs once, sleep in epoll_wait(), kernel reports only ready FDs. Drawback: Linux-only for epoll; edge-triggered mode requires careful buffer draining.
P2 — Write code to add fd to epoll instance epfd for readability Wk10A Tutorial

You have an epoll instance epfd and a file descriptor fd. Write the C code to add fd to the epoll instance watching for both readability and errors. Store the fd in the event's data union.

struct epoll_event ev;
ev.events  = EPOLLIN | EPOLLERR;   /* watch for readable + errors */
ev.data.fd = fd;                   /* store fd in user-data union */

if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
    perror("epoll_ctl");
    exit(1);
}
Key points: The struct epoll_event has two fields: events (a bitmask of flags) and data (a union — you can store an fd, a pointer, a uint64_t, etc.). EPOLLERR and EPOLLHUP are always monitored automatically but naming them makes intent explicit. Always check epoll_ctl()'s return value — passing a closed fd or an fd already registered will return -1.
P3 — What does read() return on a non-blocking FD with no data? What is errno? Wk10A + Lecture Wk8 Page 17

A file descriptor fd has been set to non-blocking mode with O_NONBLOCK. There is currently no data in its kernel buffer. Your code calls read(fd, buf, 256). What value does read() return? What is errno set to? Write the correct code to handle this case.

#include <errno.h>

ssize_t n = read(fd, buf, 256);

if (n == -1) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        /* Non-blocking: no data available right now.
           This is NOT an error — simply try again later,
           or wait for the next epoll_wait() notification. */
        return;
    }
    /* A real error occurred */
    perror("read");
    return;
}
if (n == 0) {
    /* EOF — the other end closed the connection */
    close(fd);
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
    return;
}
/* n > 0: successfully read n bytes */
process_data(buf, n);
Answer: read() returns -1. errno is set to EAGAIN (on Linux, EWOULDBLOCK is defined as the same value). This is not a real error — it means "come back later." You must check for this case explicitly. Treating EAGAIN as a fatal error is a common bug in epoll-based servers. Also handle n == 0 (EOF) by removing the fd from epoll and closing it.
P4 — Why is epoll more scalable than select for 10,000 connections? Wk10A Tutorial — select vs epoll

Your server has 10,000 simultaneous connections. Only 3 have data arriving at any given moment. Explain quantitatively why epoll is dramatically more efficient than select for this workload. Reference the specific algorithmic costs.

select() cost: Every call to select() requires: (1) rebuilding the fd_set bitmask for all 10,000 FDs from scratch — this is a user-space memcpy of ~1250 bytes; (2) copying the full fd_set into the kernel; (3) the kernel linearly scanning all 10,000 entries to find which are ready; (4) copying the result back; (5) your code iterating FD_ISSET() for all 10,000 to find the 3 that are ready. Cost = O(N) where N = 10,000.

epoll() cost: Registration happens once via epoll_ctl() — not per-call. Internally the kernel maintains a red-black tree of registered FDs and a ready-list. When data arrives on FD 5, the kernel appends it to the ready-list in O(1). epoll_wait() copies back only the 3 ready entries. Your loop iterates exactly 3 times. Cost = O(k) where k = number of active events (3), not O(N).

Practical implication: With 10,000 connections and 3 active events, select() does ~10,000 units of work per loop iteration. epoll() does ~3. At high connection counts, epoll is orders of magnitude faster. Note: select() also has a hard FD limit of FD_SETSIZE (~1024), making it literally unable to handle 10,000 FDs on default systems.
P5 — epoll_wait() returns 3. What does that mean? Write the handling loop. Wk10A Tutorial

Your code calls int nready = epoll_wait(epfd, events, MAX_EVENTS, -1) and nready is 3. What does this mean? Write the complete loop to handle all 3 events, checking for read events and error/hangup conditions. Assume char buf[1024] is available.

/* nready = 3 means: 3 file descriptors in the epoll watch list
   became ready since the last epoll_wait() call.
   events[0], events[1], events[2] hold the ready-event structs. */

for (int i = 0; i < nready; i++) {
    int fd = events[i].data.fd;
    uint32_t ev = events[i].events;

    /* Check for error or hangup first */
    if (ev & (EPOLLERR | EPOLLHUP)) {
        fprintf(stderr, "Error or hangup on fd %d\n", fd);
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        close(fd);
        continue;
    }

    /* Check for readable data */
    if (ev & EPOLLIN) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n == 0) {
            /* EOF — peer closed connection */
            epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
            close(fd);
        } else if (n == -1) {
            if (errno != EAGAIN) {   /* EAGAIN = no data, not an error */
                perror("read");
                epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                close(fd);
            }
        } else {
            /* n bytes successfully read — process them */
            printf("fd %d: got %zd bytes\n", fd, n);
        }
    }
}
Key takeaways: Always check EPOLLERR | EPOLLHUP before EPOLLIN. Use bitwise AND (&) not equality (==) to test flags, since multiple flags can be set simultaneously. Remove closed FDs from the epoll instance with EPOLL_CTL_DEL before closing them. The nready value is the count of filled entries in your events[] array — iterate exactly that many.
P6 — Edge-triggered vs level-triggered: what is the difference? Wk10A Tutorial — EPOLLET

Explain the difference between level-triggered (default) and edge-triggered (EPOLLET) epoll. Give an example: 100 bytes arrive on an FD but your read() only reads 50. What happens in each mode? What is the danger of edge-triggered if you do not drain the buffer?

/* Level-triggered (default — no EPOLLET flag): */
ev.events = EPOLLIN;              /* fires whenever data is available */

/* Edge-triggered: */
ev.events = EPOLLIN | EPOLLET;   /* fires only on state change */
Level-triggered (default): epoll_wait() fires as long as the condition is true. If 100 bytes arrive and you read only 50, the next epoll_wait() call will immediately fire again because there are still 50 bytes waiting. Safe and forgiving — similar to how select()/poll() behave.

Edge-triggered (EPOLLET): epoll_wait() fires only once when the state transitions from "no data" to "data available." If 100 bytes arrive and you read only 50, the remaining 50 bytes will never trigger another event — they sit in the buffer unread until more data arrives and causes another transition. This means you must read in a loop until EAGAIN on every edge-triggered event.

Danger: Failing to drain a buffer in edge-triggered mode causes a silent hang — your process blocks forever in epoll_wait() waiting for an event that will never come, while data sits in the kernel buffer.

Why use edge-triggered at all? It is more efficient in certain high-throughput scenarios (avoids repeated epoll_wait() returns when slow consumers fall behind) and is required by some patterns like EPOLLONESHOT.

Key concepts to memorize

Card 1 of 11
Question — click to flip
Answer
Click card to flip • Use buttons to navigate

Test your understanding

Topic 34 Quiz — Asynchronous I/O & epoll Score: 0 / 4
1
What does epoll_wait() return when no events occur within the timeout period?LO2
multiple choice
2
Fill in the blank: To make a file descriptor non-blocking, use fcntl(fd, F_SETFL, ___)LO3
fill in the blank
3
True or False: select() and epoll() have the same performance characteristics when monitoring 10,000 file descriptors with few active events.LO2
true / false
4
Which flag do you add to ev.events to watch for data available to read?LO3
multiple choice
0/4
Quiz complete!