Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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.
  • Loading branch information...
commit 232ad6549a684505efcbb6ed9d7a78943cc5f817 1 parent 03da3ba
Daniel Stenberg authored August 10, 2010
5  docs/KNOWN_BUGS
@@ -54,11 +54,6 @@ may have been fixed since this was written!
54 54
   handle with curl_easy_cleanup() and create a new. Some more details:
55 55
   http://curl.haxx.se/mail/lib-2009-04/0300.html
56 56
 
57  
-62. CURLOPT_TIMEOUT does not work properly with the regular multi and
58  
-  multi_socket interfaces. The work-around for apps is to simply remove the
59  
-  easy handle once the time is up. See also:
60  
-  http://curl.haxx.se/bug/view.cgi?id=2501457
61  
-
62 57
 61. If an upload using Expect: 100-continue receives an HTTP 417 response,
63 58
   it ought to be automatically resent without the Expect:.  A workaround is
64 59
   for the client application to redo the transfer after disabling Expect:.
142  lib/multi.c
@@ -214,6 +214,8 @@ static const char * const statename[]={
214 214
 };
215 215
 #endif
216 216
 
  217
+static void multi_freetimeout(void *a, void *b);
  218
+
217 219
 /* always use this function to change state, to make debugging easier */
218 220
 static void multistate(struct Curl_one_easy *easy, CURLMstate state)
219 221
 {
@@ -434,6 +436,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
434 436
   struct Curl_one_easy *easy;
435 437
   struct closure *cl;
436 438
   struct closure *prev=NULL;
  439
+  struct SessionHandle *data = easy_handle;
437 440
 
438 441
   /* First, make some basic checks that the CURLM handle is a good handle */
439 442
   if(!GOOD_MULTI_HANDLE(multi))
@@ -448,6 +451,10 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
448 451
     /* possibly we should create a new unique error code for this condition */
449 452
     return CURLM_BAD_EASY_HANDLE;
450 453
 
  454
+  data->state.timeoutlist = Curl_llist_alloc(multi_freetimeout);
  455
+  if(!data->state.timeoutlist)
  456
+    return CURLM_OUT_OF_MEMORY;
  457
+
451 458
   /* Now, time to add an easy handle to the multi stack */
452 459
   easy = calloc(1, sizeof(struct Curl_one_easy));
453 460
   if(!easy)
@@ -601,6 +608,7 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
601 608
 {
602 609
   struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
603 610
   struct Curl_one_easy *easy;
  611
+  struct SessionHandle *data = curl_handle;
604 612
 
605 613
   /* First, make some basic checks that the CURLM handle is a good handle */
606 614
   if(!GOOD_MULTI_HANDLE(multi))
@@ -611,7 +619,7 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
611 619
     return CURLM_BAD_EASY_HANDLE;
612 620
 
613 621
   /* pick-up from the 'curl_handle' the kept position in the list */
614  
-  easy = ((struct SessionHandle *)curl_handle)->multi_pos;
  622
+  easy = data->multi_pos;
615 623
 
616 624
   if(easy) {
617 625
     bool premature = (bool)(easy->state != CURLM_STATE_COMPLETED);
@@ -644,6 +652,12 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
644 652
        curl_easy_cleanup is called. */
645 653
     Curl_expire(easy->easy_handle, 0);
646 654
 
  655
+    /* destroy the timeout list that is held in the easy handle */
  656
+    if(data->state.timeoutlist) {
  657
+      Curl_llist_destroy(data->state.timeoutlist, NULL);
  658
+      data->state.timeoutlist = NULL;
  659
+    }
  660
+
647 661
     if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) {
648 662
       /* clear out the usage of the shared DNS cache */
649 663
       easy->easy_handle->dns.hostcache = NULL;
@@ -1652,12 +1666,34 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
1652 1666
     multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
1653 1667
     if(t) {
1654 1668
       struct SessionHandle *d = t->payload;
1655  
-      struct timeval* tv = &d->state.expiretime;
  1669
+      struct timeval *tv = &d->state.expiretime;
  1670
+      struct curl_llist *list = d->state.timeoutlist;
  1671
+      struct curl_llist_element *e;
1656 1672
 
1657  
-      /* clear the expire times within the handles that we remove from the
1658  
-         splay tree */
1659  
-      tv->tv_sec = 0;
1660  
-      tv->tv_usec = 0;
  1673
+      /* move over the timeout list for this specific handle and remove all
  1674
+         timeouts that are now passed tense and store the next pending
  1675
+         timeout in *tv */
  1676
+      for(e = list->head; e; ) {
  1677
+        struct curl_llist_element *n = e->next;
  1678
+        if(curlx_tvdiff(*(struct timeval *)e->ptr, now) < 0)
  1679
+          /* remove outdated entry */
  1680
+          Curl_llist_remove(list, e, NULL);
  1681
+        e = n;
  1682
+      }
  1683
+      if(!list->size)  {
  1684
+        /* clear the expire times within the handles that we remove from the
  1685
+           splay tree */
  1686
+        tv->tv_sec = 0;
  1687
+        tv->tv_usec = 0;
  1688
+      }
  1689
+      else {
  1690
+        e = list->head;
  1691
+        /* copy the first entry to 'tv' */
  1692
+        memcpy(tv, e->ptr, sizeof(*tv));
  1693
+
  1694
+        /* remove first entry from list */
  1695
+        Curl_llist_remove(list, e, NULL);
  1696
+      }
1661 1697
     }
1662 1698
 
1663 1699
   } while(t);
@@ -1670,14 +1706,6 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
1670 1706
   return returncode;
1671 1707
 }
1672 1708
 
1673  
-/* This is called when an easy handle is cleanup'ed that is part of a multi
1674  
-   handle */
1675  
-void Curl_multi_rmeasy(void *multi_handle, CURL *easy_handle)
1676  
-{
1677  
-  curl_multi_remove_handle(multi_handle, easy_handle);
1678  
-}
1679  
-
1680  
-
1681 1709
 CURLMcode curl_multi_cleanup(CURLM *multi_handle)
1682 1710
 {
1683 1711
   struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
@@ -2343,10 +2371,72 @@ static bool isHandleAtHead(struct SessionHandle *handle,
2343 2371
   return FALSE;
2344 2372
 }
2345 2373
 
2346  
-/* given a number of milliseconds from now to use to set the 'act before
2347  
-   this'-time for the transfer, to be extracted by curl_multi_timeout()
  2374
+/*
  2375
+ * multi_freetimeout()
  2376
+ *
  2377
+ * Callback used by the llist system when a single timeout list entry is
  2378
+ * destroyed.
  2379
+ */
  2380
+static void multi_freetimeout(void *user, void *entryptr)
  2381
+{
  2382
+  (void)user;
  2383
+
  2384
+  /* the entry was plain malloc()'ed */
  2385
+  free(entryptr);
  2386
+}
  2387
+
  2388
+/*
  2389
+ * multi_addtimeout()
  2390
+ *
  2391
+ * Add a timestamp to the list of timeouts. Keep the list sorted so that head
  2392
+ * of list is always the timeout nearest in time.
  2393
+ *
  2394
+ */
  2395
+static CURLMcode
  2396
+multi_addtimeout(struct curl_llist *timeoutlist,
  2397
+                 struct timeval *stamp)
  2398
+{
  2399
+  struct curl_llist_element *e;
  2400
+  struct timeval *timedup;
  2401
+  struct curl_llist_element *prev = NULL;
  2402
+
  2403
+  timedup = malloc(sizeof(*timedup));
  2404
+  if(!timedup)
  2405
+    return CURLM_OUT_OF_MEMORY;
  2406
+
  2407
+  /* copy the timestamp */
  2408
+  memcpy(timedup, stamp, sizeof(*timedup));
  2409
+
  2410
+  if(Curl_llist_count(timeoutlist)) {
  2411
+    /* find the correct spot in the list */
  2412
+    for(e = timeoutlist->head; e; e = e->next) {
  2413
+      struct timeval *checktime = e->ptr;
  2414
+      long diff = curlx_tvdiff(*checktime, *timedup);
  2415
+      if(diff > 0)
  2416
+        break;
  2417
+      prev = e;
  2418
+    }
  2419
+
  2420
+  }
  2421
+  /* else
  2422
+     this is the first timeout on the list */
  2423
+
  2424
+  if(!Curl_llist_insert_next(timeoutlist, prev, timedup))
  2425
+    return CURLM_OUT_OF_MEMORY;
  2426
+
  2427
+  return CURLM_OK;
  2428
+}
2348 2429
 
2349  
-   Pass zero to clear the timeout value for this handle.
  2430
+/*
  2431
+ * Curl_expire()
  2432
+ *
  2433
+ * given a number of milliseconds from now to use to set the 'act before
  2434
+ * this'-time for the transfer, to be extracted by curl_multi_timeout()
  2435
+ *
  2436
+ * Note that the timeout will be added to a queue of timeouts if it defines a
  2437
+ * moment in time that is later than the current head of queue.
  2438
+ *
  2439
+ * Pass zero to clear all timeout values for this handle.
2350 2440
 */
2351 2441
 void Curl_expire(struct SessionHandle *data, long milli)
2352 2442
 {
@@ -2364,11 +2454,18 @@ void Curl_expire(struct SessionHandle *data, long milli)
2364 2454
     if(nowp->tv_sec || nowp->tv_usec) {
2365 2455
       /* Since this is an cleared time, we must remove the previous entry from
2366 2456
          the splay tree */
  2457
+      struct curl_llist *list = data->state.timeoutlist;
  2458
+
2367 2459
       rc = Curl_splayremovebyaddr(multi->timetree,
2368 2460
                                   &data->state.timenode,
2369 2461
                                   &multi->timetree);
2370 2462
       if(rc)
2371 2463
         infof(data, "Internal error clearing splay node = %d\n", rc);
  2464
+
  2465
+      /* flush the timeout list too */
  2466
+      while(list->size > 0)
  2467
+        Curl_llist_remove(list, list->tail, NULL);
  2468
+
2372 2469
       infof(data, "Expire cleared\n");
2373 2470
       nowp->tv_sec = 0;
2374 2471
       nowp->tv_usec = 0;
@@ -2394,9 +2491,16 @@ void Curl_expire(struct SessionHandle *data, long milli)
2394 2491
          Compare if the new time is earlier, and only remove-old/add-new if it
2395 2492
          is. */
2396 2493
       long diff = curlx_tvdiff(set, *nowp);
2397  
-      if(diff > 0)
2398  
-        /* the new expire time was later so we don't change this */
  2494
+      if(diff > 0) {
  2495
+        /* the new expire time was later so just add it to the queue
  2496
+           and get out */
  2497
+        multi_addtimeout(data->state.timeoutlist, &set);
2399 2498
         return;
  2499
+      }
  2500
+
  2501
+      /* the new time is newer than the presently set one, so add the current
  2502
+         to the queue and update the head */
  2503
+      multi_addtimeout(data->state.timeoutlist, nowp);
2400 2504
 
2401 2505
       /* Since this is an updated time, we must remove the previous entry from
2402 2506
          the splay tree first and then re-add the new value */
4  lib/multiif.h
@@ -7,7 +7,7 @@
7 7
  *                            | (__| |_| |  _ <| |___
8 8
  *                             \___|\___/|_| \_\_____|
9 9
  *
10  
- * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
  10
+ * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
11 11
  *
12 12
  * This software is licensed as described in the file COPYING, which
13 13
  * you should have received as part of this distribution. The terms
@@ -27,8 +27,6 @@
27 27
  */
28 28
 void Curl_expire(struct SessionHandle *data, long milli);
29 29
 
30  
-void Curl_multi_rmeasy(void *multi, CURL *data);
31  
-
32 30
 bool Curl_multi_canPipeline(const struct Curl_multi* multi);
33 31
 void Curl_multi_handlePipeBreak(struct SessionHandle *data);
34 32
 
10  lib/url.c
@@ -479,7 +479,15 @@ CURLcode Curl_close(struct SessionHandle *data)
479 479
   if(m)
480 480
     /* This handle is still part of a multi handle, take care of this first
481 481
        and detach this handle from there. */
482  
-    Curl_multi_rmeasy(data->multi, data);
  482
+    curl_multi_remove_handle(data->multi, data);
  483
+
  484
+  /* Destroy the timeout list that is held in the easy handle. It is
  485
+     /normally/ done by curl_multi_remove_handle() but this is "just in
  486
+     case" */
  487
+  if(data->state.timeoutlist) {
  488
+    Curl_llist_destroy(data->state.timeoutlist, NULL);
  489
+    data->state.timeoutlist = NULL;
  490
+  }
483 491
 
484 492
   data->magic = 0; /* force a clear AFTER the possibly enforced removal from
485 493
                       the multi handle, since that function uses the magic
1  lib/urldata.h
@@ -1094,6 +1094,7 @@ struct UrlState {
1094 1094
 #endif /* USE_SSLEAY */
1095 1095
   struct timeval expiretime; /* set this with Curl_expire() only */
1096 1096
   struct Curl_tree timenode; /* for the splay stuff */
  1097
+  struct curl_llist *timeoutlist; /* list of pending timeouts */
1097 1098
 
1098 1099
   /* a place to store the most recently set FTP entrypath */
1099 1100
   char *most_recent_ftp_entrypath;

0 notes on commit 232ad65

Please sign in to comment.
Something went wrong with that request. Please try again.