mirror of
https://github.com/curl/curl.git
synced 2026-05-15 12:16:20 +03:00
multi: make multi_runsingle use sub functions for states
The state machine now calls dedicated sub functions for each state, to reduce the size and complexity. Closes #21506
This commit is contained in:
parent
481c9d46f1
commit
2c81cf620e
1 changed files with 254 additions and 184 deletions
438
lib/multi.c
438
lib/multi.c
|
|
@ -2000,9 +2000,9 @@ static CURLcode mspeed_check(struct Curl_easy *data)
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static CURLMcode state_performing(struct Curl_easy *data,
|
||||
bool *stream_errorp,
|
||||
CURLcode *resultp)
|
||||
static CURLMcode multistate_performing(struct Curl_easy *data,
|
||||
bool *stream_errorp,
|
||||
CURLcode *resultp)
|
||||
{
|
||||
char *newurl = NULL;
|
||||
bool retry = FALSE;
|
||||
|
|
@ -2139,9 +2139,9 @@ static CURLMcode state_performing(struct Curl_easy *data,
|
|||
return mresult;
|
||||
}
|
||||
|
||||
static CURLMcode state_do(struct Curl_easy *data,
|
||||
bool *stream_errorp,
|
||||
CURLcode *resultp)
|
||||
static CURLMcode multistate_do(struct Curl_easy *data,
|
||||
bool *stream_errorp,
|
||||
CURLcode *resultp)
|
||||
{
|
||||
CURLMcode mresult = CURLM_OK;
|
||||
CURLcode result = CURLE_OK;
|
||||
|
|
@ -2276,8 +2276,8 @@ end:
|
|||
return mresult;
|
||||
}
|
||||
|
||||
static CURLMcode state_ratelimiting(struct Curl_easy *data,
|
||||
CURLcode *resultp)
|
||||
static CURLMcode multistate_ratelimiting(struct Curl_easy *data,
|
||||
CURLcode *resultp)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
CURLMcode mresult = CURLM_OK;
|
||||
|
|
@ -2301,9 +2301,9 @@ static CURLMcode state_ratelimiting(struct Curl_easy *data,
|
|||
return mresult;
|
||||
}
|
||||
|
||||
static CURLMcode state_connect(struct Curl_multi *multi,
|
||||
struct Curl_easy *data,
|
||||
CURLcode *resultp)
|
||||
static CURLMcode multistate_connect(struct Curl_multi *multi,
|
||||
struct Curl_easy *data,
|
||||
CURLcode *resultp)
|
||||
{
|
||||
/* Connect. We want to get a connection identifier filled in. This state can
|
||||
be entered from SETUP and from PENDING. */
|
||||
|
|
@ -2449,16 +2449,240 @@ static void handle_completed(struct Curl_multi *multi,
|
|||
multi_assess_wakeup(multi);
|
||||
}
|
||||
|
||||
static CURLMcode multistate_init(struct Curl_easy *data, CURLcode *result)
|
||||
{
|
||||
*result = Curl_pretransfer(data);
|
||||
if(*result)
|
||||
return CURLM_OK;
|
||||
|
||||
/* after init, go SETUP */
|
||||
multistate(data, MSTATE_SETUP);
|
||||
Curl_pgrsTime(data, TIMER_STARTOP);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_setup(struct Curl_easy *data)
|
||||
{
|
||||
Curl_pgrsTime(data, TIMER_STARTSINGLE);
|
||||
if(data->set.timeout)
|
||||
Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT);
|
||||
if(data->set.connecttimeout)
|
||||
/* Since a connection might go to pending and back to CONNECT several
|
||||
times before it actually takes off, we need to set the timeout once
|
||||
in SETUP before we enter CONNECT the first time. */
|
||||
Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT);
|
||||
|
||||
multistate(data, MSTATE_CONNECT);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_connecting(struct Curl_easy *data,
|
||||
bool *stream_error,
|
||||
CURLcode *result)
|
||||
{
|
||||
bool connected;
|
||||
|
||||
if(!data->conn) {
|
||||
DEBUGASSERT(0);
|
||||
*result = CURLE_FAILED_INIT;
|
||||
return CURLM_OK;
|
||||
}
|
||||
if(!Curl_xfer_recv_is_paused(data)) {
|
||||
*result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected);
|
||||
if(connected && !(*result)) {
|
||||
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);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
else if(*result) {
|
||||
/* failure detected */
|
||||
CURL_TRC_M(data, "connect failed -> %d", *result);
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, *result, TRUE);
|
||||
*stream_error = TRUE;
|
||||
return CURLM_OK;
|
||||
}
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_protoconnect(struct Curl_easy *data,
|
||||
bool *stream_error,
|
||||
CURLcode *result)
|
||||
{
|
||||
bool protocol_connected = FALSE;
|
||||
|
||||
if(!(*result) && data->conn->bits.reuse) {
|
||||
/* ftp seems to hang when protoconnect on reused connection since we
|
||||
* handle PROTOCONNECT in general inside the filters, it seems wrong to
|
||||
* restart this on a reused connection.
|
||||
*/
|
||||
multistate(data, MSTATE_DO);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
if(!(*result))
|
||||
*result = protocol_connect(data, &protocol_connected);
|
||||
if(!(*result) && !protocol_connected) {
|
||||
/* switch to waiting state */
|
||||
multistate(data, MSTATE_PROTOCONNECTING);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
else if(!(*result)) {
|
||||
/* protocol connect has completed, go WAITDO or DO */
|
||||
multistate(data, MSTATE_DO);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, *result, TRUE);
|
||||
*stream_error = TRUE;
|
||||
return CURLM_OK;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_protoconnecting(struct Curl_easy *data,
|
||||
bool *stream_error,
|
||||
CURLcode *result)
|
||||
{
|
||||
bool protocol_connected = FALSE;
|
||||
|
||||
/* protocol-specific connect phase */
|
||||
*result = protocol_connecting(data, &protocol_connected);
|
||||
if(!(*result) && protocol_connected) {
|
||||
/* after the connect has completed, go WAITDO or DO */
|
||||
multistate(data, MSTATE_DO);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
else if(*result) {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, *result, TRUE);
|
||||
*stream_error = TRUE;
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_doing(struct Curl_easy *data,
|
||||
bool *stream_error,
|
||||
CURLcode *result)
|
||||
{
|
||||
bool dophase_done = FALSE;
|
||||
|
||||
/* we continue DOING until the DO phase is complete */
|
||||
DEBUGASSERT(data->conn);
|
||||
*result = protocol_doing(data, &dophase_done);
|
||||
if(!(*result)) {
|
||||
if(dophase_done) {
|
||||
/* after DO, go DO_DONE or DO_MORE */
|
||||
multistate(data, data->conn->bits.do_more ?
|
||||
MSTATE_DOING_MORE : MSTATE_DID);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
} /* dophase_done */
|
||||
}
|
||||
else {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, *result, FALSE);
|
||||
*stream_error = TRUE;
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_doing_more(struct Curl_easy *data,
|
||||
bool *stream_error,
|
||||
CURLcode *result)
|
||||
{
|
||||
int control;
|
||||
|
||||
/*
|
||||
* When we are connected, DOING MORE and then go DID
|
||||
*/
|
||||
DEBUGASSERT(data->conn);
|
||||
*result = multi_do_more(data, &control);
|
||||
|
||||
if(!(*result)) {
|
||||
if(control) {
|
||||
/* if positive, advance to DO_DONE
|
||||
if negative, go back to DOING */
|
||||
multistate(data, control == 1 ? MSTATE_DID : MSTATE_DOING);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
/* else
|
||||
stay in DO_MORE */
|
||||
}
|
||||
else {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, *result, FALSE);
|
||||
*stream_error = TRUE;
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_did(struct Curl_multi *multi,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
DEBUGASSERT(data->conn);
|
||||
if(data->conn->bits.multiplex)
|
||||
/* Check if we can move pending requests to send pipe */
|
||||
process_pending_handles(multi); /* multiplexed */
|
||||
|
||||
/* Only perform the transfer if there is a good socket to work with.
|
||||
Having both BAD is a signal to skip immediately to DONE */
|
||||
if(CONN_SOCK_IDX_VALID(data->conn->recv_idx) ||
|
||||
CONN_SOCK_IDX_VALID(data->conn->send_idx))
|
||||
multistate(data, MSTATE_PERFORMING);
|
||||
else {
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
if(data->state.wildcardmatch &&
|
||||
((data->conn->scheme->flags & PROTOPT_WILDCARD) == 0)) {
|
||||
data->wildcard->state = CURLWC_DONE;
|
||||
}
|
||||
#endif
|
||||
multistate(data, MSTATE_DONE);
|
||||
}
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
|
||||
static CURLMcode multistate_done(struct Curl_easy *data, CURLcode *result)
|
||||
{
|
||||
if(data->conn) {
|
||||
CURLcode res;
|
||||
|
||||
/* post-transfer command */
|
||||
res = multi_done(data, *result, FALSE);
|
||||
|
||||
/* allow a previously set error code take precedence */
|
||||
if(!(*result))
|
||||
*result = res;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
if(data->state.wildcardmatch) {
|
||||
if(data->wildcard->state != CURLWC_DONE) {
|
||||
/* if a wildcard is set and we are not ending -> lets start again
|
||||
with MSTATE_INIT */
|
||||
multistate(data, MSTATE_INIT);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* after we have DONE what we are supposed to do, go COMPLETED, and
|
||||
it does not matter what the multi_done() returned! */
|
||||
multistate(data, MSTATE_COMPLETED);
|
||||
return CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
|
||||
static CURLMcode multi_runsingle(struct Curl_multi *multi,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_sigpipe_ctx *sigpipe_ctx)
|
||||
{
|
||||
bool connected;
|
||||
bool protocol_connected = FALSE;
|
||||
bool dophase_done = FALSE;
|
||||
CURLMcode mresult;
|
||||
CURLcode result = CURLE_OK;
|
||||
int control;
|
||||
|
||||
if(!GOOD_EASY_HANDLE(data))
|
||||
return CURLM_BAD_EASY_HANDLE;
|
||||
|
|
@ -2517,217 +2741,63 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
|
|||
case MSTATE_INIT:
|
||||
/* Transitional state. init this transfer. A handle never comes back to
|
||||
this state. */
|
||||
result = Curl_pretransfer(data);
|
||||
if(result)
|
||||
break;
|
||||
|
||||
/* after init, go SETUP */
|
||||
multistate(data, MSTATE_SETUP);
|
||||
Curl_pgrsTime(data, TIMER_STARTOP);
|
||||
FALLTHROUGH();
|
||||
mresult = multistate_init(data, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_SETUP:
|
||||
/* Transitional state. Setup things for a new transfer. The handle
|
||||
can come back to this state on a redirect. */
|
||||
Curl_pgrsTime(data, TIMER_STARTSINGLE);
|
||||
if(data->set.timeout)
|
||||
Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT);
|
||||
if(data->set.connecttimeout)
|
||||
/* Since a connection might go to pending and back to CONNECT several
|
||||
times before it actually takes off, we need to set the timeout once
|
||||
in SETUP before we enter CONNECT the first time. */
|
||||
Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT);
|
||||
|
||||
multistate(data, MSTATE_CONNECT);
|
||||
FALLTHROUGH();
|
||||
mresult = multistate_setup(data);
|
||||
break;
|
||||
|
||||
case MSTATE_CONNECT:
|
||||
mresult = state_connect(multi, data, &result);
|
||||
mresult = multistate_connect(multi, data, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_CONNECTING:
|
||||
/* awaiting a completion of an asynch TCP connect */
|
||||
if(!data->conn) {
|
||||
DEBUGASSERT(0);
|
||||
result = CURLE_FAILED_INIT;
|
||||
break;
|
||||
}
|
||||
else if(!Curl_xfer_recv_is_paused(data)) {
|
||||
result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected);
|
||||
if(connected && !result) {
|
||||
if(!data->conn->bits.reuse &&
|
||||
Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
|
||||
/* new connection, can multiplex, wake pending handles */
|
||||
process_pending_handles(data->multi);
|
||||
}
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
multistate(data, MSTATE_PROTOCONNECT);
|
||||
}
|
||||
else if(result) {
|
||||
/* failure detected */
|
||||
CURL_TRC_M(data, "connect failed -> %d", result);
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, result, TRUE);
|
||||
stream_error = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mresult = multistate_connecting(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
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.
|
||||
*/
|
||||
multistate(data, MSTATE_DO);
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
break;
|
||||
}
|
||||
if(!result)
|
||||
result = protocol_connect(data, &protocol_connected);
|
||||
if(!result && !protocol_connected) {
|
||||
/* switch to waiting state */
|
||||
multistate(data, MSTATE_PROTOCONNECTING);
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
else if(!result) {
|
||||
/* protocol connect has completed, go WAITDO or DO */
|
||||
multistate(data, MSTATE_DO);
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
else {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, result, TRUE);
|
||||
stream_error = TRUE;
|
||||
}
|
||||
mresult = multistate_protoconnect(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_PROTOCONNECTING:
|
||||
/* protocol-specific connect phase */
|
||||
result = protocol_connecting(data, &protocol_connected);
|
||||
if(!result && protocol_connected) {
|
||||
/* after the connect has completed, go WAITDO or DO */
|
||||
multistate(data, MSTATE_DO);
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
else if(result) {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, result, TRUE);
|
||||
stream_error = TRUE;
|
||||
}
|
||||
mresult = multistate_protoconnecting(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_DO:
|
||||
mresult = state_do(data, &stream_error, &result);
|
||||
mresult = multistate_do(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_DOING:
|
||||
/* we continue DOING until the DO phase is complete */
|
||||
DEBUGASSERT(data->conn);
|
||||
result = protocol_doing(data, &dophase_done);
|
||||
if(!result) {
|
||||
if(dophase_done) {
|
||||
/* after DO, go DO_DONE or DO_MORE */
|
||||
multistate(data, data->conn->bits.do_more ?
|
||||
MSTATE_DOING_MORE : MSTATE_DID);
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
} /* dophase_done */
|
||||
}
|
||||
else {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, result, FALSE);
|
||||
stream_error = TRUE;
|
||||
}
|
||||
mresult = multistate_doing(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_DOING_MORE:
|
||||
/*
|
||||
* When we are connected, DOING MORE and then go DID
|
||||
*/
|
||||
DEBUGASSERT(data->conn);
|
||||
result = multi_do_more(data, &control);
|
||||
|
||||
if(!result) {
|
||||
if(control) {
|
||||
/* if positive, advance to DO_DONE
|
||||
if negative, go back to DOING */
|
||||
multistate(data, control == 1 ? MSTATE_DID : MSTATE_DOING);
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
}
|
||||
/* else
|
||||
stay in DO_MORE */
|
||||
}
|
||||
else {
|
||||
/* failure detected */
|
||||
multi_posttransfer(data);
|
||||
multi_done(data, result, FALSE);
|
||||
stream_error = TRUE;
|
||||
}
|
||||
mresult = multistate_doing_more(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_DID:
|
||||
DEBUGASSERT(data->conn);
|
||||
if(data->conn->bits.multiplex)
|
||||
/* Check if we can move pending requests to send pipe */
|
||||
process_pending_handles(multi); /* multiplexed */
|
||||
|
||||
/* Only perform the transfer if there is a good socket to work with.
|
||||
Having both BAD is a signal to skip immediately to DONE */
|
||||
if(CONN_SOCK_IDX_VALID(data->conn->recv_idx) ||
|
||||
CONN_SOCK_IDX_VALID(data->conn->send_idx))
|
||||
multistate(data, MSTATE_PERFORMING);
|
||||
else {
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
if(data->state.wildcardmatch &&
|
||||
((data->conn->scheme->flags & PROTOPT_WILDCARD) == 0)) {
|
||||
data->wildcard->state = CURLWC_DONE;
|
||||
}
|
||||
#endif
|
||||
multistate(data, MSTATE_DONE);
|
||||
}
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
mresult = multistate_did(multi, data);
|
||||
break;
|
||||
|
||||
case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */
|
||||
mresult = state_ratelimiting(data, &result);
|
||||
mresult = multistate_ratelimiting(data, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_PERFORMING:
|
||||
mresult = state_performing(data, &stream_error, &result);
|
||||
mresult = multistate_performing(data, &stream_error, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_DONE:
|
||||
/* this state is highly transient, so run another loop after this */
|
||||
mresult = CURLM_CALL_MULTI_PERFORM;
|
||||
|
||||
if(data->conn) {
|
||||
CURLcode res;
|
||||
|
||||
/* post-transfer command */
|
||||
res = multi_done(data, result, FALSE);
|
||||
|
||||
/* allow a previously set error code take precedence */
|
||||
if(!result)
|
||||
result = res;
|
||||
}
|
||||
|
||||
#ifndef CURL_DISABLE_FTP
|
||||
if(data->state.wildcardmatch) {
|
||||
if(data->wildcard->state != CURLWC_DONE) {
|
||||
/* if a wildcard is set and we are not ending -> lets start again
|
||||
with MSTATE_INIT */
|
||||
multistate(data, MSTATE_INIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* after we have DONE what we are supposed to do, go COMPLETED, and
|
||||
it does not matter what the multi_done() returned! */
|
||||
multistate(data, MSTATE_COMPLETED);
|
||||
mresult = multistate_done(data, &result);
|
||||
break;
|
||||
|
||||
case MSTATE_COMPLETED:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue