multi: split multi_runsingle into sub functions

Introduce five functions named after the state they serve:

- state_connect for MSTATE_CONNECT
- state_do for MSTATE_DO
- state_performing for MSTATE_PERFORMING
- state_ratelimiting for MSTATE_RATELIMITING
- state_resolving for MSTATE_RESOLVING

Closes #15418
This commit is contained in:
Daniel Stenberg 2024-10-26 22:12:41 +02:00
parent 522c89a134
commit e77326403d
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2

View File

@ -2116,19 +2116,490 @@ static CURLcode multi_follow(struct Curl_easy *data,
#endif /* CURL_DISABLE_HTTP */
}
static CURLMcode state_performing(struct Curl_easy *data,
struct curltime *nowp,
bool *stream_errorp,
CURLcode *resultp)
{
char *newurl = NULL;
bool retry = FALSE;
timediff_t recv_timeout_ms = 0;
timediff_t send_timeout_ms = 0;
CURLMcode rc = CURLM_OK;
CURLcode result = *resultp = CURLE_OK;
*stream_errorp = FALSE;
/* check if over send speed */
if(data->set.max_send_speed)
send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul,
data->set.max_send_speed,
*nowp);
/* check if over recv speed */
if(data->set.max_recv_speed)
recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl,
data->set.max_recv_speed,
*nowp);
if(send_timeout_ms || recv_timeout_ms) {
Curl_ratelimit(data, *nowp);
multistate(data, MSTATE_RATELIMITING);
if(send_timeout_ms >= recv_timeout_ms)
Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
else
Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
return CURLM_OK;
}
/* read/write data if it is ready to do so */
result = Curl_sendrecv(data, nowp);
if(data->req.done || (result == CURLE_RECV_ERROR)) {
/* If CURLE_RECV_ERROR happens early enough, we assume it was a race
* condition and the server closed the reused connection exactly when we
* wanted to use it, so figure out if that is indeed the case.
*/
CURLcode ret = Curl_retry_request(data, &newurl);
if(!ret)
retry = (newurl) ? TRUE : FALSE;
else if(!result)
result = ret;
if(retry) {
/* if we are to retry, set the result to OK and consider the
request as done */
result = CURLE_OK;
data->req.done = TRUE;
}
}
else if((CURLE_HTTP2_STREAM == result) &&
Curl_h2_http_1_1_error(data)) {
CURLcode ret = Curl_retry_request(data, &newurl);
if(!ret) {
infof(data, "Downgrades to HTTP/1.1");
streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1");
data->state.httpwant = CURL_HTTP_VERSION_1_1;
/* clear the error message bit too as we ignore the one we got */
data->state.errorbuf = FALSE;
if(!newurl)
/* typically for HTTP_1_1_REQUIRED error on first flight */
newurl = strdup(data->state.url);
/* if we are to retry, set the result to OK and consider the request
as done */
retry = TRUE;
result = CURLE_OK;
data->req.done = TRUE;
}
else
result = ret;
}
if(result) {
/*
* The transfer phase returned error, we mark the connection to get closed
* to prevent being reused. This is because we cannot possibly know if the
* connection is in a good shape or not now. Unless it is a protocol which
* uses two "channels" like FTP, as then the error happened in the data
* connection.
*/
if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
result != CURLE_HTTP2_STREAM)
streamclose(data->conn, "Transfer returned error");
multi_posttransfer(data);
multi_done(data, result, TRUE);
}
else if(data->req.done && !Curl_cwriter_is_paused(data)) {
/* call this even if the readwrite function returned error */
multi_posttransfer(data);
/* When we follow redirects or is set to retry the connection, we must to
go back to the CONNECT state */
if(data->req.newurl || retry) {
followtype follow = FOLLOW_NONE;
if(!retry) {
/* if the URL is a follow-location and not just a retried request then
figure out the URL here */
free(newurl);
newurl = data->req.newurl;
data->req.newurl = NULL;
follow = FOLLOW_REDIR;
}
else
follow = FOLLOW_RETRY;
(void)multi_done(data, CURLE_OK, FALSE);
/* multi_done() might return CURLE_GOT_NOTHING */
result = multi_follow(data, newurl, follow);
if(!result) {
multistate(data, MSTATE_SETUP);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
else {
/* after the transfer is done, go DONE */
/* but first check to see if we got a location info even though we are
not following redirects */
if(data->req.location) {
free(newurl);
newurl = data->req.location;
data->req.location = NULL;
result = multi_follow(data, newurl, FOLLOW_FAKE);
if(result) {
*stream_errorp = TRUE;
result = multi_done(data, result, TRUE);
}
}
if(!result) {
multistate(data, MSTATE_DONE);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
}
else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) {
/* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer does
not get stuck on this transfer at the expense of other concurrent
transfers */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
free(newurl);
*resultp = result;
return rc;
}
static CURLMcode state_do(struct Curl_easy *data,
bool *stream_errorp,
CURLcode *resultp)
{
CURLMcode rc = CURLM_OK;
CURLcode result = CURLE_OK;
if(data->set.fprereq) {
int prereq_rc;
/* call the prerequest callback function */
Curl_set_in_callback(data, TRUE);
prereq_rc = data->set.fprereq(data->set.prereq_userp,
data->info.primary.remote_ip,
data->info.primary.local_ip,
data->info.primary.remote_port,
data->info.primary.local_port);
Curl_set_in_callback(data, FALSE);
if(prereq_rc != CURL_PREREQFUNC_OK) {
failf(data, "operation aborted by pre-request callback");
/* failure in pre-request callback - do not do any other processing */
result = CURLE_ABORTED_BY_CALLBACK;
multi_posttransfer(data);
multi_done(data, result, FALSE);
*stream_errorp = TRUE;
goto end;
}
}
if(data->set.connect_only == 1) {
/* keep connection open for application to use the socket */
connkeep(data->conn, "CONNECT_ONLY");
multistate(data, MSTATE_DONE);
result = CURLE_OK;
rc = CURLM_CALL_MULTI_PERFORM;
}
else {
bool dophase_done = FALSE;
/* Perform the protocol's DO action */
result = multi_do(data, &dophase_done);
/* When multi_do() returns failure, data->conn might be NULL! */
if(!result) {
if(!dophase_done) {
#ifndef CURL_DISABLE_FTP
/* some steps needed for wildcard matching */
if(data->state.wildcardmatch) {
struct WildcardData *wc = data->wildcard;
if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
/* skip some states if it is important */
multi_done(data, CURLE_OK, FALSE);
/* if there is no connection left, skip the DONE state */
multistate(data, data->conn ?
MSTATE_DONE : MSTATE_COMPLETED);
rc = CURLM_CALL_MULTI_PERFORM;
goto end;
}
}
#endif
/* DO was not completed in one function call, we must continue
DOING... */
multistate(data, MSTATE_DOING);
rc = CURLM_CALL_MULTI_PERFORM;
}
/* after DO, go DO_DONE... or DO_MORE */
else if(data->conn->bits.do_more) {
/* we are supposed to do more, but we need to sit down, relax and wait
a little while first */
multistate(data, MSTATE_DOING_MORE);
rc = CURLM_CALL_MULTI_PERFORM;
}
else {
/* we are done with the DO, now DID */
multistate(data, MSTATE_DID);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
else if((CURLE_SEND_ERROR == result) &&
data->conn->bits.reuse) {
/*
* In this situation, a connection that we were trying to use may have
* unexpectedly died. If possible, send the connection back to the
* CONNECT phase so we can try again.
*/
char *newurl = NULL;
followtype follow = FOLLOW_NONE;
CURLcode drc;
drc = Curl_retry_request(data, &newurl);
if(drc) {
/* a failure here pretty much implies an out of memory */
result = drc;
*stream_errorp = TRUE;
}
multi_posttransfer(data);
drc = multi_done(data, result, FALSE);
/* When set to retry the connection, we must go back to the CONNECT
* state */
if(newurl) {
if(!drc || (drc == CURLE_SEND_ERROR)) {
follow = FOLLOW_RETRY;
drc = multi_follow(data, newurl, follow);
if(!drc) {
multistate(data, MSTATE_SETUP);
rc = CURLM_CALL_MULTI_PERFORM;
result = CURLE_OK;
}
else {
/* Follow failed */
result = drc;
}
}
else {
/* done did not return OK or SEND_ERROR */
result = drc;
}
}
else {
/* Have error handler disconnect conn if we cannot retry */
*stream_errorp = TRUE;
}
free(newurl);
}
else {
/* failure detected */
multi_posttransfer(data);
if(data->conn)
multi_done(data, result, FALSE);
*stream_errorp = TRUE;
}
}
end:
*resultp = result;
return rc;
}
static CURLMcode state_ratelimiting(struct Curl_easy *data,
struct curltime *nowp,
CURLcode *resultp)
{
CURLcode result = CURLE_OK;
CURLMcode rc = CURLM_OK;
DEBUGASSERT(data->conn);
/* if both rates are within spec, resume transfer */
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, *nowp);
if(result) {
if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
result != CURLE_HTTP2_STREAM)
streamclose(data->conn, "Transfer returned error");
multi_posttransfer(data);
multi_done(data, result, TRUE);
}
else {
timediff_t recv_timeout_ms = 0;
timediff_t send_timeout_ms = 0;
if(data->set.max_send_speed)
send_timeout_ms =
Curl_pgrsLimitWaitTime(&data->progress.ul,
data->set.max_send_speed,
*nowp);
if(data->set.max_recv_speed)
recv_timeout_ms =
Curl_pgrsLimitWaitTime(&data->progress.dl,
data->set.max_recv_speed,
*nowp);
if(!send_timeout_ms && !recv_timeout_ms) {
multistate(data, MSTATE_PERFORMING);
Curl_ratelimit(data, *nowp);
/* start performing again right away */
rc = CURLM_CALL_MULTI_PERFORM;
}
else if(send_timeout_ms >= recv_timeout_ms)
Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
else
Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
}
*resultp = result;
return rc;
}
static CURLMcode state_resolving(struct Curl_multi *multi,
struct Curl_easy *data,
bool *stream_errorp,
CURLcode *resultp)
{
struct Curl_dns_entry *dns = NULL;
struct connectdata *conn = data->conn;
const char *hostname;
CURLcode result = CURLE_OK;
CURLMcode rc = CURLM_OK;
DEBUGASSERT(conn);
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy)
hostname = conn->http_proxy.host.name;
else
#endif
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
/* check if we have the name resolved by now */
dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port);
if(dns) {
#ifdef CURLRES_ASYNCH
data->state.async.dns = dns;
data->state.async.done = TRUE;
#endif
result = CURLE_OK;
infof(data, "Hostname '%s' was found in DNS cache", hostname);
}
if(!dns)
result = Curl_resolv_check(data, &dns);
/* Update sockets here, because the socket(s) may have been closed and the
application thus needs to be told, even if it is likely that the same
socket(s) will again be used further down. If the name has not yet been
resolved, it is likely that new sockets have been opened in an attempt to
contact another resolver. */
rc = singlesocket(multi, data);
if(rc)
return rc;
if(dns) {
bool connected;
/* Perform the next step in the connection phase, and then move on to the
WAITCONNECT state */
result = Curl_once_resolved(data, &connected);
if(result)
/* if Curl_once_resolved() returns failure, the connection struct is
already freed and gone */
data->conn = NULL; /* no more connection */
else {
/* call again please so that we get the next socket setup */
rc = CURLM_CALL_MULTI_PERFORM;
if(connected)
multistate(data, MSTATE_PROTOCONNECT);
else {
multistate(data, MSTATE_CONNECTING);
}
}
}
if(result)
/* failure detected */
*stream_errorp = TRUE;
*resultp = result;
return rc;
}
static CURLMcode state_connect(struct Curl_multi *multi,
struct Curl_easy *data,
struct curltime *nowp,
CURLcode *resultp)
{
/* Connect. We want to get a connection identifier filled in. This state can
be entered from SETUP and from PENDING. */
bool connected;
bool async;
CURLMcode rc = CURLM_OK;
CURLcode result = Curl_connect(data, &async, &connected);
if(CURLE_NO_CONNECTION_AVAILABLE == result) {
/* There was no connection available. We will go to the pending state and
wait for an available connection. */
multistate(data, MSTATE_PENDING);
/* unlink from process list */
Curl_node_remove(&data->multi_queue);
/* add handle to pending list */
Curl_llist_append(&multi->pending, data, &data->multi_queue);
*resultp = CURLE_OK;
return rc;
}
else
process_pending_handles(data->multi);
if(!result) {
*nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE);
if(async)
/* We are now waiting for an asynchronous name lookup */
multistate(data, MSTATE_RESOLVING);
else {
/* after the connect has been sent off, go WAITCONNECT unless the
protocol connect is already done and we can go directly to WAITDO or
DO! */
rc = CURLM_CALL_MULTI_PERFORM;
if(connected) {
if(!data->conn->bits.reuse &&
Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
/* new connection, can multiplex, wake pending handles */
process_pending_handles(data->multi);
}
multistate(data, MSTATE_PROTOCONNECT);
}
else {
multistate(data, MSTATE_CONNECTING);
}
}
}
*resultp = result;
return rc;
}
static CURLMcode multi_runsingle(struct Curl_multi *multi,
struct curltime *nowp,
struct Curl_easy *data)
{
struct Curl_message *msg = NULL;
bool connected;
bool async;
bool protocol_connected = FALSE;
bool dophase_done = FALSE;
CURLMcode rc;
CURLcode result = CURLE_OK;
timediff_t recv_timeout_ms;
timediff_t send_timeout_ms;
int control;
if(!GOOD_EASY_HANDLE(data))
@ -2173,8 +2644,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
switch(data->mstate) {
case MSTATE_INIT:
/* Transitional state. init this transfer. A handle never comes
back to this state. */
/* Transitional state. init this transfer. A handle never comes back to
this state. */
result = Curl_pretransfer(data);
if(result)
break;
@ -2200,119 +2671,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
FALLTHROUGH();
case MSTATE_CONNECT:
/* Connect. We want to get a connection identifier filled in. This state
can be entered from SETUP and from PENDING. */
result = Curl_connect(data, &async, &connected);
if(CURLE_NO_CONNECTION_AVAILABLE == result) {
/* There was no connection available. We will go to the pending
state and wait for an available connection. */
multistate(data, MSTATE_PENDING);
/* unlink from process list */
Curl_node_remove(&data->multi_queue);
/* add handle to pending list */
Curl_llist_append(&multi->pending, data, &data->multi_queue);
result = CURLE_OK;
break;
}
else
process_pending_handles(data->multi);
if(!result) {
*nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE);
if(async)
/* We are now waiting for an asynchronous name lookup */
multistate(data, MSTATE_RESOLVING);
else {
/* after the connect has been sent off, go WAITCONNECT unless the
protocol connect is already done and we can go directly to
WAITDO or DO! */
rc = CURLM_CALL_MULTI_PERFORM;
if(connected) {
if(!data->conn->bits.reuse &&
Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
/* new connection, can multiplex, wake pending handles */
process_pending_handles(data->multi);
}
multistate(data, MSTATE_PROTOCONNECT);
}
else {
multistate(data, MSTATE_CONNECTING);
}
}
}
rc = state_connect(multi, data, nowp, &result);
break;
case MSTATE_RESOLVING:
/* awaiting an asynch name resolve to complete */
{
struct Curl_dns_entry *dns = NULL;
struct connectdata *conn = data->conn;
const char *hostname;
DEBUGASSERT(conn);
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy)
hostname = conn->http_proxy.host.name;
else
#endif
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
/* check if we have the name resolved by now */
dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port);
if(dns) {
#ifdef CURLRES_ASYNCH
data->state.async.dns = dns;
data->state.async.done = TRUE;
#endif
result = CURLE_OK;
infof(data, "Hostname '%s' was found in DNS cache", hostname);
}
if(!dns)
result = Curl_resolv_check(data, &dns);
/* Update sockets here, because the socket(s) may have been
closed and the application thus needs to be told, even if it
is likely that the same socket(s) will again be used further
down. If the name has not yet been resolved, it is likely
that new sockets have been opened in an attempt to contact
another resolver. */
rc = singlesocket(multi, data);
if(rc)
return rc;
if(dns) {
/* Perform the next step in the connection phase, and then move on
to the WAITCONNECT state */
result = Curl_once_resolved(data, &connected);
if(result)
/* if Curl_once_resolved() returns failure, the connection struct
is already freed and gone */
data->conn = NULL; /* no more connection */
else {
/* call again please so that we get the next socket setup */
rc = CURLM_CALL_MULTI_PERFORM;
if(connected)
multistate(data, MSTATE_PROTOCONNECT);
else {
multistate(data, MSTATE_CONNECTING);
}
}
}
if(result) {
/* failure detected */
stream_error = TRUE;
break;
}
}
break;
rc = state_resolving(multi, data, &stream_error, &result);
break;
#ifndef CURL_DISABLE_HTTP
case MSTATE_TUNNELING:
@ -2353,9 +2718,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
case MSTATE_PROTOCONNECT:
if(!result && data->conn->bits.reuse) {
/* ftp seems to hang when protoconnect on reused connection
* since we handle PROTOCONNECT in general inside the filers, it
* seems wrong to restart this on a reused connection. */
/* ftp seems to hang when protoconnect on reused connection since we
* handle PROTOCONNECT in general inside the filers, it seems wrong to
* restart this on a reused connection.
*/
multistate(data, MSTATE_DO);
rc = CURLM_CALL_MULTI_PERFORM;
break;
@ -2397,135 +2763,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
case MSTATE_DO:
if(data->set.fprereq) {
int prereq_rc;
/* call the prerequest callback function */
Curl_set_in_callback(data, TRUE);
prereq_rc = data->set.fprereq(data->set.prereq_userp,
data->info.primary.remote_ip,
data->info.primary.local_ip,
data->info.primary.remote_port,
data->info.primary.local_port);
Curl_set_in_callback(data, FALSE);
if(prereq_rc != CURL_PREREQFUNC_OK) {
failf(data, "operation aborted by pre-request callback");
/* failure in pre-request callback - do not do any other
processing */
result = CURLE_ABORTED_BY_CALLBACK;
multi_posttransfer(data);
multi_done(data, result, FALSE);
stream_error = TRUE;
break;
}
}
if(data->set.connect_only == 1) {
/* keep connection open for application to use the socket */
connkeep(data->conn, "CONNECT_ONLY");
multistate(data, MSTATE_DONE);
result = CURLE_OK;
rc = CURLM_CALL_MULTI_PERFORM;
}
else {
/* Perform the protocol's DO action */
result = multi_do(data, &dophase_done);
/* When multi_do() returns failure, data->conn might be NULL! */
if(!result) {
if(!dophase_done) {
#ifndef CURL_DISABLE_FTP
/* some steps needed for wildcard matching */
if(data->state.wildcardmatch) {
struct WildcardData *wc = data->wildcard;
if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) {
/* skip some states if it is important */
multi_done(data, CURLE_OK, FALSE);
/* if there is no connection left, skip the DONE state */
multistate(data, data->conn ?
MSTATE_DONE : MSTATE_COMPLETED);
rc = CURLM_CALL_MULTI_PERFORM;
break;
}
}
#endif
/* DO was not completed in one function call, we must continue
DOING... */
multistate(data, MSTATE_DOING);
rc = CURLM_CALL_MULTI_PERFORM;
}
/* after DO, go DO_DONE... or DO_MORE */
else if(data->conn->bits.do_more) {
/* we are supposed to do more, but we need to sit down, relax
and wait a little while first */
multistate(data, MSTATE_DOING_MORE);
rc = CURLM_CALL_MULTI_PERFORM;
}
else {
/* we are done with the DO, now DID */
multistate(data, MSTATE_DID);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
else if((CURLE_SEND_ERROR == result) &&
data->conn->bits.reuse) {
/*
* In this situation, a connection that we were trying to use
* may have unexpectedly died. If possible, send the connection
* back to the CONNECT phase so we can try again.
*/
char *newurl = NULL;
followtype follow = FOLLOW_NONE;
CURLcode drc;
drc = Curl_retry_request(data, &newurl);
if(drc) {
/* a failure here pretty much implies an out of memory */
result = drc;
stream_error = TRUE;
}
multi_posttransfer(data);
drc = multi_done(data, result, FALSE);
/* When set to retry the connection, we must go back to the CONNECT
* state */
if(newurl) {
if(!drc || (drc == CURLE_SEND_ERROR)) {
follow = FOLLOW_RETRY;
drc = multi_follow(data, newurl, follow);
if(!drc) {
multistate(data, MSTATE_SETUP);
rc = CURLM_CALL_MULTI_PERFORM;
result = CURLE_OK;
}
else {
/* Follow failed */
result = drc;
}
}
else {
/* done did not return OK or SEND_ERROR */
result = drc;
}
}
else {
/* Have error handler disconnect conn if we cannot retry */
stream_error = TRUE;
}
free(newurl);
}
else {
/* failure detected */
multi_posttransfer(data);
if(data->conn)
multi_done(data, result, FALSE);
stream_error = TRUE;
}
}
rc = state_do(data, &stream_error, &result);
break;
case MSTATE_DOING:
@ -2598,195 +2836,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */
DEBUGASSERT(data->conn);
/* if both rates are within spec, resume transfer */
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, *nowp);
if(result) {
if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
result != CURLE_HTTP2_STREAM)
streamclose(data->conn, "Transfer returned error");
multi_posttransfer(data);
multi_done(data, result, TRUE);
}
else {
send_timeout_ms = 0;
if(data->set.max_send_speed)
send_timeout_ms =
Curl_pgrsLimitWaitTime(&data->progress.ul,
data->set.max_send_speed,
*nowp);
recv_timeout_ms = 0;
if(data->set.max_recv_speed)
recv_timeout_ms =
Curl_pgrsLimitWaitTime(&data->progress.dl,
data->set.max_recv_speed,
*nowp);
if(!send_timeout_ms && !recv_timeout_ms) {
multistate(data, MSTATE_PERFORMING);
Curl_ratelimit(data, *nowp);
/* start performing again right away */
rc = CURLM_CALL_MULTI_PERFORM;
}
else if(send_timeout_ms >= recv_timeout_ms)
Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
else
Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
}
rc = state_ratelimiting(data, nowp, &result);
break;
case MSTATE_PERFORMING:
{
char *newurl = NULL;
bool retry = FALSE;
/* check if over send speed */
send_timeout_ms = 0;
if(data->set.max_send_speed)
send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul,
data->set.max_send_speed,
*nowp);
/* check if over recv speed */
recv_timeout_ms = 0;
if(data->set.max_recv_speed)
recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl,
data->set.max_recv_speed,
*nowp);
if(send_timeout_ms || recv_timeout_ms) {
Curl_ratelimit(data, *nowp);
multistate(data, MSTATE_RATELIMITING);
if(send_timeout_ms >= recv_timeout_ms)
Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
else
Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST);
break;
}
/* read/write data if it is ready to do so */
result = Curl_sendrecv(data, nowp);
if(data->req.done || (result == CURLE_RECV_ERROR)) {
/* If CURLE_RECV_ERROR happens early enough, we assume it was a race
* condition and the server closed the reused connection exactly when
* we wanted to use it, so figure out if that is indeed the case.
*/
CURLcode ret = Curl_retry_request(data, &newurl);
if(!ret)
retry = (newurl) ? TRUE : FALSE;
else if(!result)
result = ret;
if(retry) {
/* if we are to retry, set the result to OK and consider the
request as done */
result = CURLE_OK;
data->req.done = TRUE;
}
}
else if((CURLE_HTTP2_STREAM == result) &&
Curl_h2_http_1_1_error(data)) {
CURLcode ret = Curl_retry_request(data, &newurl);
if(!ret) {
infof(data, "Downgrades to HTTP/1.1");
streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1");
data->state.httpwant = CURL_HTTP_VERSION_1_1;
/* clear the error message bit too as we ignore the one we got */
data->state.errorbuf = FALSE;
if(!newurl)
/* typically for HTTP_1_1_REQUIRED error on first flight */
newurl = strdup(data->state.url);
/* if we are to retry, set the result to OK and consider the request
as done */
retry = TRUE;
result = CURLE_OK;
data->req.done = TRUE;
}
else
result = ret;
}
if(result) {
/*
* The transfer phase returned error, we mark the connection to get
* closed to prevent being reused. This is because we cannot possibly
* know if the connection is in a good shape or not now. Unless it is
* a protocol which uses two "channels" like FTP, as then the error
* happened in the data connection.
*/
if(!(data->conn->handler->flags & PROTOPT_DUAL) &&
result != CURLE_HTTP2_STREAM)
streamclose(data->conn, "Transfer returned error");
multi_posttransfer(data);
multi_done(data, result, TRUE);
}
else if(data->req.done && !Curl_cwriter_is_paused(data)) {
/* call this even if the readwrite function returned error */
multi_posttransfer(data);
/* When we follow redirects or is set to retry the connection, we must
to go back to the CONNECT state */
if(data->req.newurl || retry) {
followtype follow = FOLLOW_NONE;
if(!retry) {
/* if the URL is a follow-location and not just a retried request
then figure out the URL here */
free(newurl);
newurl = data->req.newurl;
data->req.newurl = NULL;
follow = FOLLOW_REDIR;
}
else
follow = FOLLOW_RETRY;
(void)multi_done(data, CURLE_OK, FALSE);
/* multi_done() might return CURLE_GOT_NOTHING */
result = multi_follow(data, newurl, follow);
if(!result) {
multistate(data, MSTATE_SETUP);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
else {
/* after the transfer is done, go DONE */
/* but first check to see if we got a location info even though we
are not following redirects */
if(data->req.location) {
free(newurl);
newurl = data->req.location;
data->req.location = NULL;
result = multi_follow(data, newurl, FOLLOW_FAKE);
if(result) {
stream_error = TRUE;
result = multi_done(data, result, TRUE);
}
}
if(!result) {
multistate(data, MSTATE_DONE);
rc = CURLM_CALL_MULTI_PERFORM;
}
}
}
else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) {
/* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer
will not get stuck on this transfer at the expense of other
concurrent transfers */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
free(newurl);
rc = state_performing(data, nowp, &stream_error, &result);
break;
}
case MSTATE_DONE:
/* this state is highly transient, so run another loop after this */