diff options
Diffstat (limited to 'lib/multi.c')
-rw-r--r-- | lib/multi.c | 656 |
1 files changed, 475 insertions, 181 deletions
diff --git a/lib/multi.c b/lib/multi.c index 476cb813..875e136e 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -61,7 +61,6 @@ struct Curl_message { /* the 'CURLMsg' is the part that is visible to the external user */ struct CURLMsg extmsg; - struct Curl_message *next; }; /* NOTE: if you add a state here, add the name to the statename[] array as @@ -84,7 +83,7 @@ typedef enum { CURLM_STATE_TOOFAST, /* wait because limit-rate exceeded */ CURLM_STATE_DONE, /* post data transfer operation */ CURLM_STATE_COMPLETED, /* operation complete */ - + CURLM_STATE_MSGSENT, /* the operation complete message is sent */ CURLM_STATE_LAST /* not a true state, never use this */ } CURLMstate; @@ -110,12 +109,7 @@ struct Curl_one_easy { CURLMstate state; /* the handle's state */ CURLcode result; /* previous result */ - struct Curl_message *msg; /* A pointer to one single posted message. - Cleanup should be done on this pointer NOT on - the linked list in Curl_multi. This message - will be deleted when this handle is removed - from the multi-handle */ - int msg_num; /* number of messages left in 'msg' to return */ + struct Curl_message msg; /* A single posted message. */ /* Array with the plain socket numbers this handle takes care of, in no particular order. Note that all sockets are added to the sockhash, where @@ -142,10 +136,11 @@ struct Curl_multi { struct Curl_one_easy easy; int num_easy; /* amount of entries in the linked list above. */ - int num_msgs; /* amount of messages in the easy handles */ int num_alive; /* amount of easy handles that are added but have not yet reached COMPLETE state */ + struct curl_llist *msglist; /* a list of messages from completed transfers */ + /* callback function and user data pointer for the *socket() API */ curl_socket_callback socket_cb; void *socket_userp; @@ -197,6 +192,9 @@ static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle, struct connectdata *conn); static bool isHandleAtHead(struct SessionHandle *handle, struct curl_llist *pipeline); +static CURLMcode add_next_timeout(struct timeval now, + struct Curl_multi *multi, + struct SessionHandle *d); #ifdef DEBUGBUILD static const char * const statename[]={ @@ -216,9 +214,12 @@ static const char * const statename[]={ "TOOFAST", "DONE", "COMPLETED", + "MSGSENT", }; #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) { @@ -360,6 +361,33 @@ static struct curl_hash *sh_init(void) sh_freeentry); } +/* + * multi_addmsg() + * + * Called when a transfer is completed. Adds the given msg pointer to + * the list kept in the multi handle. + */ +static CURLMcode multi_addmsg(struct Curl_multi *multi, + struct Curl_message *msg) +{ + if(!Curl_llist_insert_next(multi->msglist, multi->msglist->tail, msg)) + return CURLM_OUT_OF_MEMORY; + + return CURLM_OK; +} + +/* + * multi_freeamsg() + * + * Callback used by the llist system when a single list entry is destroyed. + */ +static void multi_freeamsg(void *a, void *b) +{ + (void)a; + (void)b; +} + + CURLM *curl_multi_init(void) { struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); @@ -370,27 +398,20 @@ CURLM *curl_multi_init(void) multi->type = CURL_MULTI_HANDLE; multi->hostcache = Curl_mk_dnscache(); - if(!multi->hostcache) { - /* failure, free mem and bail out */ - free(multi); - return NULL; - } + if(!multi->hostcache) + goto error; multi->sockhash = sh_init(); - if(!multi->sockhash) { - /* failure, free mem and bail out */ - Curl_hash_destroy(multi->hostcache); - free(multi); - return NULL; - } + if(!multi->sockhash) + goto error; multi->connc = Curl_mk_connc(CONNCACHE_MULTI, -1L); - if(!multi->connc) { - Curl_hash_destroy(multi->sockhash); - Curl_hash_destroy(multi->hostcache); - free(multi); - return NULL; - } + if(!multi->connc) + goto error; + + multi->msglist = Curl_llist_alloc(multi_freeamsg); + if(!multi->msglist) + goto error; /* Let's make the doubly-linked list a circular list. This makes the linked list code simpler and allows inserting at the end @@ -399,6 +420,17 @@ CURLM *curl_multi_init(void) multi->easy.prev = &multi->easy; return (CURLM *) multi; + + error: + if(multi->sockhash) + Curl_hash_destroy(multi->sockhash); + if(multi->hostcache) + Curl_hash_destroy(multi->hostcache); + if(multi->connc) + Curl_rm_connc(multi->connc); + + free(multi); + return NULL; } CURLMcode curl_multi_add_handle(CURLM *multi_handle, @@ -408,6 +440,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)) @@ -422,6 +455,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) @@ -575,6 +612,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)) @@ -585,10 +623,10 @@ 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); + bool premature = (bool)(easy->state < CURLM_STATE_COMPLETED); bool easy_owns_conn = (bool)(easy->easy_conn && (easy->easy_conn->data == easy->easy_handle)); @@ -618,6 +656,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; @@ -656,6 +700,10 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, to that since we're not part of that handle anymore */ easy->easy_handle->state.connc = NULL; + /* Since we return the connection back to the communal connection pool + we mark the last connection as inaccessible */ + easy->easy_handle->state.lastconnect = -1; + /* Modify the connectindex since this handle can't point to the connection cache anymore. @@ -679,6 +727,22 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association to this multi handle */ + { + /* make sure there's no pending message in the queue sent from this easy + handle */ + struct curl_llist_element *e; + + for(e = multi->msglist->head; e; e = e->next) { + struct Curl_message *msg = e->ptr; + + if(msg->extmsg.easy_handle == easy->easy_handle) { + Curl_llist_remove(multi->msglist, e, NULL); + /* there can only be one from this specific handle */ + break; + } + } + } + /* make the previous node point to our next */ if(easy->prev) easy->prev->next = easy->next; @@ -693,8 +757,6 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, /* NOTE NOTE NOTE We do not touch the easy handle here! */ - if(easy->msg) - free(easy->msg); free(easy); multi->num_easy--; /* one less to care about now */ @@ -779,6 +841,7 @@ static int multi_getsock(struct Curl_one_easy *easy, to be present */ case CURLM_STATE_TOOFAST: /* returns 0, so will not select. */ case CURLM_STATE_COMPLETED: + case CURLM_STATE_MSGSENT: case CURLM_STATE_INIT: case CURLM_STATE_CONNECT: case CURLM_STATE_WAITDO: @@ -866,6 +929,7 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle, } static CURLMcode multi_runsingle(struct Curl_multi *multi, + struct timeval now, struct Curl_one_easy *easy) { struct Curl_message *msg = NULL; @@ -876,10 +940,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, bool done = FALSE; CURLMcode result = CURLM_OK; struct SingleRequest *k; + struct SessionHandle *data; + long timeout_ms; if(!GOOD_EASY_HANDLE(easy->easy_handle)) return CURLM_BAD_EASY_HANDLE; + data = easy->easy_handle; + do { /* this is a do-while loop just to allow a break to skip to the end of it */ @@ -887,18 +955,18 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Handle the case when the pipe breaks, i.e., the connection we're using gets cleaned up and we're left with nothing. */ - if(easy->easy_handle->state.pipe_broke) { - infof(easy->easy_handle, "Pipe broke: handle 0x%p, url = %s\n", - easy, easy->easy_handle->state.path); + if(data->state.pipe_broke) { + infof(data, "Pipe broke: handle 0x%p, url = %s\n", + easy, data->state.path); - if(easy->state != CURLM_STATE_COMPLETED) { + if(easy->state < CURLM_STATE_COMPLETED) { /* Head back to the CONNECT state */ multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; easy->result = CURLE_OK; } - easy->easy_handle->state.pipe_broke = FALSE; + data->state.pipe_broke = FALSE; easy->easy_conn = NULL; break; } @@ -906,31 +974,59 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(easy->easy_conn && easy->state > CURLM_STATE_CONNECT && easy->state < CURLM_STATE_COMPLETED) /* Make sure we set the connection's current owner */ - easy->easy_conn->data = easy->easy_handle; + easy->easy_conn->data = data; + + if(easy->easy_conn && (easy->state >= CURLM_STATE_CONNECT)) { + /* we need to wait for the connect state as only then is the + start time stored */ + + timeout_ms = Curl_timeleft(easy->easy_conn, &now, + easy->state <= CURLM_STATE_WAITDO); + + if(timeout_ms < 0) { + /* Handle timed out */ + if(easy->state == CURLM_STATE_WAITRESOLVE) + failf(data, "Resolving timed out after %ld milliseconds", + Curl_tvdiff(now, data->progress.t_startsingle)); + else if(easy->state == CURLM_STATE_WAITCONNECT) + failf(data, "Connection timed out after %ld milliseconds", + Curl_tvdiff(now, data->progress.t_startsingle)); + else { + k = &data->req; + failf(data, "Operation timed out after %ld milliseconds with %" + FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received", + Curl_tvdiff(now, data->progress.t_startsingle), k->bytecount, + k->size); + } + easy->result = CURLE_OPERATION_TIMEDOUT; + multistate(easy, CURLM_STATE_COMPLETED); + break; + } + } switch(easy->state) { case CURLM_STATE_INIT: /* init this transfer. */ - easy->result=Curl_pretransfer(easy->easy_handle); + easy->result=Curl_pretransfer(data); if(CURLE_OK == easy->result) { /* after init, go CONNECT */ multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; - easy->easy_handle->state.used_interface = Curl_if_multi; + data->state.used_interface = Curl_if_multi; } break; case CURLM_STATE_CONNECT: /* Connect. We get a connection identifier filled in. */ - Curl_pgrsTime(easy->easy_handle, TIMER_STARTSINGLE); - easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn, + Curl_pgrsTime(data, TIMER_STARTSINGLE); + easy->result = Curl_connect(data, &easy->easy_conn, &async, &protocol_connect); if(CURLE_OK == easy->result) { /* Add this handle to the send or pend pipeline */ - easy->result = addHandleToSendOrPendPipeline(easy->easy_handle, + easy->result = addHandleToSendOrPendPipeline(data, easy->easy_conn); if(CURLE_OK == easy->result) { if(async) @@ -1013,9 +1109,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(easy->easy_conn->bits.proxy_connect_closed) { /* reset the error buffer */ - if(easy->easy_handle->set.errorbuffer) - easy->easy_handle->set.errorbuffer[0] = '\0'; - easy->easy_handle->state.errorbuf = FALSE; + if(data->set.errorbuffer) + data->set.errorbuffer[0] = '\0'; + data->state.errorbuf = FALSE; easy->result = CURLE_OK; result = CURLM_CALL_MULTI_PERFORM; @@ -1087,7 +1183,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else if(easy->result) { /* failure detected */ - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); Curl_done(&easy->easy_conn, easy->result, FALSE); disconnect_conn = TRUE; } @@ -1096,15 +1192,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_WAITDO: /* Wait for our turn to DO when we're pipelining requests */ #ifdef DEBUGBUILD - infof(easy->easy_handle, "Conn %ld send pipe %zu inuse %d athead %d\n", + infof(data, "Conn %ld send pipe %zu inuse %d athead %d\n", easy->easy_conn->connectindex, easy->easy_conn->send_pipe->size, easy->easy_conn->writechannel_inuse?1:0, - isHandleAtHead(easy->easy_handle, + isHandleAtHead(data, easy->easy_conn->send_pipe)?1:0); #endif if(!easy->easy_conn->writechannel_inuse && - isHandleAtHead(easy->easy_handle, + isHandleAtHead(data, easy->easy_conn->send_pipe)) { /* Grab the channel */ easy->easy_conn->writechannel_inuse = TRUE; @@ -1114,7 +1210,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case CURLM_STATE_DO: - if(easy->easy_handle->set.connect_only) { + if(data->set.connect_only) { /* keep connection open for application to use the socket */ easy->easy_conn->bits.close = FALSE; multistate(easy, CURLM_STATE_DONE); @@ -1128,6 +1224,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(CURLE_OK == easy->result) { if(!dophase_done) { + /* some steps needed for wildcard matching */ + if(data->set.wildcardmatch) { + struct WildcardData *wc = &data->wildcard; + if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { + /* skip some states if it is important */ + Curl_done(&easy->easy_conn, CURLE_OK, FALSE); + multistate(easy, CURLM_STATE_DONE); + result = CURLM_CALL_MULTI_PERFORM; + break; + } + } /* DO was not completed in one function call, we must continue DOING... */ multistate(easy, CURLM_STATE_DOING); @@ -1168,7 +1275,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, else retry = (bool)(newurl?TRUE:FALSE); - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); drc = Curl_done(&easy->easy_conn, easy->result, FALSE); /* When set to retry the connection, we must to go back to @@ -1176,7 +1283,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(retry) { if ((drc == CURLE_OK) || (drc == CURLE_SEND_ERROR)) { follow = FOLLOW_RETRY; - drc = Curl_follow(easy->easy_handle, newurl, follow); + drc = Curl_follow(data, newurl, follow); if(drc == CURLE_OK) { multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; @@ -1201,7 +1308,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); Curl_done(&easy->easy_conn, easy->result, FALSE); disconnect_conn = TRUE; } @@ -1230,7 +1337,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); Curl_done(&easy->easy_conn, easy->result, FALSE); disconnect_conn = TRUE; } @@ -1256,7 +1363,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else { /* failure detected */ - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); Curl_done(&easy->easy_conn, easy->result, FALSE); disconnect_conn = TRUE; } @@ -1265,7 +1372,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_DO_DONE: /* Move ourselves from the send to recv pipeline */ - moveHandleFromSendToRecvPipeline(easy->easy_handle, easy->easy_conn); + moveHandleFromSendToRecvPipeline(data, easy->easy_conn); /* Check if we can move pending requests to send pipe */ checkPendPipeline(easy->easy_conn); multistate(easy, CURLM_STATE_WAITPERFORM); @@ -1275,7 +1382,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_WAITPERFORM: /* Wait for our turn to PERFORM */ if(!easy->easy_conn->readchannel_inuse && - isHandleAtHead(easy->easy_handle, + isHandleAtHead(data, easy->easy_conn->recv_pipe)) { /* Grab the channel */ easy->easy_conn->readchannel_inuse = TRUE; @@ -1284,11 +1391,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } #ifdef DEBUGBUILD else { - infof(easy->easy_handle, "Conn %ld recv pipe %zu inuse %d athead %d\n", + infof(data, "Conn %ld recv pipe %zu inuse %d athead %d\n", easy->easy_conn->connectindex, easy->easy_conn->recv_pipe->size, easy->easy_conn->readchannel_inuse?1:0, - isHandleAtHead(easy->easy_handle, + isHandleAtHead(data, easy->easy_conn->recv_pipe)?1:0); } #endif @@ -1296,37 +1403,50 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */ /* if both rates are within spec, resume transfer */ - Curl_pgrsUpdate(easy->easy_conn); - if( ( ( easy->easy_handle->set.max_send_speed == 0 ) || - ( easy->easy_handle->progress.ulspeed < - easy->easy_handle->set.max_send_speed ) ) && - ( ( easy->easy_handle->set.max_recv_speed == 0 ) || - ( easy->easy_handle->progress.dlspeed < - easy->easy_handle->set.max_recv_speed ) ) - ) + if( ( (data->set.max_send_speed == 0) || + (data->progress.ulspeed < data->set.max_send_speed )) && + ( (data->set.max_recv_speed == 0) || + (data->progress.dlspeed < data->set.max_recv_speed) ) ) multistate(easy, CURLM_STATE_PERFORM); break; case CURLM_STATE_PERFORM: - /* check if over speed */ - if( ( ( easy->easy_handle->set.max_send_speed > 0 ) && - ( easy->easy_handle->progress.ulspeed > - easy->easy_handle->set.max_send_speed ) ) || - ( ( easy->easy_handle->set.max_recv_speed > 0 ) && - ( easy->easy_handle->progress.dlspeed > - easy->easy_handle->set.max_recv_speed ) ) - ) { - /* Transfer is over the speed limit. Change state. TODO: Call - * Curl_expire() with the time left until we're targeted to be below - * the speed limit again. */ - multistate(easy, CURLM_STATE_TOOFAST ); + /* check if over send speed */ + if( (data->set.max_send_speed > 0) && + (data->progress.ulspeed > data->set.max_send_speed) ) { + int buffersize; + + multistate(easy, CURLM_STATE_TOOFAST); + + /* calculate upload rate-limitation timeout. */ + buffersize = (int)(data->set.buffer_size ? + data->set.buffer_size : BUFSIZE); + timeout_ms = Curl_sleep_time(data->set.max_send_speed, + data->progress.ulspeed, buffersize); + Curl_expire(data, timeout_ms); + break; + } + + /* check if over recv speed */ + if( (data->set.max_recv_speed > 0) && + (data->progress.dlspeed > data->set.max_recv_speed) ) { + int buffersize; + + multistate(easy, CURLM_STATE_TOOFAST); + + /* Calculate download rate-limitation timeout. */ + buffersize = (int)(data->set.buffer_size ? + data->set.buffer_size : BUFSIZE); + timeout_ms = Curl_sleep_time(data->set.max_recv_speed, + data->progress.dlspeed, buffersize); + Curl_expire(data, timeout_ms); break; } /* read/write data if it is ready to do so */ easy->result = Curl_readwrite(easy->easy_conn, &done); - k = &easy->easy_handle->req; + k = &data->req; if(!(k->keepon & KEEP_RECV)) { /* We're done receiving */ @@ -1338,7 +1458,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, easy->easy_conn->writechannel_inuse = FALSE; } - if(easy->result) { + if(easy->result) { /* The transfer phase returned error, we mark the connection to get * closed to prevent being re-used. This is because we can't possibly * know if the connection is in a good shape or not now. Unless it is @@ -1348,7 +1468,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!(easy->easy_conn->protocol & PROT_DUALCHANNEL)) easy->easy_conn->bits.close = TRUE; - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); Curl_done(&easy->easy_conn, easy->result, FALSE); } else if(TRUE == done) { @@ -1361,10 +1481,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, retry = (bool)(newurl?TRUE:FALSE); /* call this even if the readwrite function returned error */ - Curl_posttransfer(easy->easy_handle); + Curl_posttransfer(data); /* we're no longer receving */ - moveHandleFromRecvToDonePipeline(easy->easy_handle, + moveHandleFromRecvToDonePipeline(data, easy->easy_conn); /* expire the new receiving pipeline head */ @@ -1376,19 +1496,19 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* When we follow redirects or is set to retry the connection, we must to go back to the CONNECT state */ - if(easy->easy_handle->req.newurl || retry) { + if(data->req.newurl || retry) { if(!retry) { /* if the URL is a follow-location and not just a retried request then figure out the URL here */ - newurl = easy->easy_handle->req.newurl; - easy->easy_handle->req.newurl = NULL; + newurl = data->req.newurl; + data->req.newurl = NULL; follow = FOLLOW_REDIR; } else follow = FOLLOW_RETRY; easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE); if(easy->result == CURLE_OK) - easy->result = Curl_follow(easy->easy_handle, newurl, follow); + easy->result = Curl_follow(data, newurl, follow); if(CURLE_OK == easy->result) { multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; @@ -1403,10 +1523,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* but first check to see if we got a location info even though we're not following redirects */ - if (easy->easy_handle->req.location) { - newurl = easy->easy_handle->req.location; - easy->easy_handle->req.location = NULL; - easy->result = Curl_follow(easy->easy_handle, newurl, FOLLOW_FAKE); + if (data->req.location) { + newurl = data->req.location; + data->req.location = NULL; + easy->result = Curl_follow(data, newurl, FOLLOW_FAKE); if (easy->result) free(newurl); } @@ -1423,9 +1543,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(easy->easy_conn) { /* Remove ourselves from the receive and done pipelines. Handle should be on one of these lists, depending upon how we got here. */ - Curl_removeHandleFromPipeline(easy->easy_handle, + Curl_removeHandleFromPipeline(data, easy->easy_conn->recv_pipe); - Curl_removeHandleFromPipeline(easy->easy_handle, + Curl_removeHandleFromPipeline(data, easy->easy_conn->done_pipe); /* Check if we can move pending requests to send pipe */ checkPendPipeline(easy->easy_conn); @@ -1449,6 +1569,16 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, easy->easy_conn = NULL; } + if(data->set.wildcardmatch) { + if(data->wildcard.state != CURLWC_DONE) { + /* if a wildcard is set and we are not ending -> lets start again + with CURLM_STATE_INIT */ + result = CURLM_CALL_MULTI_PERFORM; + multistate(easy, CURLM_STATE_INIT); + break; + } + } + /* after we have DONE what we're supposed to do, go COMPLETED, and it doesn't matter what the Curl_done() returned! */ multistate(easy, CURLM_STATE_COMPLETED); @@ -1464,13 +1594,18 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Important: reset the conn pointer so that we don't point to memory that could be freed anytime */ easy->easy_conn = NULL; + + Curl_expire(data, 0); /* stop all timers */ break; + case CURLM_STATE_MSGSENT: + return CURLM_OK; /* do nothing */ + default: return CURLM_INTERNAL_ERROR; } - if(CURLM_STATE_COMPLETED != easy->state) { + if(CURLM_STATE_COMPLETED > easy->state) { if(CURLE_OK != easy->result) { /* * If an error was returned, and we aren't in completed state now, @@ -1480,17 +1615,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* NOTE: no attempt to disconnect connections must be made in the case blocks above - cleanup happens only here */ - easy->easy_handle->state.pipe_broke = FALSE; + data->state.pipe_broke = FALSE; if(easy->easy_conn) { /* if this has a connection, unsubscribe from the pipelines */ easy->easy_conn->writechannel_inuse = FALSE; easy->easy_conn->readchannel_inuse = FALSE; - Curl_removeHandleFromPipeline(easy->easy_handle, + Curl_removeHandleFromPipeline(data, easy->easy_conn->send_pipe); - Curl_removeHandleFromPipeline(easy->easy_handle, + Curl_removeHandleFromPipeline(data, easy->easy_conn->recv_pipe); - Curl_removeHandleFromPipeline(easy->easy_handle, + Curl_removeHandleFromPipeline(data, easy->easy_conn->done_pipe); /* Check if we can move pending requests to send pipe */ checkPendPipeline(easy->easy_conn); @@ -1507,30 +1642,29 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, multistate(easy, CURLM_STATE_COMPLETED); } + /* if there's still a connection to use, call the progress function */ + else if(easy->easy_conn && Curl_pgrsUpdate(easy->easy_conn)) + easy->result = CURLE_ABORTED_BY_CALLBACK; } } while(0); - if((CURLM_STATE_COMPLETED == easy->state) && !easy->msg) { - if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) { + + if(CURLM_STATE_COMPLETED == easy->state) { + if(data->dns.hostcachetype == HCACHE_MULTI) { /* clear out the usage of the shared DNS cache */ - easy->easy_handle->dns.hostcache = NULL; - easy->easy_handle->dns.hostcachetype = HCACHE_NONE; + data->dns.hostcache = NULL; + data->dns.hostcachetype = HCACHE_NONE; } - /* now add a node to the Curl_message linked list with this info */ - msg = malloc(sizeof(struct Curl_message)); - - if(!msg) - return CURLM_OUT_OF_MEMORY; + /* now fill in the Curl_message with this info */ + msg = &easy->msg; msg->extmsg.msg = CURLMSG_DONE; - msg->extmsg.easy_handle = easy->easy_handle; + msg->extmsg.easy_handle = data; msg->extmsg.data.result = easy->result; - msg->next = NULL; - easy->msg = msg; - easy->msg_num = 1; /* there is one unread message here */ + result = multi_addmsg(multi, msg); - multi->num_msgs++; /* increase message counter */ + multistate(easy, CURLM_STATE_MSGSENT); } return result; @@ -1543,6 +1677,7 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) struct Curl_one_easy *easy; CURLMcode returncode=CURLM_OK; struct Curl_tree *t; + struct timeval now = Curl_tvnow(); if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -1550,11 +1685,26 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy=multi->easy.next; while(easy != &multi->easy) { CURLMcode result; + struct WildcardData *wc = &easy->easy_handle->wildcard; + + if(easy->easy_handle->set.wildcardmatch) { + if(!wc->filelist) { + CURLcode ret = Curl_wildcard_init(wc); /* init wildcard structures */ + if(ret) + return CURLM_OUT_OF_MEMORY; + } + } do - result = multi_runsingle(multi, easy); + result = multi_runsingle(multi, now, easy); while (CURLM_CALL_MULTI_PERFORM == result); + if(easy->easy_handle->set.wildcardmatch) { + /* destruct wildcard structures if it is needed */ + if(wc->state == CURLWC_DONE || result) + Curl_wildcard_dtor(wc); + } + if(result) returncode = result; @@ -1565,20 +1715,17 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) * Simply remove all expired timers from the splay since handles are dealt * with unconditionally by this function and curl_multi_timeout() requires * that already passed/handled expire times are removed from the splay. + * + * It is important that the 'now' value is set at the entry of this function + * and not for the current time as it may have ticked a little while since + * then and then we risk this loop to remove timers that actually have not + * been handled! */ do { - struct timeval now = Curl_tvnow(); - multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) { - struct SessionHandle *d = t->payload; - struct timeval* tv = &d->state.expiretime; - - /* clear the expire times within the handles that we remove from the - splay tree */ - tv->tv_sec = 0; - tv->tv_usec = 0; - } + if(t) + /* the removed may have another timeout in queue */ + (void)add_next_timeout(now, multi, t->payload); } while(t); @@ -1590,14 +1737,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; @@ -1638,6 +1777,9 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle) Curl_rm_connc(multi->connc); + /* remove the pending list of messages */ + Curl_llist_destroy(multi->msglist, NULL); + /* remove all easy handles */ easy = multi->easy.next; while(easy != &multi->easy) { @@ -1653,8 +1795,6 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle) Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */ - if(easy->msg) - free(easy->msg); free(easy); easy = nexteasy; } @@ -1667,33 +1807,38 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle) return CURLM_BAD_HANDLE; } +/* + * curl_multi_info_read() + * + * This function is the primary way for a multi/multi_socket application to + * figure out if a transfer has ended. We MUST make this function as fast as + * possible as it will be polled frequently and we MUST NOT scan any lists in + * here to figure out things. We must scale fine to thousands of handles and + * beyond. The current design is fully O(1). + */ + CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue) { struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_message *msg; *msgs_in_queue = 0; /* default to none */ - if(GOOD_MULTI_HANDLE(multi)) { - struct Curl_one_easy *easy; + if(GOOD_MULTI_HANDLE(multi) && Curl_llist_count(multi->msglist)) { + /* there is one or more messages in the list */ + struct curl_llist_element *e; - if(!multi->num_msgs) - return NULL; /* no messages left to return */ + /* extract the head of the list to return */ + e = multi->msglist->head; - easy=multi->easy.next; - while(easy != &multi->easy) { - if(easy->msg_num) { - easy->msg_num--; - break; - } - easy = easy->next; - } - if(!easy) - return NULL; /* this means internal count confusion really */ + msg = e->ptr; - multi->num_msgs--; - *msgs_in_queue = multi->num_msgs; + /* remove the extracted entry */ + Curl_llist_remove(multi->msglist, e, NULL); - return &easy->msg->extmsg; + *msgs_in_queue = (int)Curl_llist_count(multi->msglist); + + return &msg->extmsg; } else return NULL; @@ -1757,11 +1902,12 @@ static void singlesocket(struct Curl_multi *multi, return; } + /* we know (entry != NULL) at this point, see the logic above */ multi->socket_cb(easy->easy_handle, s, action, multi->socket_userp, - entry ? entry->socketp : NULL); + entry->socketp); entry->action = action; /* store the current action state */ } @@ -1851,6 +1997,62 @@ static void singlesocket(struct Curl_multi *multi, easy->numsocks = num; } +/* + * add_next_timeout() + * + * Each SessionHandle has a list of timeouts. The add_next_timeout() is called + * when it has just been removed from the splay tree because the timeout has + * expired. This function is then to advance in the list to pick the next + * timeout to use (skip the already expired ones) and add this node back to + * the splay tree again. + * + * The splay tree only has each sessionhandle as a single node and the nearest + * timeout is used to sort it on. + */ +static CURLMcode add_next_timeout(struct timeval now, + struct Curl_multi *multi, + struct SessionHandle *d) +{ + struct timeval *tv = &d->state.expiretime; + struct curl_llist *list = d->state.timeoutlist; + struct curl_llist_element *e; + + /* 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; + long diff = curlx_tvdiff(*(struct timeval *)e->ptr, now); + if(diff <= 0) + /* remove outdated entry */ + Curl_llist_remove(list, e, NULL); + else + /* the list is sorted so get out on the first mismatch */ + break; + 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); + + /* insert this node again into the splay */ + multi->timetree = Curl_splayinsert(*tv, multi->timetree, + &d->state.timenode); + } + return CURLM_OK; +} + + static CURLMcode multi_socket(struct Curl_multi *multi, bool checkall, curl_socket_t s, @@ -1860,6 +2062,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, CURLMcode result = CURLM_OK; struct SessionHandle *data = NULL; struct Curl_tree *t; + struct timeval now = Curl_tvnow(); if(checkall) { struct Curl_one_easy *easyp; @@ -1914,7 +2117,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, data->set.one_easy->easy_conn->cselect_bits = ev_bitmask; do - result = multi_runsingle(multi, data->set.one_easy); + result = multi_runsingle(multi, now, data->set.one_easy); while (CURLM_CALL_MULTI_PERFORM == result); if(data->set.one_easy->easy_conn) @@ -1934,18 +2137,23 @@ static CURLMcode multi_socket(struct Curl_multi *multi, } } + now.tv_usec += 40000; /* compensate for bad precision timers that might've + triggered too early */ + if(now.tv_usec > 1000000) { + now.tv_sec++; + now.tv_usec -= 1000000; + } + /* * The loop following here will go on as long as there are expire-times left * to process in the splay and 'data' will be re-assigned for every expired * handle we deal with. */ do { - struct timeval now; - /* the first loop lap 'data' can be NULL */ if(data) { do - result = multi_runsingle(multi, data->set.one_easy); + result = multi_runsingle(multi, now, data->set.one_easy); while (CURLM_CALL_MULTI_PERFORM == result); if(CURLM_OK >= result) @@ -1957,20 +2165,10 @@ static CURLMcode multi_socket(struct Curl_multi *multi, /* Check if there's one (more) expired timer to deal with! This function extracts a matching node if there is one */ - now = Curl_tvnow(); - now.tv_usec += 1000; /* to compensate for the truncating of 999us to 0ms, - we always add time here to make the comparison - below better */ - multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); if(t) { - /* assign 'data' to be the easy handle we just removed from the splay - tree */ - data = t->payload; - /* clear the expire time within the handle we removed from the - splay tree */ - data->state.expiretime.tv_sec = 0; - data->state.expiretime.tv_usec = 0; + data = t->payload; /* assign this for next loop */ + (void)add_next_timeout(now, multi, t->payload); } } while(t); @@ -2106,12 +2304,22 @@ CURLMcode curl_multi_timeout(CURLM *multi_handle, static int update_timer(struct Curl_multi *multi) { long timeout_ms; + if(!multi->timer_cb) return 0; - if( multi_timeout(multi, &timeout_ms) != CURLM_OK ) + if(multi_timeout(multi, &timeout_ms)) { return -1; - if( timeout_ms < 0 ) + } + if( timeout_ms < 0 ) { + static const struct timeval none={0,0}; + if(Curl_splaycomparekeys(none, multi->timer_lastcall)) { + multi->timer_lastcall = none; + /* there's no timeout now but there was one previously, tell the app to + disable it */ + return multi->timer_cb((CURLM*)multi, -1, multi->timer_userp); + } return 0; + } /* When multi_timeout() is done, multi->timetree points to the node with the * timeout we got the (relative) time-out time for. We can thus easily check @@ -2129,7 +2337,9 @@ static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, struct connectdata *conn) { size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; + struct curl_llist_element *sendhead = conn->send_pipe->head; struct curl_llist *pipeline; + CURLcode rc; if(!Curl_isPipeliningEnabled(handle) || pipeLen == 0) @@ -2142,7 +2352,17 @@ static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, pipeline = conn->pend_pipe; } - return Curl_addHandleToPipeline(handle, pipeline); + rc = Curl_addHandleToPipeline(handle, pipeline); + + if (pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) { + /* this is a new one as head, expire it */ + conn->writechannel_inuse = FALSE; /* not in use yet */ + infof(conn->data, "%p is at send pipe head!\n", + conn->send_pipe->head->ptr); + Curl_expire(conn->send_pipe->head->ptr, 1); + } + + return rc; } static int checkPendPipeline(struct connectdata *conn) @@ -2242,10 +2462,74 @@ 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; + + /* 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 */ - Pass zero to clear the timeout value for this handle. + if(!Curl_llist_insert_next(timeoutlist, prev, timedup)) { + free(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) { @@ -2263,11 +2547,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; @@ -2293,9 +2584,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 */ @@ -2307,10 +2605,6 @@ void Curl_expire(struct SessionHandle *data, long milli) } *nowp = set; -#if 0 - infof(data, "Expire at %ld / %ld (%ldms) %p\n", - (long)nowp->tv_sec, (long)nowp->tv_usec, milli, data); -#endif data->state.timenode.payload = data; multi->timetree = Curl_splayinsert(*nowp, multi->timetree, @@ -2462,7 +2756,7 @@ void Curl_multi_dump(const struct Curl_multi *multi_handle) fprintf(stderr, "* Multi status: %d handles, %d alive\n", multi->num_easy, multi->num_alive); for(easy=multi->easy.next; easy != &multi->easy; easy = easy->next) { - if(easy->state != CURLM_STATE_COMPLETED) { + if(easy->state < CURLM_STATE_COMPLETED) { /* only display handles that are not completed */ fprintf(stderr, "handle %p, state %s, %d sockets\n", (void *)easy->easy_handle, |