From 944ab4fe95596127ec3c517037bad8fe6102dfe4 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 18:56:02 +0100 Subject: Stop reading a connection at a short read After answering, a connection went back to recv for its next request and got EAGAIN nearly every time: a failed syscall a request. A recv that returns less than it asked for has emptied the socket, and the edge-triggered epoll reports the next bytes when they come (epoll(7); as nginx does), so the connection now waits for that; a peer that hung up is read to its end as before. Small keep-alive GETs, 64 connections, five rounds in rotation (Raspberry Pi 5): 131.9k/s before this step, 139.9k after. With the three steps before it (a burst's keys in one look, the clock once a round, the head without printf): 116.1k -> 139.9k/s, p99 672 -> 512 us, above the 127.0k of 2026-09-26's build that the app model had since slowed. App reads: the total 111.5k -> 125.4k/s, a 20-row page 86.2k -> 94.3k; 1 MiB reads and durable posts unchanged. --- serve/serve.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'serve') diff --git a/serve/serve.c b/serve/serve.c index 14b0c45..4f633ed 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -84,6 +84,7 @@ typedef struct Connection { int fd; Phase phase; bool readable, writable, keep_alive, put, post; + bool hung_up; /* the peer closed its side: read until the end, never stop at a short read */ uint64_t deadline; char key[65]; /* the document's key, or the view's or input's name */ RouteKind route; @@ -770,8 +771,17 @@ static void drive(Connection *c) { if (end) { start_request(c, end); continue; } if (c->in_used == HEADER_LIMIT) { refuse(c, 431, "Request Header Fields Too Large", "headers"); continue; } if (!c->readable) return; - ssize_t n = recv(c->fd, c->in + c->in_used, HEADER_LIMIT - c->in_used, 0); - if (n > 0) { c->in_used += (size_t)n; c->deadline = c->host->now + c->host->timeout_ms; continue; } + size_t room = HEADER_LIMIT - c->in_used; + ssize_t n = recv(c->fd, c->in + c->in_used, room, 0); + if (n > 0) { + c->in_used += (size_t)n; + c->deadline = c->host->now + c->host->timeout_ms; + /* A short read emptied the socket; the edge-triggered epoll + * tells when more comes. Not asking again spares a recv that + * would fail (EAGAIN) after every request. */ + if ((size_t)n < room && !c->hung_up) c->readable = false; + continue; + } if (n < 0 && errno == EAGAIN) { c->readable = false; return; } if (n < 0 && errno == EINTR) continue; close_connection(c); @@ -779,10 +789,12 @@ static void drive(Connection *c) { } if (c->phase == READING_BODY) { if (!c->readable) return; - ssize_t n = recv(c->fd, c->body + c->received, c->body_size - c->received, 0); + size_t room = c->body_size - c->received; + ssize_t n = recv(c->fd, c->body + c->received, room, 0); if (n > 0) { c->received += (size_t)n; c->deadline = c->host->now + c->host->timeout_ms; + if ((size_t)n < room && !c->hung_up) c->readable = false; if (c->received == c->body_size) body_done(c); continue; } @@ -1537,6 +1549,7 @@ static void accept_all(Host *host) { c->fd = fd; c->phase = READING_HEAD; c->readable = c->writable = c->keep_alive = true; + c->hung_up = false; c->deadline = host->now + host->timeout_ms; c->ticket = (PureTicket){0}; c->out = NULL; @@ -1646,6 +1659,7 @@ static void *run_shard(void *context) { if (c->phase == CLOSED) continue; if (events[e].events & EPOLLERR) { close_connection(c); continue; } if (events[e].events & (EPOLLIN | EPOLLRDHUP | EPOLLHUP)) c->readable = true; + if (events[e].events & (EPOLLRDHUP | EPOLLHUP)) c->hung_up = true; if (events[e].events & EPOLLOUT) c->writable = true; drive(c); } -- cgit v1.2.3