Fix timeout cleanup race for long-running first web requests (#21722)
Fix web timeout race for long-running function responses
Costa Tsaousis committed
Feb 8, 2026 at 06:25 UTC
b66a1b78d472cbe419f6069c526d285def3d5b63
3 files changed
+17
-2
src/libnetdata/socket/poll-events.c
+7
-2
@@ -546,9 +546,14 @@ void poll_events(LISTEN_SOCKETS *sockets
546
next = pi->next;
547
548
if(likely(pi->flags & POLLINFO_FLAG_CLIENT_SOCKET)) {
549
- if (unlikely(pi->send_count == 0 && p.complete_request_timeout > 0 && (now - pi->connected_t) >= p.complete_request_timeout)) {
549
+ if (unlikely(
550
+ !(pi->flags & POLLINFO_FLAG_FIRST_REQUEST_RECEIVED) &&
551
+ pi->send_count == 0 &&
552
+ p.complete_request_timeout > 0 &&
553
+ (now - pi->connected_t) >= p.complete_request_timeout
554
+ )) {
555
nd_log(NDLS_DAEMON, NDLP_DEBUG,
551
- "POLLFD: LISTENER: client slot %zu (fd %d) from %s port %s has not sent a complete request in %zu seconds - closing it. "
556
+ "POLLFD: LISTENER: client slot %zu (fd %d) from %s port %s has not completed its first request in %zu seconds - closing it. "
557
, i
558
, pi->fd
559
, pi->client_ip ? pi->client_ip : "<undefined-ip>"
src/libnetdata/socket/poll-events.h
+1
@@ -9,6 +9,7 @@
9
#define POLLINFO_FLAG_CLIENT_SOCKET (1U << 1)
10
#define POLLINFO_FLAG_DONT_CLOSE (1U << 2)
11
#define POLLINFO_FLAG_REMOVED_FROM_POLL (1U << 3)
12
+#define POLLINFO_FLAG_FIRST_REQUEST_RECEIVED (1U << 4)
13
14
typedef struct poll POLLJOB;
15
typedef struct pollinfo POLLINFO;
src/web/server/static/static-threaded.c
+9
@@ -196,6 +196,15 @@ static int web_server_rcv_callback(POLLINFO *pi, nd_poll_event_t *events) {
196
web_client_process_request_from_web_server(w);
197
current_thread_pollinfo = NULL;
198
199
+ // Request processing may block for long-running functions.
200
+ // Refresh receive timestamp so idle timeout uses the actual return time.
201
+ pi->last_received_t = now_boottime_sec();
202
+
203
+ // The first-request timeout protects request ingress only.
204
+ // Once we no longer wait to receive request bytes, the first request is complete.
205
+ if(unlikely(!(pi->flags & POLLINFO_FLAG_FIRST_REQUEST_RECEIVED) && !web_client_has_wait_receive(w)))
206
+ pi->flags |= POLLINFO_FLAG_FIRST_REQUEST_RECEIVED;
207
+
208
if (unlikely(w->mode == HTTP_REQUEST_MODE_STREAM)) {
209
ssize_t rc = web_client_send(w);
210
if(rc > 0)