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:
Daniel Stenberg 2026-05-05 15:02:32 +02:00
parent 481c9d46f1
commit 2c81cf620e
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -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: