From: Aleksandar Lazic Date: Tue, 3 Nov 2026 13:00:00 +0000 (+0100) Subject: BUG/MEDIUM: mux-fcgi: do not enforce the timeout on connections with streams X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=6e3bad2f32dcc68e76481b4856d509869fa41be1;p=haproxy.git BUG/MEDIUM: mux-fcgi: do not enforce the timeout on connections with streams The FCGI connection task is rearmed with "timeout server" on each pass in fcgi_process(), and fcgi_timeout_task() kills the connection when it expires, even when streams are still attached to it. With a slow client, HAProxy stops reading from the FastCGI server once the buffers are full, so nothing happens on the FCGI connection for a while, and the connection is killed in the middle of the response. The response is truncated, the log reports "SD--", and the FastCGI server gets a reset while it is blocked in write(). Whether the stream waits for the server or for the client is only known by the stream layer, which already enforces "timeout server" when the server does not answer. So, as done for the H2 mux by commit c2ea47fb1 ("BUG/MEDIUM: mux-h2: do not enforce timeout on long connections", for GitHub issue #308), the connection timeout is now only enforced when no stream connector is attached anymore: fcgi_conn_may_expire() mirrors h2c_may_expire(), fcgi_process() leaves the task unarmed while streams are attached, and fcgi_timeout_task() does not kill the connection in this case. A FastCGI server which does not answer at all still gets a 504 after "timeout server", reported as "sH--", as before. This fixes GitHub issue #3558. It should be backported to all supported versions. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Aleksandar Lazic --- diff --git a/src/mux_fcgi.c b/src/mux_fcgi.c index cc92ac8ef..d03adb7f9 100644 --- a/src/mux_fcgi.c +++ b/src/mux_fcgi.c @@ -834,6 +834,15 @@ static int fcgi_conn_read0_pending(struct fcgi_conn *fconn) } +/* returns true if the connection may expire, i.e. if no stream connector is + * attached anymore. As long as one is attached, the timeouts are enforced by + * the stream layer, which knows if it waits for the server or for the client. + */ +static inline int fcgi_conn_may_expire(const struct fcgi_conn *fconn) +{ + return !fconn->nb_sc; +} + /* Returns true if the FCGI connection must be release */ static inline int fcgi_conn_is_dead(struct fcgi_conn *fconn) { @@ -3247,7 +3256,10 @@ static int fcgi_process(struct fcgi_conn *fconn) fcgi_release_mbuf(fconn); if (fconn->task) { - fconn->task->expire = tick_add(now_ms, (fconn->state == FCGI_CS_CLOSED ? fconn->shut_timeout : fconn->timeout)); + if (fcgi_conn_may_expire(fconn)) + fconn->task->expire = tick_add(now_ms, (fconn->state == FCGI_CS_CLOSED ? fconn->shut_timeout : fconn->timeout)); + else + fconn->task->expire = TICK_ETERNITY; task_queue(fconn->task); } @@ -3358,6 +3370,16 @@ struct task *fcgi_timeout_task(struct task *t, void *context, unsigned int state return t; } + if (!fcgi_conn_may_expire(fconn)) { + /* streams are still attached, they may be waiting for + * the client, so the timeout is not ours to enforce. + */ + HA_SPIN_UNLOCK(IDLE_CONNS_LOCK, &idle_conns[tid].idle_conns_lock); + t->expire = TICK_ETERNITY; + TRACE_DEVEL("leaving (streams attached)", FCGI_EV_FCONN_WAKE, fconn->conn); + return t; + } + /* We're about to destroy the connection, so make sure nobody attempts * to steal it from us. */