Commit 34770b8a authored by Yang Tse's avatar Yang Tse
Browse files

multi.c: OOM handling fixes

Prevent modification of easy handle being added with curl_multi_add_handle()
unless this function actually suceeds.

Run Curl_posttransfer() to allow restoring of SIGPIPE handler when
Curl_connect() fails early in multi_runsingle().
parent 880cf0be
Loading
Loading
Loading
Loading
+77 −61
Original line number Diff line number Diff line
@@ -135,7 +135,7 @@ struct Curl_multi {
     this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */
  long type;

  /* We have a linked list with easy handles */
  /* We have a doubly-linked circular list with easy handles */
  struct Curl_one_easy easy;

  int num_easy; /* amount of entries in the linked list above. */
@@ -440,11 +440,12 @@ CURLM *curl_multi_init(void)
CURLMcode curl_multi_add_handle(CURLM *multi_handle,
                                CURL *easy_handle)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
  struct curl_llist *timeoutlist;
  struct Curl_one_easy *easy;
  struct closure *cl;
  struct closure *prev = NULL;
  struct SessionHandle *data = easy_handle;
  struct Curl_multi *multi = (struct Curl_multi *)multi_handle;
  struct SessionHandle *data = (struct SessionHandle *)easy_handle;

  /* First, make some basic checks that the CURLM handle is a good handle */
  if(!GOOD_MULTI_HANDLE(multi))
@@ -454,39 +455,74 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
  if(!GOOD_EASY_HANDLE(easy_handle))
    return CURLM_BAD_EASY_HANDLE;

  /* Prevent users to add the same handle more than once! */
  if(((struct SessionHandle *)easy_handle)->multi)
  /* Prevent users from adding same easy handle more than
     once and prevent adding to more than one multi stack */
  if(data->multi)
    /* possibly we should create a new unique error code for this condition */
    return CURLM_BAD_EASY_HANDLE;

  data->state.timeoutlist = Curl_llist_alloc(multi_freetimeout);
  if(!data->state.timeoutlist)
  /* We want the connection cache to have plenty of room. Before we supported
     the shared cache every single easy handle had 5 entries in their cache
     by default. */
  if(((multi->num_easy + 1) * 4) > multi->connc->num) {
    long newmax = (multi->num_easy + 1) * 4;

    if(multi->maxconnects && (newmax > multi->maxconnects))
      /* don't grow beyond the allowed size */
      newmax = multi->maxconnects;

    if(newmax > multi->connc->num) {
      /* we only do this is we can in fact grow the cache */
      CURLcode res = Curl_ch_connc(data, multi->connc, newmax);
      if(res)
        return CURLM_OUT_OF_MEMORY;
    }
  }

  /* Allocate and initialize timeout list for easy handle */
  timeoutlist = Curl_llist_alloc(multi_freetimeout);
  if(!timeoutlist)
    return CURLM_OUT_OF_MEMORY;

  /* Now, time to add an easy handle to the multi stack */
  /* Allocate new node for the doubly-linked circular list of
     Curl_one_easy structs that holds pointers to easy handles */
  easy = calloc(1, sizeof(struct Curl_one_easy));
  if(!easy)
  if(!easy) {
    Curl_llist_destroy(timeoutlist, NULL);
    return CURLM_OUT_OF_MEMORY;
  }

  /*
  ** No failure allowed in this function beyond this point. And
  ** no modification of easy nor multi handle allowed before this
  ** except for potential multi's connection cache growing which
  ** won't be undone in this function no matter what.
  */

  /* Make easy handle use timeout list initialized above */
  data->state.timeoutlist = timeoutlist;
  timeoutlist = NULL;

  /* Remove handle from the list of 'closure handles' in case it is there */
  cl = multi->closure;
  while(cl) {
    struct closure *next = cl->next;
    if(cl->easy_handle == (struct SessionHandle *)easy_handle) {
      /* remove this handle from the closure list */
    if(cl->easy_handle == data) {
      /* Remove node from list */
      free(cl);
      if(prev)
        prev->next = next;
      else
        multi->closure = next;
      break; /* no need to continue since this handle can only be present once
                in the list */
      /* No need to continue, handle can only be present once in the list */
      break;
    }
    prev = cl;
    cl = next;
  }

  /* set the easy handle */
  easy->easy_handle = easy_handle;
  easy->easy_handle = data;
  multistate(easy, CURLM_STATE_INIT);

  /* set the back pointer to one_easy to assist in removal */
@@ -507,25 +543,21 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
    easy->easy_handle->dns.hostcachetype = HCACHE_MULTI;
  }

  if(easy->easy_handle->state.connc) {
    if(easy->easy_handle->state.connc->type == CONNCACHE_PRIVATE) {
      /* kill old private version */
  /* On a multi stack the connection cache, owned by the multi handle,
     is shared between all easy handles within the multi handle. */
  if(easy->easy_handle->state.connc &&
     (easy->easy_handle->state.connc->type == CONNCACHE_PRIVATE)) {
    /* kill old private connection cache */
    Curl_rm_connc(easy->easy_handle->state.connc);
      /* point out our shared one instead */
      easy->easy_handle->state.connc = multi->connc;
    }
    /* else it is already using multi? */
    easy->easy_handle->state.connc = NULL;
  }
  else
    /* point out our shared one */
  /* Point now to this multi's connection cache */
  easy->easy_handle->state.connc = multi->connc;

  /* Make sure the type is setup correctly */
  easy->easy_handle->state.connc->type = CONNCACHE_MULTI;

  /* This adds the new entry at the back of the list
     to try and maintain a FIFO queue so the pipelined
     requests are in order. */
  /* This adds the new entry at the 'end' of the doubly-linked circular
     list of Curl_one_easy structs to try and maintain a FIFO queue so
     the pipelined requests are in order. */

  /* We add this new entry last in the list. We make our 'next' point to the
     'first' struct and our 'prev' point to the previous 'prev' */
@@ -539,6 +571,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
     the new node */
  easy->prev->next = easy;

  /* make the SessionHandle refer back to this multi handle */
  Curl_easy_addmulti(easy_handle, multi_handle);

  /* make the SessionHandle struct refer back to this struct */
@@ -555,27 +588,6 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
  /* increase the node-counter */
  multi->num_easy++;

  if((multi->num_easy * 4) > multi->connc->num) {
    /* We want the connection cache to have plenty room. Before we supported
       the shared cache every single easy handle had 5 entries in their cache
       by default. */
    long newmax = multi->num_easy * 4;

    if(multi->maxconnects && (multi->maxconnects < newmax))
      /* don't grow beyond the allowed size */
      newmax = multi->maxconnects;

    if(newmax > multi->connc->num) {
      /* we only do this is we can in fact grow the cache */
      CURLcode res = Curl_ch_connc(easy_handle, multi->connc, newmax);
      if(res != CURLE_OK) {
        /* FIXME: may need to do more cleanup here */
        curl_multi_remove_handle(multi_handle, easy_handle);
        return CURLM_OUT_OF_MEMORY;
      }
    }
  }

  /* increase the alive-counter */
  multi->num_alive++;

@@ -1622,7 +1634,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
      return CURLM_INTERNAL_ERROR;
    }

    if(CURLM_STATE_COMPLETED > easy->state) {
    if(easy->state < CURLM_STATE_COMPLETED) {
      if(CURLE_OK != easy->result) {
        /*
         * If an error was returned, and we aren't in completed state now,
@@ -1646,7 +1658,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
                                        easy->easy_conn->done_pipe);
          /* Check if we can move pending requests to send pipe */
          checkPendPipeline(easy->easy_conn);
        }

          if(disconnect_conn) {
            /* disconnect properly */
@@ -1657,6 +1668,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
               failure is detected */
            easy->easy_conn = NULL;
          }
        }
        else if(easy->state == CURLM_STATE_CONNECT) {
          /* Curl_connect() failed */
          (void)Curl_posttransfer(data);
        }

        multistate(easy, CURLM_STATE_COMPLETED);
      }