multi: support timeouts

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.
diff --git a/docs/KNOWN_BUGS b/docs/KNOWN_BUGS
index 42611d6..9647891 100644
--- a/docs/KNOWN_BUGS
+++ b/docs/KNOWN_BUGS
@@ -54,11 +54,6 @@
   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:.
diff --git a/lib/multi.c b/lib/multi.c
index c449542..69b80f0 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -214,6 +214,8 @@
 };
 #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 @@
   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 @@
     /* 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 @@
 {
   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 @@
     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 @@
        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 @@
     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 @@
   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 @@
   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;
 
-   Pass zero to clear the timeout value for this handle.
+  /* 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;
+}
+
+/*
+ * 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 @@
     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 @@
          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 */
diff --git a/lib/multiif.h b/lib/multiif.h
index 798544e..7691818 100644
--- a/lib/multiif.h
+++ b/lib/multiif.h
@@ -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);
 
diff --git a/lib/url.c b/lib/url.c
index fd6443a..ac621f2 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -479,7 +479,15 @@
   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
diff --git a/lib/urldata.h b/lib/urldata.h
index 7919921..de9acf8 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1094,6 +1094,7 @@
 #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;