diff options
author | Richard Whitehouse <richard.whitehouse@metaswitch.com> | 2017-11-30 16:56:53 +0000 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2021-06-24 15:51:39 +0200 |
commit | 0842175fa46a2f02bc95af3cafd6901130c562dd (patch) | |
tree | c9402548db3f64edc3ad6805dd83ab6e8417cbc8 /lib/multi.c | |
parent | a5ab72d5edd72eba3485c790b42006d407e6f162 (diff) | |
download | curl-0842175fa46a2f02bc95af3cafd6901130c562dd.tar.gz |
multi: alter transfer timeout ordering
- Check whether a connection has succeded before checking whether it's
timed out.
This means if we've connected quickly, but subsequently been
descheduled, we allow the connection to succeed. Note, if we timeout,
but between checking the timeout, and connecting to the server the
connection succeeds, we will allow it to go ahead. This is viewed as
an acceptable trade off.
- Add additional failf logging around failed connection attempts to
propogate the cause up to the caller.
Co-Authored-by: Martin Howarth
Closes #7178
Diffstat (limited to 'lib/multi.c')
-rw-r--r-- | lib/multi.c | 115 |
1 files changed, 75 insertions, 40 deletions
diff --git a/lib/multi.c b/lib/multi.c index 322f14377..2237ca69b 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1544,6 +1544,58 @@ static CURLcode multi_do_more(struct Curl_easy *data, int *complete) } /* + * Check whether a timeout occurred, and handle it if it did + */ +static bool multi_handle_timeout(struct Curl_easy *data, + struct curltime *now, + bool *stream_error, + CURLcode *result, + bool connect_timeout) +{ + timediff_t timeout_ms; + timeout_ms = Curl_timeleft(data, now, connect_timeout); + + if(timeout_ms < 0) { + /* Handle timed out */ + if(data->mstate == MSTATE_RESOLVING) + failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", + Curl_timediff(*now, data->progress.t_startsingle)); + else if(data->mstate == MSTATE_CONNECTING) + failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", + Curl_timediff(*now, data->progress.t_startsingle)); + else { + struct SingleRequest *k = &data->req; + if(k->size != -1) { + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" + CURL_FORMAT_CURL_OFF_T " bytes received", + Curl_timediff(*now, data->progress.t_startsingle), + k->bytecount, k->size); + } + else { + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T + " bytes received", + Curl_timediff(*now, data->progress.t_startsingle), + k->bytecount); + } + } + + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnected with pending data"); + *stream_error = TRUE; + } + *result = CURLE_OPERATION_TIMEDOUT; + (void)multi_done(data, *result, TRUE); + } + + return (timeout_ms < 0); +} + +/* * We are doing protocol-specific connecting and this is being called over and * over from the multi interface until the connection phase is done on * protocol layer. @@ -1674,7 +1726,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, bool done = FALSE; CURLMcode rc; CURLcode result = CURLE_OK; - timediff_t timeout_ms; timediff_t recv_timeout_ms; timediff_t send_timeout_ms; int control; @@ -1704,47 +1755,16 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(data->conn && (data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED)) { + /* Check for overall operation timeout here but defer handling the + * connection timeout to later, to allow for a connection to be set up + * in the window since we last checked timeout. This prevents us + * tearing down a completed connection in the case where we were slow + * to check the timeout (e.g. process descheduled during this loop). + * We set connect_timeout=FALSE to do this. */ + /* we need to wait for the connect state as only then is the start time stored, but we must not check already completed handles */ - timeout_ms = Curl_timeleft(data, nowp, - (data->mstate <= MSTATE_DO)? - TRUE:FALSE); - - if(timeout_ms < 0) { - /* Handle timed out */ - if(data->mstate == MSTATE_RESOLVING) - failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*nowp, data->progress.t_startsingle)); - else if(data->mstate == MSTATE_CONNECTING) - failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*nowp, data->progress.t_startsingle)); - else { - struct SingleRequest *k = &data->req; - if(k->size != -1) { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(*nowp, data->progress.t_startsingle), - k->bytecount, k->size); - } - else { - failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds with %" CURL_FORMAT_CURL_OFF_T - " bytes received", - Curl_timediff(*nowp, data->progress.t_startsingle), - k->bytecount); - } - } - - /* Force connection closed if the connection has indeed been used */ - if(data->mstate > MSTATE_DO) { - streamclose(data->conn, "Disconnected with pending data"); - stream_error = TRUE; - } - result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(data, result, TRUE); + if(multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) { /* Skip the statemachine and go directly to error handling section. */ goto statemachine_end; } @@ -2422,6 +2442,21 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, default: return CURLM_INTERNAL_ERROR; } + + if(data->conn && + data->mstate >= MSTATE_CONNECT && + data->mstate < MSTATE_DO && + rc != CURLM_CALL_MULTI_PERFORM && + !multi_ischanged(multi, false)) { + /* We now handle stream timeouts if and only if this will be the last + * loop iteration. We only check this on the last iteration to ensure + * that if we know we have additional work to do immediately + * (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before + * declaring the connection timed out as we may almost have a completed + * connection. */ + multi_handle_timeout(data, nowp, &stream_error, &result, TRUE); + } + statemachine_end: if(data->mstate < MSTATE_COMPLETED) { |