]> granicus.if.org Git - curl/commitdiff
multi: support timeouts
authorDaniel Stenberg <daniel@haxx.se>
Tue, 10 Aug 2010 09:02:07 +0000 (11:02 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 15 Aug 2010 11:16:39 +0000 (13:16 +0200)
Curl_expire() is now expanded to hold a list of timeouts for each easy
handle. Only the closest in time will be the one used as the primary
timeout for the handle and will be used for the splay tree (which sorts
and lists all handles within the multi handle).

When the main timeout has triggered/expired, the next timeout in time
that is kept in the list will be moved to the main timeout position and
used as the key to splay with. This way, all timeouts that are set with
Curl_expire() internally will end up as a proper timeout. Previously any
Curl_expire() that set a _later_ timeout than what was already set was
just silently ignored and thus missed.

Setting Curl_expire() with timeout 0 (zero) will cancel all previously
added timeouts.

Corrects known bug #62.

docs/KNOWN_BUGS
lib/multi.c
lib/multiif.h
lib/url.c
lib/urldata.h

index 42611d62c2babb7620071bbe08c143967e0b8f11..96478917d1e9617c283305a95c592e97f8238662 100644 (file)
@@ -54,11 +54,6 @@ may have been fixed since this was written!
   handle with curl_easy_cleanup() and create a new. Some more details:
   http://curl.haxx.se/mail/lib-2009-04/0300.html
 
-62. CURLOPT_TIMEOUT does not work properly with the regular multi and
-  multi_socket interfaces. The work-around for apps is to simply remove the
-  easy handle once the time is up. See also:
-  http://curl.haxx.se/bug/view.cgi?id=2501457
-
 61. If an upload using Expect: 100-continue receives an HTTP 417 response,
   it ought to be automatically resent without the Expect:.  A workaround is
   for the client application to redo the transfer after disabling Expect:.
index c449542d6e1c6d97398264ca1a0551264823ddb1..69b80f0eef04696f967216931ac852d13d0cae71 100644 (file)
@@ -214,6 +214,8 @@ static const char * const statename[]={
 };
 #endif
 
+static void multi_freetimeout(void *a, void *b);
+
 /* always use this function to change state, to make debugging easier */
 static void multistate(struct Curl_one_easy *easy, CURLMstate state)
 {
@@ -434,6 +436,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
   struct Curl_one_easy *easy;
   struct closure *cl;
   struct closure *prev=NULL;
+  struct SessionHandle *data = easy_handle;
 
   /* First, make some basic checks that the CURLM handle is a good handle */
   if(!GOOD_MULTI_HANDLE(multi))
@@ -448,6 +451,10 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
     /* 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)
+    return CURLM_OUT_OF_MEMORY;
+
   /* Now, time to add an easy handle to the multi stack */
   easy = calloc(1, sizeof(struct Curl_one_easy));
   if(!easy)
@@ -601,6 +608,7 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
 {
   struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
   struct Curl_one_easy *easy;
+  struct SessionHandle *data = curl_handle;
 
   /* First, make some basic checks that the CURLM handle is a good handle */
   if(!GOOD_MULTI_HANDLE(multi))
@@ -611,7 +619,7 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
     return CURLM_BAD_EASY_HANDLE;
 
   /* pick-up from the 'curl_handle' the kept position in the list */
-  easy = ((struct SessionHandle *)curl_handle)->multi_pos;
+  easy = data->multi_pos;
 
   if(easy) {
     bool premature = (bool)(easy->state != CURLM_STATE_COMPLETED);
@@ -644,6 +652,12 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
        curl_easy_cleanup is called. */
     Curl_expire(easy->easy_handle, 0);
 
+    /* destroy the timeout list that is held in the easy handle */
+    if(data->state.timeoutlist) {
+      Curl_llist_destroy(data->state.timeoutlist, NULL);
+      data->state.timeoutlist = NULL;
+    }
+
     if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) {
       /* clear out the usage of the shared DNS cache */
       easy->easy_handle->dns.hostcache = NULL;
@@ -1652,12 +1666,34 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
     multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
     if(t) {
       struct SessionHandle *d = t->payload;
-      struct timeval* tv = &d->state.expiretime;
+      struct timeval *tv = &d->state.expiretime;
+      struct curl_llist *list = d->state.timeoutlist;
+      struct curl_llist_element *e;
 
-      /* clear the expire times within the handles that we remove from the
-         splay tree */
-      tv->tv_sec = 0;
-      tv->tv_usec = 0;
+      /* 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;
+        if(curlx_tvdiff(*(struct timeval *)e->ptr, now) < 0)
+          /* remove outdated entry */
+          Curl_llist_remove(list, e, NULL);
+        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);
+      }
     }
 
   } while(t);
@@ -1670,14 +1706,6 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
   return returncode;
 }
 
-/* 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);
-}
-
-
 CURLMcode curl_multi_cleanup(CURLM *multi_handle)
 {
   struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
@@ -2343,10 +2371,72 @@ static bool isHandleAtHead(struct SessionHandle *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()
+/*
+ * 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))
+    return CURLM_OUT_OF_MEMORY;
+
+  return CURLM_OK;
+}
 
-   Pass zero to clear the timeout value for this handle.
+/*
+ * 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)
 {
@@ -2364,11 +2454,18 @@ void Curl_expire(struct SessionHandle *data, long milli)
     if(nowp->tv_sec || nowp->tv_usec) {
       /* Since this is an cleared time, we must remove the previous entry from
          the splay tree */
+      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);
+
+      /* flush the timeout list too */
+      while(list->size > 0)
+        Curl_llist_remove(list, list->tail, NULL);
+
       infof(data, "Expire cleared\n");
       nowp->tv_sec = 0;
       nowp->tv_usec = 0;
@@ -2394,9 +2491,16 @@ void Curl_expire(struct SessionHandle *data, long milli)
          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 */
+      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);
         return;
+      }
+
+      /* 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 */
index 798544e06368ac1aed344a7e064bc05d8a5b0403..76918181e4715b989a9de46c5a8d834840c0d451 100644 (file)
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -27,8 +27,6 @@
  */
 void Curl_expire(struct SessionHandle *data, long milli);
 
-void Curl_multi_rmeasy(void *multi, CURL *data);
-
 bool Curl_multi_canPipeline(const struct Curl_multi* multi);
 void Curl_multi_handlePipeBreak(struct SessionHandle *data);
 
index fd6443a590bedc900411fb34a3d3f25a675dfa44..ac621f2206d34f68757bac2b261a6a87f8e12712 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -479,7 +479,15 @@ CURLcode Curl_close(struct SessionHandle *data)
   if(m)
     /* This handle is still part of a multi handle, take care of this first
        and detach this handle from there. */
-    Curl_multi_rmeasy(data->multi, data);
+    curl_multi_remove_handle(data->multi, data);
+
+  /* Destroy the timeout list that is held in the easy handle. It is
+     /normally/ done by curl_multi_remove_handle() but this is "just in
+     case" */
+  if(data->state.timeoutlist) {
+    Curl_llist_destroy(data->state.timeoutlist, NULL);
+    data->state.timeoutlist = NULL;
+  }
 
   data->magic = 0; /* force a clear AFTER the possibly enforced removal from
                       the multi handle, since that function uses the magic
index 7919921f77a87e5611d5d5bb73cd1e9673b06889..de9acf8bf46a4a006410e7dcd9e8523008e13b3e 100644 (file)
@@ -1094,6 +1094,7 @@ struct UrlState {
 #endif /* USE_SSLEAY */
   struct timeval expiretime; /* set this with Curl_expire() only */
   struct Curl_tree timenode; /* for the splay stuff */
+  struct curl_llist *timeoutlist; /* list of pending timeouts */
 
   /* a place to store the most recently set FTP entrypath */
   char *most_recent_ftp_entrypath;