Skip to content
multi.c 42.3 KiB
Newer Older
    Curl_hash_destroy(multi->hostcache);
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;
      }
      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;
      }

      /* call the callback with this new info */
      if(multi->socket_cb) {
        multi->socket_cb(easy->easy_handle,
                         s,
                         action,
                         multi->socket_userp);
      }

      /* Update the sockhash accordingly */
      if(action == CURL_POLL_REMOVE)
        /* remove from hash for this easy handle */
      else
        /* make sure this socket is present in the hash for this handle */
        sh_addentry(multi->sockhash, s, easy->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,
                              curl_socket_t s)
{
  CURLMcode result = CURLM_OK;
  int running_handles;
  struct SessionHandle *data = NULL;
  struct Curl_tree *t;

  if(checkall) {
    struct Curl_one_easy *easyp;
    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? */

    /* Now, there is potentially a chain of easy handles in this hash
       entry struct and we need to deal with all of them */

    result = multi_runsingle(multi, data->set.one_easy, &running_handles);
    if(result == CURLM_OK)
      /* get the socket(s) and check if the state has been changed since
         last */
      singlesocket(multi, data->set.one_easy);
    /* or should we fall-through and do the timer-based stuff? */
    return result;
  }

  /*
   * 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, &running_handles);

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

  } while(t);

  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;
  default:
    res = CURLM_UNKNOWN_OPTION;
  }
  va_end(param);
  return res;
}


CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s)
{
#if 0
  printf("multi_socket(%d)\n", (int)s);
#endif

  return multi_socket((struct Curl_multi *)multi_handle, FALSE, s);
}

CURLMcode curl_multi_socket_all(CURLM *multi_handle)

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

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
}