]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux-fcgi: detect an early connection error during the handshake
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 6 Nov 2026 09:40:06 +0000 (10:40 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 9 Nov 2026 12:33:52 +0000 (13:33 +0100)
fcgi_conn_is_dead() treats a connection with FCGI_CF_ERROR as dead, but not
one with FCGI_CF_ERR_PENDING. The latter is set by fcgi_send() when the
transport reports an error, and is only turned into FCGI_CF_ERROR once
FCGI_CF_EOS is also set, i.e. once a read0 was seen. A connection whose
transport failed while the GET_VALUES handshake was still in progress
carries FCGI_CF_ERR_PENDING alone, and is therefore considered alive, so
nothing closes it: fcgi_send() bails out immediately on the flag,
fcgi_recv() is not allowed to run either (fcgi_recv_allowed() refuses when
the wait event is subscribed for SEND only... in practice nothing makes
progress any more), and h_detach()'s idle-list branch excludes it so it is
not parked either. It is only released when the idle-connection purge
reaches it.

This is the same situation mux-h2 fixed in commit 21fb6bdab ("BUG/MEDIUM:
mux-h2: Be able to detect connection error during handshake"), which reports
a connection as dead when H2_CF_ERR_PENDING is set before any frame was
received. Let's do the same here: a connection with FCGI_CF_ERR_PENDING
whose state is still below FCGI_CS_RECORD_H has not exchanged a single FCGI
record yet, so it cannot be reused and is dropped.

The state threshold differs from mux-h2: there, H2_CS_FRAME_H means "waiting
for the next frame header" and is reached before any frame is processed,
while FCGI_CS_RECORD_H is the state where records may be exchanged, the
three states below it covering only the GET_VALUES handshake.

This should be backported to all supported versions.

src/mux_fcgi.c

index d6673f687def88842b083d13eafae90a19b51d5f..2d4d6253c3cddad1a7583d4e4515335dfe5f7635 100644 (file)
@@ -891,6 +891,8 @@ static inline int fcgi_conn_is_dead(struct fcgi_conn *fconn)
        if (eb_is_empty(&fconn->streams_by_id) &&               /* don't close if streams exist */
            (!(fconn->flags & FCGI_CF_KEEP_CONN) ||             /* don't keep the connection alive */
             (fconn->flags & FCGI_CF_ERROR) ||                  /* errors close immediately */
+            (fconn->flags & FCGI_CF_ERR_PENDING &&
+             fconn->state < FCGI_CS_RECORD_H) || /* early error during connect */
             (fconn->state == FCGI_CS_CLOSED && !fconn->task) ||/* a timeout stroke earlier */
             (!(fconn->conn->owner)) ||                         /* Nobody's left to take care of the connection, drop it now */
             (!br_data(fconn->mbuf) &&                          /* mux buffer empty, also process clean events below */