diff --git a/lib/conncache.c b/lib/conncache.c index 01681d4ebbc9..4ee276246f0c 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -947,11 +947,19 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, { struct Curl_easy *admin; bool healthy = TRUE; + bool poll_input; DEBUGASSERT(!data->conn); if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */ return FALSE; - if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000) + + /* An idle, non-multiplexed connection must not be reused when it has + * input pending (RFC 9112, section 9.2), so that is polled on every + * reuse. The once a second limit only applies to the other checks. */ + poll_input = !CONN_INUSE(conn) && !conn->bits.connect_only && + !Curl_conn_is_multiplex(conn, FIRSTSOCKET) && + !conn->scheme->run->connection_is_dead; + if(!poll_input && curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000) return TRUE; admin = Curl_get_admin(data); @@ -966,8 +974,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, Curl_attach_connection(admin, conn, FALSE); healthy = Curl_conn_is_alive(admin, conn, &input_pending); Curl_detach_connection(admin); - if(healthy && input_pending && - !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { + if(healthy && input_pending && poll_input) { /* Non-multiplexed connections without attached transfers should * not have input pending. The input might be a TLS Notify Close, * for all we know. */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 3b32fce7bb30..2266010d868d 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -1709,6 +1709,7 @@ TESTCASES = \ test1686 \ \ test1700 \ + test1701 \ test1702 \ test1703 \ test1704 \ diff --git a/tests/data/test1701 b/tests/data/test1701 new file mode 100644 index 000000000000..e4e3d044302a --- /dev/null +++ b/tests/data/test1701 @@ -0,0 +1,75 @@ + + + + +HTTP +HTTP GET +DELAY + + +# Server-side + +# the first response is exactly 60 bytes so that with writedelay the server +# sends it in three 20 byte writes and the unsolicited second response only +# starts arriving after the first transfer is complete and the connection +# has been returned to the pool + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 6 + +first +HTTP/1.1 200 OK +Content-Length: 6 + +stray + + +HTTP/1.1 200 OK +Content-Length: 7 + +second + + +writedelay: 150 + + + +# Client-side + + +http + + +HTTP/1.1 idle connection with unsolicited data is not reused + +# --rate 1/s starts the second request 1000 ms after the first one started, +# which is about 700 ms after the first response completed and the +# connection went back to the pool, inside the one second window where the +# pool used to skip the alive check. The stray response has fully arrived +# about 750 ms in. + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 --rate 1/s + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +first +second + + +