From 232ad6549a684505efcbb6ed9d7a78943cc5f817 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 10 Aug 2010 11:02:07 +0200 Subject: [PATCH] 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. --- docs/KNOWN_BUGS | 5 -- lib/multi.c | 142 +++++++++++++++++++++++++++++++++++++++++------- lib/multiif.h | 4 +- lib/url.c | 10 +++- lib/urldata.h | 1 + 5 files changed, 134 insertions(+), 28 deletions(-) diff --git a/docs/KNOWN_BUGS b/docs/KNOWN_BUGS index 42611d62c..96478917d 100644 --- a/docs/KNOWN_BUGS +++ b/docs/KNOWN_BUGS @@ -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:. diff --git a/lib/multi.c b/lib/multi.c index c449542d6..69b80f0ee 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -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; - 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 @@ 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 */ diff --git a/lib/multiif.h b/lib/multiif.h index 798544e06..76918181e 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2010, Daniel Stenberg, , 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 fd6443a59..ac621f220 100644 --- 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 diff --git a/lib/urldata.h b/lib/urldata.h index 7919921f7..de9acf8bf 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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;