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.
This commit is contained in:
Daniel Stenberg 2010-08-10 11:02:07 +02:00
parent 03da3ba1c0
commit 232ad6549a
5 changed files with 134 additions and 28 deletions

View 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:.

View 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;
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 */

View 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);

View File

@ -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

View 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;