Skip to content
multi.c 82.8 KiB
Newer Older
      now.tv_sec++;
      now.tv_usec -= 1000000;
    }
    multi->timetree = Curl_splaygetbest(now, 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;
    }
#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) != CURLM_OK )
    return 0;

  /* 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 *pipeline;

  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;
  }

  return Curl_addHandleToPipeline(handle, pipeline);
}

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 */
      infof(conn->data, "%p is at send pipe head!\n",
            conn->send_pipe->head->ptr);
      Curl_expire(conn->send_pipe->head->ptr, 1);
    }
  }

/* 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 */
        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;
}

/* 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()

   Pass zero to clear the timeout value 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 */
      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 || 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. */
      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);
    infof(data, "Expire at %ld / %ld (%ldms) %p\n",
          (long)nowp->tv_sec, (long)nowp->tv_usec, milli, data);
    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->protocol & PROT_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);
          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);
      }
    } else {
      if(cl->easy_handle == data)
        add = FALSE;

  if (add) {
    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