Skip to content
multi.c 55.3 KiB
Newer Older
      /* if both rates are within spec, resume transfer */
      Curl_pgrsUpdate(easy->easy_conn);
      if ( ( ( easy->easy_handle->set.max_send_speed == 0 ) ||
             ( easy->easy_handle->progress.ulspeed <
               easy->easy_handle->set.max_send_speed ) )  &&
           ( ( easy->easy_handle->set.max_recv_speed == 0 ) ||
             ( easy->easy_handle->progress.dlspeed <
               easy->easy_handle->set.max_recv_speed ) )
        )
      multistate(easy, CURLM_STATE_PERFORM);
      /* check if over speed */
      if ( (  ( easy->easy_handle->set.max_send_speed > 0 ) &&
              ( easy->easy_handle->progress.ulspeed >
                easy->easy_handle->set.max_send_speed ) )  ||
           (  ( easy->easy_handle->set.max_recv_speed > 0 ) &&
              ( easy->easy_handle->progress.dlspeed >
                easy->easy_handle->set.max_recv_speed ) )
        ) {
        /* Transfer is over the speed limit. Change state.  TODO: Call
         * Curl_expire() with the time left until we're targeted to be below
         * the speed limit again. */
        multistate(easy, CURLM_STATE_TOOFAST );
        break;
      }

      /* read/write data if it is ready to do so */
      easy->result = Curl_readwrite(easy->easy_conn, &done);

      k = &easy->easy_handle->reqdata.keep;

      if (!(k->keepon & KEEP_READ)) {
          /* We're done reading */
          easy->easy_conn->readchannel_inuse = FALSE;
      }

      if (!(k->keepon & KEEP_WRITE)) {
          /* We're done writing */
          easy->easy_conn->writechannel_inuse = FALSE;
      }

      if(easy->result)  {
        /* The transfer phase returned error, we mark the connection to get
         * closed to prevent being re-used. This is becasue we can't
         * possibly know if the connection is in a good shape or not now. */
        easy->easy_conn->bits.close = TRUE;

        if(CURL_SOCKET_BAD != easy->easy_conn->sock[SECONDARYSOCKET]) {
          /* if we failed anywhere, we must clean up the secondary socket if
             it was used */
          sclose(easy->easy_conn->sock[SECONDARYSOCKET]);
          easy->easy_conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD;
        Curl_posttransfer(easy->easy_handle);
        Curl_done(&easy->easy_conn, easy->result);
      }
      else if(TRUE == done) {
        char *newurl;
        bool retry = Curl_retry_request(easy->easy_conn, &newurl);
        /* call this even if the readwrite function returned error */
        Curl_posttransfer(easy->easy_handle);
        if (retry) {
          Curl_removeHandleFromPipeline(easy->easy_handle,
                                        easy->easy_conn->recv_pipe);
        }

        /* When we follow redirects, must to go back to the CONNECT state */
        if(easy->easy_handle->reqdata.newurl || retry) {
          if(!retry) {
            /* if the URL is a follow-location and not just a retried request
               then figure out the URL here */
            newurl = easy->easy_handle->reqdata.newurl;
            easy->easy_handle->reqdata.newurl = NULL;
          easy->result = Curl_done(&easy->easy_conn, CURLE_OK);
          if(easy->result == CURLE_OK)
            easy->result = Curl_follow(easy->easy_handle, newurl, retry);
          if(CURLE_OK == easy->result) {
            multistate(easy, CURLM_STATE_CONNECT);
          else
            /* Since we "took it", we are in charge of freeing this on
               failure */
            free(newurl);
        else {
          /* after the transfer is done, go DONE */
          multistate(easy, CURLM_STATE_DONE);
          result = CURLM_CALL_MULTI_PERFORM;
        }
      }
      /* Remove ourselves from the receive pipeline */
      Curl_removeHandleFromPipeline(easy->easy_handle,
                                    easy->easy_conn->recv_pipe);
      easy->easy_handle->state.is_in_pipeline = FALSE;

      if (easy->easy_conn->bits.stream_was_rewound) {
          /* This request read past its response boundary so we quickly
             let the other requests consume those bytes since there is no
             guarantee that the socket will become active again */
          result = CURLM_CALL_MULTI_PERFORM;
      }

      if (!easy->easy_handle->state.cancelled) {
        /* post-transfer command */
        easy->result = Curl_done(&easy->easy_conn, CURLE_OK);

        /* after we have DONE what we're supposed to do, go COMPLETED, and
           it doesn't matter what the Curl_done() returned! */
        multistate(easy, CURLM_STATE_COMPLETED);
      }
      if (easy->easy_handle->state.cancelled) {
        /* Go into the CANCELLED state if we were cancelled */
        multistate(easy, CURLM_STATE_CANCELLED);
      }

      /* this is a completed transfer, it is likely to still be connected */
      /* This node should be delinked from the list now and we should post
         an information message that we are complete. */
      break;

    case CURLM_STATE_CANCELLED:
      /* Cancelled transfer, wait to be cleaned up */
      break;

    if(CURLM_STATE_COMPLETED != easy->state) {
      if(CURLE_OK != easy->result) {
        /*
         * If an error was returned, and we aren't in completed state now,
         * then we go to completed and consider this transfer aborted.
         */
  } while (easy->easy_handle->change.url_changed);
  if ((CURLM_STATE_COMPLETED == easy->state) && !easy->msg) {
    if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) {
      /* clear out the usage of the shared DNS cache */
      easy->easy_handle->dns.hostcache = NULL;
      easy->easy_handle->dns.hostcachetype = HCACHE_NONE;
    }
    /* now add a node to the Curl_message linked list with this info */
    msg = (struct Curl_message *)malloc(sizeof(struct Curl_message));
    msg->extmsg.msg = CURLMSG_DONE;
    msg->extmsg.easy_handle = easy->easy_handle;
    msg->extmsg.data.result = easy->result;
    easy->msg = msg;
    easy->msg_num = 1; /* there is one unread message here */
    multi->num_msgs++; /* increase message counter */

CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
  struct Curl_one_easy *easy;
  CURLMcode returncode=CURLM_OK;
  struct Curl_tree *t;

  if(!GOOD_MULTI_HANDLE(multi))
    return CURLM_BAD_HANDLE;

  easy=multi->easy.next;
  while(easy) {
    CURLMcode result;

    if (easy->easy_handle->state.cancelled &&
        easy->state == CURLM_STATE_CANCELLED) {
      /* Remove cancelled handles once it's safe to do so */
      easy = easy->next;
      Curl_multi_rmeasy(multi_handle, easy->easy_handle);
      continue;
    }

    result = multi_runsingle(multi, easy);
    if(result)
      returncode = result;

    easy = easy->next; /* operate on next handle */
  }

  /*
   * Simply remove all expired timers from the splay since handles are dealt
   * with unconditionally by this function and curl_multi_timeout() requires
   * that already passed/handled expire times are removed from the splay.
   */
  do {
    struct timeval now = Curl_tvnow();
    int key = now.tv_sec; /* drop the usec part */

    multi->timetree = Curl_splaygetbest(key, multi->timetree, &t);
    if (t) {
      struct SessionHandle *d = t->payload;
      struct timeval* tv = &d->state.expiretime;

      /* clear the expire times within the handles that we remove from the
         splay tree */
      tv->tv_sec = 0;
      tv->tv_usec = 0;
    }

/* This is called when an easy handle is cleanup'ed that is part of a multi
   handle */
void Curl_multi_rmeasy(void *multi_handle, CURL *easy_handle)
{
  curl_multi_remove_handle(multi_handle, easy_handle);
}

Daniel Stenberg's avatar
Daniel Stenberg committed
CURLMcode curl_multi_cleanup(CURLM *multi_handle)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
  struct Curl_one_easy *easy;
  struct Curl_one_easy *nexteasy;
Daniel Stenberg's avatar
Daniel Stenberg committed
  if(GOOD_MULTI_HANDLE(multi)) {
    multi->type = 0; /* not good anymore */
    Curl_hash_destroy(multi->hostcache);
#if 1
    /* go over all connections that have close actions */
    for(i=0; i< multi->connc->num; i++) {
      if(multi->connc->connects[i] &&
         multi->connc->connects[i]->protocol & PROT_CLOSEACTION)
        Curl_disconnect(multi->connc->connects[i]);
    }
    /* now walk through the list of handles we kept around only to be
       able to close connections "properly" */
    cl = multi->closure;
    while(cl) {
      cl->easy_handle->state.shared_conn = NULL; /* no more shared */
      Curl_close(cl->easy_handle); /* close handle */
      n = cl->next;
      free(cl);
      cl= n;
    }
#endif

    Curl_rm_connc(multi->connc);

Daniel Stenberg's avatar
Daniel Stenberg committed
    /* remove all easy handles */
    easy = multi->easy.next;
    while(easy) {
      nexteasy=easy->next;
      if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) {
        /* clear out the usage of the shared DNS cache */
        easy->easy_handle->dns.hostcache = NULL;
        easy->easy_handle->dns.hostcachetype = HCACHE_NONE;
      }

      /* Clear the pointer to the connection cache */
      easy->easy_handle->state.connc = NULL;

      Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */
Daniel Stenberg's avatar
Daniel Stenberg committed
    free(multi);

    return CURLM_OK;
  }
  else
    return CURLM_BAD_HANDLE;
}
CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;

  if(GOOD_MULTI_HANDLE(multi)) {
    struct Curl_one_easy *easy;
    if(!multi->num_msgs)
      return NULL; /* no messages left to return */
    easy=multi->easy.next;
    while(easy) {
      if(easy->msg_num) {
        easy->msg_num--;
        break;
      }
      easy = easy->next;
    }
    if(!easy)
      return NULL; /* this means internal count confusion really */
    multi->num_msgs--;
    *msgs_in_queue = multi->num_msgs;
Daniel Stenberg's avatar
Daniel Stenberg committed
    return NULL;
 * singlesocket() checks what sockets we deal with and their "action state"
 * and if we have a different state in any of those sockets from last time we
 * call the callback accordingly.
 */
static void singlesocket(struct Curl_multi *multi,
                         struct Curl_one_easy *easy)
{
  struct socketstate current;
  int i;

  memset(&current, 0, sizeof(current));
  for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++)
    current.socks[i] = CURL_SOCKET_BAD;

  /* first fill in the 'current' struct with the state as it is now */
  current.action = multi_getsock(easy, current.socks, MAX_SOCKSPEREASYHANDLE);

  /* when filled in, we compare with the previous round's state in a first
     quick memory compare check */
  if(memcmp(&current, &easy->sockstate, sizeof(struct socketstate))) {

    /* there is difference, call the callback once for every socket change ! */
    for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) {
      int action;
      curl_socket_t s = current.socks[i];

      /* Ok, this approach is probably too naive and simple-minded but
         it might work for a start */

      if((easy->sockstate.socks[i] == CURL_SOCKET_BAD) &&
         (s == CURL_SOCKET_BAD)) {
        /* no socket now and there was no socket before */
        break;
      }

      if(s == CURL_SOCKET_BAD) {
        /* socket is removed */
        action = CURL_POLL_REMOVE;
        s = easy->sockstate.socks[i]; /* this is the removed socket */
      }
      else {
        if(easy->sockstate.socks[i] == s) {
          /* still the same socket, but are we waiting for the same actions? */
          unsigned int curr;
          unsigned int prev;

          /* the current read/write bits for this particular socket */
          curr = current.action & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i));

          /* the previous read/write bits for this particular socket */
          prev = easy->sockstate.action &
            (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i));

          if(curr == prev)
            continue;
        }

        action = CURL_POLL_NONE;
        if(current.action & GETSOCK_READSOCK(i))
          action |= CURL_POLL_IN;
        if(current.action & GETSOCK_WRITESOCK(i))
          action |= CURL_POLL_OUT;
Daniel Stenberg's avatar
Daniel Stenberg committed
      /* Update the sockhash accordingly BEFORE the callback if not a removal,
         in case the callback wants to use curl_multi_assign(), but do the
         removal AFTER the callback for the very same reason (but then to be
         able to pass the correct entry->socketp) */

      if(action != CURL_POLL_REMOVE)
        /* make sure this socket is present in the hash for this handle */
        sh_addentry(multi->sockhash, s, easy->easy_handle);

      /* call the callback with this new info */
      if(multi->socket_cb) {
        struct Curl_sh_entry *entry =
          Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));

      }

      if(action == CURL_POLL_REMOVE)
        /* remove from hash for this easy handle */
    }
    /* copy the current state to the storage area */
    memcpy(&easy->sockstate, &current, sizeof(struct socketstate));
  }
  else {
    /* identical, nothing new happened so we don't do any callbacks */
  }

}

static CURLMcode multi_socket(struct Curl_multi *multi,
                              bool checkall,
{
  CURLMcode result = CURLM_OK;
  struct SessionHandle *data = NULL;
  struct Curl_tree *t;

  if(checkall) {
    struct Curl_one_easy *easyp;
    /* *perform() deals with running_handles on its own */
    result = curl_multi_perform(multi, running_handles);

    /* walk through each easy handle and do the socket state change magic
       and callbacks */
    easyp=multi->easy.next;
    while(easyp) {
      singlesocket(multi, easyp);
      easyp = easyp->next;
    }

    /* or should we fall-through and do the timer-based stuff? */
    return result;
  }
  else if (s != CURL_SOCKET_TIMEOUT) {

    struct Curl_sh_entry *entry =
      Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));

    if(!entry)
      /* unmatched socket, major problemo! */
      return CURLM_BAD_SOCKET; /* better return code? */

    result = multi_runsingle(multi, data->set.one_easy);
    if(result == CURLM_OK)
      /* get the socket(s) and check if the state has been changed since
         last */
      singlesocket(multi, data->set.one_easy);
    /* Now we fall-through and do the timer-based stuff, since we don't want
       to force the user to have to deal with timeouts as long as at least one
       connection in fact has traffic. */

    data = NULL; /* set data to NULL again to avoid calling multi_runsingle()
                    in case there's no need to */
  }

  /*
   * The loop following here will go on as long as there are expire-times left
   * to process in the splay and 'data' will be re-assigned for every expired
   * handle we deal with.
   */
  do {
    int key;
    struct timeval now;

    /* the first loop lap 'data' can be NULL */
    if(data) {
      result = multi_runsingle(multi, data->set.one_easy);

      if(result == CURLM_OK)
        /* get the socket(s) and check if the state has been changed since
           last */
        singlesocket(multi, data->set.one_easy);
    }

    /* Check if there's one (more) expired timer to deal with! This function
       extracts a matching node if there is one */

    now = Curl_tvnow();
    key = now.tv_sec; /* drop the usec part */

    multi->timetree = Curl_splaygetbest(key, multi->timetree, &t);
    if(t) {
      /* assign 'data' to be the easy handle we just removed from the splay
         tree */
      /* clear the expire time within the handle we removed from the
         splay tree */
      data->state.expiretime.tv_sec = 0;
      data->state.expiretime.tv_usec = 0;
    }
  return result;
}

CURLMcode curl_multi_setopt(CURLM *multi_handle,
                            CURLMoption option, ...)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
  CURLMcode res = CURLM_OK;
  va_list param;

  if(!GOOD_MULTI_HANDLE(multi))
    return CURLM_BAD_HANDLE;

  va_start(param, option);

  switch(option) {
  case CURLMOPT_SOCKETFUNCTION:
    multi->socket_cb = va_arg(param, curl_socket_callback);
    break;
  case CURLMOPT_SOCKETDATA:
    multi->socket_userp = va_arg(param, void *);
    break;
Yang Tse's avatar
Yang Tse committed
    multi->pipelining_enabled = (bool)(0 != va_arg(param, long));
CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s,
                            int *running_handles)
  return multi_socket((struct Curl_multi *)multi_handle, FALSE, s,
                      running_handles);
CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles)

{
  return multi_socket((struct Curl_multi *)multi_handle,
                      TRUE, CURL_SOCKET_BAD, running_handles);
}

CURLMcode curl_multi_timeout(CURLM *multi_handle,
                             long *timeout_ms)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;

  /* First, make some basic checks that the CURLM handle is a good handle */
  if(!GOOD_MULTI_HANDLE(multi))
    return CURLM_BAD_HANDLE;

  if(multi->timetree) {
    /* we have a tree of expire times */
    struct timeval now = Curl_tvnow();

    /* splay the lowest to the bottom */
    multi->timetree = Curl_splay(0, multi->timetree);

    /* At least currently, the splay key is a time_t for the expire time */
    *timeout_ms = (multi->timetree->key - now.tv_sec) * 1000 -
      now.tv_usec/1000;
    if(*timeout_ms < 0)
      /* 0 means immediately */
      *timeout_ms = 0;
  }
  else
    *timeout_ms = -1;

  return CURLM_OK;
}

/* given a number of milliseconds from now to use to set the 'act before
   this'-time for the transfer, to be extracted by curl_multi_timeout() */
void Curl_expire(struct SessionHandle *data, long milli)
{
  struct Curl_multi *multi = data->multi;
  struct timeval *nowp = &data->state.expiretime;

  /* this is only interesting for multi-interface using libcurl, and only
     while there is still a multi interface struct remaining! */
  if(!multi)
    return;

  if(!milli) {
    /* No timeout, clear the time data. */
    if(nowp->tv_sec) {
      /* Since this is an cleared time, we must remove the previous entry from
         the splay tree */
      rc = Curl_splayremovebyaddr(multi->timetree,
                                  &data->state.timenode,
                                  &multi->timetree);
      if(rc)
        infof(data, "Internal error clearing splay node = %d\n", rc);
Yang Tse's avatar
Yang Tse committed
      nowp->tv_sec = 0;
Yang Tse's avatar
Yang Tse committed
      nowp->tv_usec = 0;
    }
  }
  else {
    struct timeval set;
    int rest;

    set = Curl_tvnow();
    set.tv_sec += milli/1000;
    set.tv_usec += (milli%1000)*1000;

    rest = (int)(set.tv_usec - 1000000);
    if(rest > 0) {
      /* bigger than a full microsec */
      set.tv_sec++;
      set.tv_usec -= 1000000;
    }

    if(nowp->tv_sec) {
      /* This means that the struct is added as a node in the splay tree.
         Compare if the new time is earlier, and only remove-old/add-new if it
         is. */
      long diff = curlx_tvdiff(set, *nowp);
      if(diff > 0)
        /* the new expire time was later so we don't change this */
        return;

      /* Since this is an updated time, we must remove the previous entry from
         the splay tree first and then re-add the new value */
      rc = Curl_splayremovebyaddr(multi->timetree,
                                  &data->state.timenode,
                                  &multi->timetree);
      if(rc)
        infof(data, "Internal error removing splay node = %d\n", rc);
    }

    *nowp = set;
    infof(data, "Expire at %ld / %ld (%ldms)\n",
          (long)nowp->tv_sec, (long)nowp->tv_usec, milli);

    data->state.timenode.payload = data;
    multi->timetree = Curl_splayinsert((int)nowp->tv_sec,
                                       multi->timetree,
                                       &data->state.timenode);
  }
#if 0
  Curl_splayprint(multi->timetree, 0, TRUE);
#endif
}

CURLMcode curl_multi_assign(CURLM *multi_handle,
                            curl_socket_t s, void *hashp)
{
  struct Curl_sh_entry *there = NULL;
  struct Curl_multi *multi = (struct Curl_multi *)multi_handle;

  if(s != CURL_SOCKET_BAD)
    there = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(curl_socket_t));

  if(!there)
    return CURLM_BAD_SOCKET;

  there->socketp = hashp;

  return CURLM_OK;
}

static bool multi_conn_using(struct Curl_multi *multi,
                             struct SessionHandle *data)
{
  /* any live CLOSEACTION-connections pointing to the give 'data' ? */
  int i;

  for(i=0; i< multi->connc->num; i++) {
    if(multi->connc->connects[i] &&
       (multi->connc->connects[i]->data == data) &&
       multi->connc->connects[i]->protocol & PROT_CLOSEACTION)
      return TRUE;
  }

  return FALSE;
}

/* add the given data pointer to the list of 'closure handles' that are
   kept around only to be able to close some connections nicely */
void Curl_multi_add_closure(struct Curl_multi *multi,
                            struct SessionHandle *data)
{
  int i;
  struct closure *cl = (struct closure *)calloc(sizeof(struct closure), 1);
  struct closure *p=NULL;
  struct closure *n;
  if(cl) {
    cl->easy_handle = data;
    cl->next = multi->closure;
    multi->closure = cl;
  }

  p = multi->closure;
  cl = p->next; /* start immediately on the second since the first is the one
                   we just added and it is _very_ likely to actually exist
                   used in the cache since that's the whole purpose of adding
                   it to this list! */

  /* When adding, scan through all the other currently kept handles and see if
     there are any connections still referring to them and kill them if not. */
  while(cl) {
    bool inuse = FALSE;
    for(i=0; i< multi->connc->num; i++) {
      if(multi->connc->connects[i] &&
         (multi->connc->connects[i]->data == cl->easy_handle)) {
        inuse = TRUE;
        break;
      }
    }

    n = cl->next;

    if(!inuse) {
      /* cl->easy_handle is now killable */
      infof(data, "Delayed kill of easy handle %p\n", cl->easy_handle);
      /* unmark it as not having a connection around that uses it anymore */
      cl->easy_handle->state.shared_conn= NULL;
      Curl_close(cl->easy_handle);
      if(p)
        p->next = n;
      else
        multi->closure = n;
      free(cl);
    }
    else
      p = cl;

    cl = n;
  }

}