Skip to content
multi.c 90.4 KiB
Newer Older
  memcpy(easy->sockets, socks, num*sizeof(curl_socket_t));
/*
 * add_next_timeout()
 *
 * Each SessionHandle has a list of timeouts. The add_next_timeout() is called
 * when it has just been removed from the splay tree because the timeout has
 * expired. This function is then to advance in the list to pick the next
 * timeout to use (skip the already expired ones) and add this node back to
 * the splay tree again.
 *
 * The splay tree only has each sessionhandle as a single node and the nearest
 * timeout is used to sort it on.
 */
static CURLMcode add_next_timeout(struct timeval now,
                                  struct Curl_multi *multi,
                                  struct SessionHandle *d)
{
  struct timeval *tv = &d->state.expiretime;
  struct curl_llist *list = d->state.timeoutlist;
  struct curl_llist_element *e;

  /* move over the timeout list for this specific handle and remove all
     timeouts that are now passed tense and store the next pending
     timeout in *tv */
  for(e = list->head; e; ) {
    struct curl_llist_element *n = e->next;
    long diff = curlx_tvdiff(*(struct timeval *)e->ptr, now);
    if(diff <= 0)
      /* remove outdated entry */
      Curl_llist_remove(list, e, NULL);
    else
      /* the list is sorted so get out on the first mismatch */
      break;
    e = n;
  }
  if(!list->size)  {
    /* clear the expire times within the handles that we remove from the
       splay tree */
    tv->tv_sec = 0;
    tv->tv_usec = 0;
  }
  else {
    e = list->head;
    /* copy the first entry to 'tv' */
    memcpy(tv, e->ptr, sizeof(*tv));

    /* remove first entry from list */
    Curl_llist_remove(list, e, NULL);

    /* insert this node again into the splay */
    multi->timetree = Curl_splayinsert(*tv, multi->timetree,
                                       &d->state.timenode);
  }
  return CURLM_OK;
}


static CURLMcode multi_socket(struct Curl_multi *multi,
                              bool checkall,
{
  CURLMcode result = CURLM_OK;
  struct SessionHandle *data = NULL;
  struct Curl_tree *t;
  struct timeval now = Curl_tvnow();
    /* *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;
    /* or should we fall-through and do the timer-based stuff? */

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

    if(!entry)
      /* Unmatched socket, we can't act on it but we ignore this fact.  In
         real-world tests it has been proved that libevent can in fact give
         the application actions even though the socket was just previously
         asked to get removed, so thus we better survive stray socket actions
         and just move on. */
      ;
    else {
      data = entry->easy;
      if(data->magic != CURLEASY_MAGIC_NUMBER)
        /* bad bad bad bad bad bad bad */
        return CURLM_INTERNAL_ERROR;
      /* If the pipeline is enabled, take the handle which is in the head of
         the pipeline. If we should write into the socket, take the send_pipe
         head.  If we should read from the socket, take the recv_pipe head. */
      if(data->set.one_easy->easy_conn) {
        if((ev_bitmask & CURL_POLL_OUT) &&
           data->set.one_easy->easy_conn->send_pipe &&
           data->set.one_easy->easy_conn->send_pipe->head)
          data = data->set.one_easy->easy_conn->send_pipe->head->ptr;
        else if((ev_bitmask & CURL_POLL_IN) &&
                data->set.one_easy->easy_conn->recv_pipe &&
                data->set.one_easy->easy_conn->recv_pipe->head)
          data = data->set.one_easy->easy_conn->recv_pipe->head->ptr;
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(data->set.one_easy->easy_conn &&
         !(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK))
        /* set socket event bitmask if they're not locked */
        data->set.one_easy->easy_conn->cselect_bits = ev_bitmask;
        result = multi_runsingle(multi, now, data->set.one_easy);
      while(CURLM_CALL_MULTI_PERFORM == result);
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(data->set.one_easy->easy_conn &&
         !(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK))
        /* clear the bitmask only if not locked */
        data->set.one_easy->easy_conn->cselect_bits = 0;
      if(CURLM_OK >= result)
        /* 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 */
    }
  now.tv_usec += 40000; /* compensate for bad precision timers that might've
                           triggered too early */
    now.tv_sec++;
    now.tv_usec -= 1000000;
  }

  /*
   * 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 {
    /* the first loop lap 'data' can be NULL */
    if(data) {
        result = multi_runsingle(multi, now, data->set.one_easy);
      while(CURLM_CALL_MULTI_PERFORM == result);
      if(CURLM_OK >= result)
        /* 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 */

    multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
    if(t) {
      data = t->payload; /* assign this for next loop */
      (void)add_next_timeout(now, multi, t->payload);
#undef curl_multi_setopt
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));
  case CURLMOPT_TIMERFUNCTION:
    multi->timer_cb = va_arg(param, curl_multi_timer_callback);
    break;
  case CURLMOPT_TIMERDATA:
    multi->timer_userp = va_arg(param, void *);
    break;
  case CURLMOPT_MAXCONNECTS:
    multi->maxconnects = va_arg(param, long);
    break;
/* we define curl_multi_socket() in the public multi.h header */
#undef curl_multi_socket
CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s,
                            int *running_handles)
  CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s,
    update_timer((struct Curl_multi *)multi_handle);
  return result;
}

CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s,
Daniel Stenberg's avatar
Daniel Stenberg committed
                                   int ev_bitmask, int *running_handles)
{
  CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s,
                                  ev_bitmask, running_handles);
    update_timer((struct Curl_multi *)multi_handle);
  return result;
CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles)
  CURLMcode result = multi_socket((struct Curl_multi *)multi_handle,
                                  TRUE, CURL_SOCKET_BAD, 0, running_handles);
    update_timer((struct Curl_multi *)multi_handle);
  return result;
static CURLMcode multi_timeout(struct Curl_multi *multi,
                               long *timeout_ms)
  static struct timeval tv_zero = {0,0};

  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(tv_zero, multi->timetree);
    if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) {
      /* some time left before expiration */
      *timeout_ms = curlx_tvdiff(multi->timetree->key, now);
      if(!*timeout_ms)
        /*
         * Since we only provide millisecond resolution on the returned value
         * and the diff might be less than one millisecond here, we don't
         * return zero as that may cause short bursts of busyloops on fast
         * processors while the diff is still present but less than one
         * millisecond! instead we return 1 until the time is ripe.
         */
        *timeout_ms=1;
    }
      /* 0 means immediately */
      *timeout_ms = 0;
  }
  else
    *timeout_ms = -1;

  return CURLM_OK;
}

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;

  return multi_timeout(multi, timeout_ms);
}

/*
 * Tell the application it should update its timers, if it subscribes to the
 * update timer callback.
 */
static int update_timer(struct Curl_multi *multi)
{
  long timeout_ms;
  if(multi_timeout(multi, &timeout_ms)) {
    static const struct timeval none={0,0};
    if(Curl_splaycomparekeys(none, multi->timer_lastcall)) {
      multi->timer_lastcall = none;
      /* there's no timeout now but there was one previously, tell the app to
         disable it */
      return multi->timer_cb((CURLM*)multi, -1, multi->timer_userp);
    }

  /* When multi_timeout() is done, multi->timetree points to the node with the
   * timeout we got the (relative) time-out time for. We can thus easily check
   * if this is the same (fixed) time as we got in a previous call and then
   * avoid calling the callback again. */
  if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0)
    return 0;

  multi->timer_lastcall = multi->timetree->key;

  return multi->timer_cb((CURLM*)multi, timeout_ms, multi->timer_userp);
}

static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle,
                                              struct connectdata *conn)
{
  size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size;
  struct curl_llist_element *sendhead = conn->send_pipe->head;

  if(!Curl_isPipeliningEnabled(handle) ||
     pipeLen == 0)
    pipeline = conn->send_pipe;
  else {
    if(conn->server_supports_pipelining &&
       pipeLen < MAX_PIPELINE_LENGTH)
      pipeline = conn->send_pipe;
    else
      pipeline = conn->pend_pipe;
  }

  rc = Curl_addHandleToPipeline(handle, pipeline);

  if(pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) {
    /* this is a new one as head, expire it */
    conn->writechannel_inuse = FALSE; /* not in use yet */
#ifdef DEBUGBUILD
    infof(conn->data, "%p is at send pipe head!\n",
          conn->send_pipe->head->ptr);
    Curl_expire(conn->send_pipe->head->ptr, 1);
}

static int checkPendPipeline(struct connectdata *conn)
{
  int result = 0;
  struct curl_llist_element *sendhead = conn->send_pipe->head;
  size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size;
  if(conn->server_supports_pipelining || pipeLen == 0) {
    struct curl_llist_element *curr = conn->pend_pipe->head;
    const size_t maxPipeLen =
      conn->server_supports_pipelining ? MAX_PIPELINE_LENGTH : 1;
    while(pipeLen < maxPipeLen && curr) {
      Curl_llist_move(conn->pend_pipe, curr,
                      conn->send_pipe, conn->send_pipe->tail);
      Curl_pgrsTime(curr->ptr, TIMER_PRETRANSFER);
      ++result; /* count how many handles we moved */
      curr = conn->pend_pipe->head;
      ++pipeLen;
    }
  }

    /* something moved, check for a new send pipeline leader */
    if(sendhead != conn->send_pipe->head) {
      /* this is a new one as head, expire it */
      conn->writechannel_inuse = FALSE; /* not in use yet */
#ifdef DEBUGBUILD
      infof(conn->data, "%p is at send pipe head!\n",
            conn->send_pipe->head->ptr);
/* Move this transfer from the sending list to the receiving list.

   Pay special attention to the new sending list "leader" as it needs to get
   checked to update what sockets it acts on.

static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle,
Daniel Stenberg's avatar
Daniel Stenberg committed
                                             struct connectdata *conn)
{
  struct curl_llist_element *curr;

  curr = conn->send_pipe->head;
  while(curr) {
    if(curr->ptr == handle) {
      Curl_llist_move(conn->send_pipe, curr,
                      conn->recv_pipe, conn->recv_pipe->tail);

      if(conn->send_pipe->head) {
        /* Since there's a new easy handle at the start of the send pipeline,
           set its timeout value to 1ms to make it trigger instantly */
        conn->writechannel_inuse = FALSE; /* not used now */
#ifdef DEBUGBUILD
        infof(conn->data, "%p is at send pipe head B!\n",
              conn->send_pipe->head->ptr);
        Curl_expire(conn->send_pipe->head->ptr, 1);
      }

      /* The receiver's list is not really interesting here since either this
         handle is now first in the list and we'll deal with it soon, or
         another handle is already first and thus is already taken care of */

      break; /* we're done! */
static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle,
                                            struct connectdata *conn)
{
  struct curl_llist_element *curr;

  curr = conn->recv_pipe->head;
  while(curr) {
    if(curr->ptr == handle) {
      Curl_llist_move(conn->recv_pipe, curr,
                      conn->done_pipe, conn->done_pipe->tail);
      break;
    }
    curr = curr->next;
  }
}
static bool isHandleAtHead(struct SessionHandle *handle,
                           struct curl_llist *pipeline)
{
  struct curl_llist_element *curr = pipeline->head;
  if(curr)
    return (bool)(curr->ptr == handle);

  return FALSE;
}

Daniel Stenberg's avatar
Daniel Stenberg committed
/*
 * multi_freetimeout()
 *
 * Callback used by the llist system when a single timeout list entry is
 * destroyed.
 */
static void multi_freetimeout(void *user, void *entryptr)
{
  (void)user;

  /* the entry was plain malloc()'ed */
  free(entryptr);
}

/*
 * multi_addtimeout()
 *
 * Add a timestamp to the list of timeouts. Keep the list sorted so that head
 * of list is always the timeout nearest in time.
 *
 */
static CURLMcode
multi_addtimeout(struct curl_llist *timeoutlist,
                 struct timeval *stamp)
{
  struct curl_llist_element *e;
  struct timeval *timedup;
  struct curl_llist_element *prev = NULL;

  timedup = malloc(sizeof(*timedup));
  if(!timedup)
    return CURLM_OUT_OF_MEMORY;

  /* copy the timestamp */
  memcpy(timedup, stamp, sizeof(*timedup));

  if(Curl_llist_count(timeoutlist)) {
    /* find the correct spot in the list */
    for(e = timeoutlist->head; e; e = e->next) {
      struct timeval *checktime = e->ptr;
      long diff = curlx_tvdiff(*checktime, *timedup);
      if(diff > 0)
        break;
      prev = e;
    }

  }
  /* else
     this is the first timeout on the list */

  if(!Curl_llist_insert_next(timeoutlist, prev, timedup)) {
    free(timedup);
Daniel Stenberg's avatar
Daniel Stenberg committed
    return CURLM_OUT_OF_MEMORY;
Daniel Stenberg's avatar
Daniel Stenberg committed

  return CURLM_OK;
}
Daniel Stenberg's avatar
Daniel Stenberg committed
/*
 * Curl_expire()
 *
 * 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()
 *
 * Note that the timeout will be added to a queue of timeouts if it defines a
 * moment in time that is later than the current head of queue.
 *
 * Pass zero to clear all timeout values for this handle.
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 || nowp->tv_usec) {
      /* Since this is an cleared time, we must remove the previous entry from
         the splay tree */
Daniel Stenberg's avatar
Daniel Stenberg committed
      struct curl_llist *list = data->state.timeoutlist;

      rc = Curl_splayremovebyaddr(multi->timetree,
                                  &data->state.timenode,
                                  &multi->timetree);
      if(rc)
        infof(data, "Internal error clearing splay node = %d\n", rc);
Daniel Stenberg's avatar
Daniel Stenberg committed

      /* flush the timeout list too */
      while(list->size > 0)
        Curl_llist_remove(list, list->tail, NULL);

#ifdef DEBUGBUILD
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;

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

    if(nowp->tv_sec || nowp->tv_usec) {
      /* 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. */
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(diff > 0) {
        /* the new expire time was later so just add it to the queue
           and get out */
        multi_addtimeout(data->state.timeoutlist, &set);
Daniel Stenberg's avatar
Daniel Stenberg committed
      }

      /* the new time is newer than the presently set one, so add the current
         to the queue and update the head */
      multi_addtimeout(data->state.timeoutlist, nowp);

      /* 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);
    multi->timetree = Curl_splayinsert(*nowp,
                                       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 void multi_connc_remove_handle(struct Curl_multi *multi,
  /* a connection in the connection cache pointing to the given 'data' ? */
    struct connectdata * conn = multi->connc->connects[i];

    if(conn && conn->data == data) {
      /* If this easy_handle was the last one in charge for one or more
         connections in the shared connection cache, we might need to keep
         this handle around until either A) the connection is closed and
         killed properly, or B) another easy_handle uses the connection.

         The reason why we need to have a easy_handle associated with a live
         connection is simply that some connections will need a handle to get
         closed down properly. Currently, the only connections that need to
         keep a easy_handle handle around are using FTP(S). Such connections
         have the PROT_CLOSEACTION bit set.

         Thus, we need to check for all connections in the shared cache that
         points to this handle and are using PROT_CLOSEACTION. If there's any,
         we need to add this handle to the list of "easy handles kept around
         for nice connection closures".
      */
      if(conn->handler->flags & PROTOPT_CLOSEACTION) {
        /* this handle is still being used by a shared connection and
           thus we leave it around for now */
        if(add_closure(multi, data) == CURLM_OK)
          data->state.shared_conn = multi;
        else {
          /* out of memory - so much for graceful shutdown */
          Curl_disconnect(conn, /* dead_connection */ FALSE);
          multi->connc->connects[i] = NULL;
        }
      }
      else
        /* disconect the easy handle from the connection since the connection
           will now remain but this easy handle is going */
        conn->data = NULL;
    }
  }
/* Add the given data pointer to the list of 'closure handles' that are kept
   around only to be able to close some connections nicely - just make sure
   that this handle isn't already added, like for the cases when an easy
   handle is removed, added and removed again... */
static CURLMcode add_closure(struct Curl_multi *multi,
                             struct SessionHandle *data)
  struct closure *cl = multi->closure;
  struct closure *p = NULL;
  bool add = TRUE;
  /* Before 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. */
    struct closure *n;
    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 */
      /* unmark it as not having a connection around that uses it anymore */
      cl->easy_handle->state.shared_conn= NULL;

      if(cl->easy_handle->state.closed) {
        infof(data, "Delayed kill of easy handle %p\n", cl->easy_handle);
        /* close handle only if curl_easy_cleanup() already has been called
           for this easy handle */
        Curl_close(cl->easy_handle);
      }
      if(cl->easy_handle == data)
        add = FALSE;

    cl = calloc(1, sizeof(struct closure));
    if(!cl)
      return CURLM_OUT_OF_MEMORY;

    cl->easy_handle = data;
    cl->next = multi->closure;
    multi->closure = cl;
  }

  return CURLM_OK;
#ifdef DEBUGBUILD
void Curl_multi_dump(const struct Curl_multi *multi_handle)
{
  struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
  struct Curl_one_easy *easy;
  int i;
  fprintf(stderr, "* Multi status: %d handles, %d alive\n",
          multi->num_easy, multi->num_alive);
  for(easy=multi->easy.next; easy != &multi->easy; easy = easy->next) {
    if(easy->state < CURLM_STATE_COMPLETED) {
      /* only display handles that are not completed */
      fprintf(stderr, "handle %p, state %s, %d sockets\n",
              (void *)easy->easy_handle,
              statename[easy->state], easy->numsocks);
      for(i=0; i < easy->numsocks; i++) {
        curl_socket_t s = easy->sockets[i];
        struct Curl_sh_entry *entry =
          Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));

        fprintf(stderr, "%d ", (int)s);
        if(!entry) {
          fprintf(stderr, "INTERNAL CONFUSION\n");
          continue;
        }
        fprintf(stderr, "[%s %s] ",
                entry->action&CURL_POLL_IN?"RECVING":"",
                entry->action&CURL_POLL_OUT?"SENDING":"");
      }
      if(easy->numsocks)
        fprintf(stderr, "\n");
    }
  }
}
#endif