networking · intermediate · ~15 min

Drain a non-blocking 'read' to EAGAIN

The drain loop for edge-triggered I/O.

Challenge

Implement int drain(int (*read_fn)(char *, int), char *buf, int cap, int *out_total).

read_fn(dst, n) returns:

  • >0: bytes copied into dst
  • 0: EOF — peer closed
  • -1: EAGAIN (no more data right now)
  • -2: real error

Call read_fn repeatedly into buf + offset, advancing offset by the return, until read_fn returns 0, -1, or -2. Cap the total at cap bytes (refuse to loop forever filling memory).

Return:

  • 0 on EAGAIN or EOF (success — drained as far as possible).
  • -1 on real error.

Set *out_total to the total bytes read in either case.

Why this matters

Edge-triggered epoll requires this loop. Get the loop wrong and connections silently stall — a notorious cause of 'works in dev, fails in prod' bugs.

Input format

Stub read function + buffer + cap.

Output format

0 / -1, plus *out_total.

Constraints

Cap the total bytes; never overrun buf.

Starter code

int drain(int (*read_fn)(char *, int), char *buf, int cap, int *out_total) { /* TODO */ return 0; }

Common mistakes

Returning -1 on EAGAIN; treating EAGAIN as fatal.

Edge cases to handle

Immediate EAGAIN (0 bytes read). Cap reached mid-drain.

Complexity

O(total bytes).

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.