@cryptotaxi247 / netdata-1 / commits / b66a1b78d

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)