]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
multi: make multi_runsingle use sub functions for states
authorDaniel Stenberg <daniel@haxx.se>
Tue, 5 May 2026 13:02:32 +0000 (15:02 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 5 May 2026 15:54:54 +0000 (17:54 +0200)
The state machine now calls dedicated sub functions for each state, to
reduce the size and complexity.

Closes #21506

lib/multi.c

index 7520253d702c108c96e26d1b2e123586bac3a243..5e5c3194de3c852e4239b4db9892f3c3e417be80 100644 (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 multi_runsingle(struct Curl_multi *multi,
-                                 struct Curl_easy *data,
-                                 struct Curl_sigpipe_ctx *sigpipe_ctx)
+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)
+{
   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: