From: Stefan Eissing Date: Wed, 4 Jun 2025 13:26:55 +0000 (+0200) Subject: SCP/SFTP: avoid busy loop after EAGAIN X-Git-Tag: curl-8_15_0~332 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=34d694f89c9269bb23b9f71f58b88062142dcc5b;p=thirdparty%2Fcurl.git SCP/SFTP: avoid busy loop after EAGAIN The ssh libraries do not reveal if they still have data buffered from the peer. Only when their buffers are read empty can curl be sure that it is safe to rely on socket polling. This change adds detection of EGAIN on receive in the transfer loop and allows SFTP/SCP transfers to avoid a busy loop in such a case (which should happen often when CPU exceeds network bandwidth). Closes #17533 --- diff --git a/lib/transfer.c b/lib/transfer.c index ef7d7f19af..f67aab5d49 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -115,7 +115,7 @@ char *Curl_checkheaders(const struct Curl_easy *data, } #endif -static int data_pending(struct Curl_easy *data) +static int data_pending(struct Curl_easy *data, bool rcvd_eagain) { struct connectdata *conn = data->conn; @@ -124,8 +124,9 @@ static int data_pending(struct Curl_easy *data) /* in the case of libssh2, we can never be really sure that we have emptied its internal buffers so we MUST always try until we get EAGAIN back */ - return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) || - Curl_conn_data_pending(data, FIRSTSOCKET); + return (!rcvd_eagain && + conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP)) || + Curl_conn_data_pending(data, FIRSTSOCKET); } /* @@ -271,6 +272,7 @@ static CURLcode sendrecv_dl(struct Curl_easy *data, int maxloops = 10; curl_off_t total_received = 0; bool is_multiplex = FALSE; + bool rcvd_eagain = FALSE; result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); if(result) @@ -303,23 +305,25 @@ static CURLcode sendrecv_dl(struct Curl_easy *data, bytestoread = (size_t)data->set.max_recv_speed; } + rcvd_eagain = FALSE; nread = xfer_recv_resp(data, buf, bytestoread, is_multiplex, &result); if(nread < 0) { if(CURLE_AGAIN != result) goto out; /* real error */ + rcvd_eagain = TRUE; result = CURLE_OK; if(data->req.download_done && data->req.no_body && !data->req.resp_trailer) { DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, " "not waiting for EOS")); nread = 0; - /* continue as if we read the EOS */ + /* continue as if we received the EOS */ } else break; /* get out of loop */ } - /* We only get a 0-length read on EndOfStream */ + /* We only get a 0-length receive at the end of the response */ blen = (size_t)nread; is_eos = (blen == 0); *didwhat |= KEEP_RECV; @@ -355,12 +359,14 @@ static CURLcode sendrecv_dl(struct Curl_easy *data, } while(maxloops--); - if((maxloops <= 0) || data_pending(data)) { - /* did not read until EAGAIN or there is still pending data, mark as - read-again-please */ + if(!rcvd_eagain || data_pending(data, rcvd_eagain)) { + /* Did not read until EAGAIN or there is still data pending + * in buffers. Mark as read-again via simulated SELECT results. */ data->state.select_bits = CURL_CSELECT_IN; if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) data->state.select_bits |= CURL_CSELECT_OUT; + CURL_TRC_M(data, "sendrecv_dl() no EAGAIN/pending data, " + "set select_bits=%x", data->state.select_bits); } if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) &&