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;