Newer
Older
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);
}
/* Force the connection closed because the server could continue to
send us stuff at any time. (The disconnect_conn logic used below
doesn't work at this point). */
easy->easy_conn->bits.close = TRUE;
easy->result = CURLE_OPERATION_TIMEDOUT;
multistate(easy, CURLM_STATE_COMPLETED);
break;
}
}
Daniel Stenberg
committed
switch(easy->state) {
case CURLM_STATE_INIT:
/* init this transfer. */
easy->result=Curl_pretransfer(data);
Daniel Stenberg
committed
if(CURLE_OK == easy->result) {
/* after init, go CONNECT */
multistate(easy, CURLM_STATE_CONNECT);
result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg
committed
data->state.used_interface = Curl_if_multi;
Daniel Stenberg
committed
}
break;
Daniel Stenberg
committed
Daniel Stenberg
committed
case CURLM_STATE_CONNECT:
/* Connect. We get a connection identifier filled in. */
Curl_pgrsTime(data, TIMER_STARTSINGLE);
easy->result = Curl_connect(data, &easy->easy_conn,
Daniel Stenberg
committed
&async, &protocol_connect);
Daniel Stenberg
committed
Daniel Stenberg
committed
if(CURLE_OK == easy->result) {
Daniel Stenberg
committed
/* Add this handle to the send or pend pipeline */
easy->result = addHandleToSendOrPendPipeline(data,
Daniel Stenberg
committed
easy->easy_conn);
if(CURLE_OK == easy->result) {
Daniel Stenberg
committed
if(async)
/* We're now waiting for an asynchronous name lookup */
multistate(easy, CURLM_STATE_WAITRESOLVE);
else {
Daniel Stenberg
committed
/* after the connect has been sent off, go WAITCONNECT unless the
protocol connect is already done and we can go directly to
Daniel Stenberg
committed
WAITDO or DO! */
Daniel Stenberg
committed
result = CURLM_CALL_MULTI_PERFORM;
if(protocol_connect)
Daniel Stenberg
committed
multistate(easy, multi->pipelining_enabled?
CURLM_STATE_WAITDO:CURLM_STATE_DO);
else {
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg
committed
if(easy->easy_conn->bits.tunnel_connecting)
multistate(easy, CURLM_STATE_WAITPROXYCONNECT);
else
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_WAITCONNECT);
}
}
}
Daniel Stenberg
committed
}
break;
Daniel Stenberg
committed
case CURLM_STATE_WAITRESOLVE:
/* awaiting an asynch name resolve to complete */
{
struct Curl_dns_entry *dns = NULL;
/* check if we have the name resolved by now */
easy->result = Curl_resolver_is_resolved(easy->easy_conn, &dns);
Daniel Stenberg
committed
if(dns) {
Daniel Stenberg
committed
/* Update sockets here. Mainly because the socket(s) may have been
closed and the application thus needs to be told, even if it is
likely that the same socket(s) will again be used further down. */
singlesocket(multi, easy);
Daniel Stenberg
committed
/* Perform the next step in the connection phase, and then move on
to the WAITCONNECT state */
easy->result = Curl_async_resolved(easy->easy_conn,
&protocol_connect);
if(CURLE_OK != easy->result)
/* if Curl_async_resolved() returns failure, the connection struct
is already freed and gone */
Daniel Stenberg
committed
easy->easy_conn = NULL; /* no more connection */
Daniel Stenberg
committed
else {
/* call again please so that we get the next socket setup */
result = CURLM_CALL_MULTI_PERFORM;
if(protocol_connect)
Daniel Stenberg
committed
multistate(easy, multi->pipelining_enabled?
CURLM_STATE_WAITDO:CURLM_STATE_DO);
Daniel Stenberg
committed
else {
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg
committed
if(easy->easy_conn->bits.tunnel_connecting)
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_WAITPROXYCONNECT);
else
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_WAITCONNECT);
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
if(CURLE_OK != easy->result) {
/* failure detected */
disconnect_conn = TRUE;
Daniel Stenberg
committed
break;
}
}
break;
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg
committed
case CURLM_STATE_WAITPROXYCONNECT:
/* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */
easy->result = Curl_http_connect(easy->easy_conn, &protocol_connect);
Daniel Stenberg
committed
if(easy->easy_conn->bits.proxy_connect_closed) {
/* reset the error buffer */
if(data->set.errorbuffer)
data->set.errorbuffer[0] = '\0';
data->state.errorbuf = FALSE;
Daniel Stenberg
committed
Daniel Stenberg
committed
easy->result = CURLE_OK;
result = CURLM_CALL_MULTI_PERFORM;
multistate(easy, CURLM_STATE_CONNECT);
Daniel Stenberg
committed
}
else if(CURLE_OK == easy->result) {
Daniel Stenberg
committed
if(!easy->easy_conn->bits.tunnel_connecting)
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_WAITCONNECT);
}
break;
Daniel Stenberg
committed
Daniel Stenberg
committed
case CURLM_STATE_WAITCONNECT:
/* awaiting a completion of an asynch connect */
easy->result = Curl_is_connected(easy->easy_conn,
FIRSTSOCKET,
Daniel Stenberg
committed
&connected);
if(connected) {
if(!easy->result)
/* if everything is still fine we do the protocol-specific connect
setup */
easy->result = Curl_protocol_connect(easy->easy_conn,
&protocol_connect);
}
Daniel Stenberg
committed
if(CURLE_OK != easy->result) {
/* failure detected */
/* Just break, the cleaning up is handled all in one place */
disconnect_conn = TRUE;
break;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
if(connected) {
if(!protocol_connect) {
/* We have a TCP connection, but 'protocol_connect' may be false
and then we continue to 'STATE_PROTOCONNECT'. If protocol
Daniel Stenberg
committed
connect is TRUE, we move on to STATE_DO.
BUT if we are using a proxy we must change to WAITPROXYCONNECT
Daniel Stenberg
committed
#ifndef CURL_DISABLE_HTTP
Daniel Stenberg
committed
if(easy->easy_conn->bits.tunnel_connecting)
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_WAITPROXYCONNECT);
else
#endif
multistate(easy, CURLM_STATE_PROTOCONNECT);
Daniel Stenberg
committed
Daniel Stenberg
committed
}
Daniel Stenberg
committed
else
Daniel Stenberg
committed
/* after the connect has completed, go WAITDO or DO */
multistate(easy, multi->pipelining_enabled?
CURLM_STATE_WAITDO:CURLM_STATE_DO);
Daniel Stenberg
committed
result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg
committed
}
break;
Daniel Stenberg
committed
Daniel Stenberg
committed
case CURLM_STATE_PROTOCONNECT:
/* protocol-specific connect phase */
easy->result = Curl_protocol_connecting(easy->easy_conn,
&protocol_connect);
if((easy->result == CURLE_OK) && protocol_connect) {
Daniel Stenberg
committed
/* after the connect has completed, go WAITDO or DO */
multistate(easy, multi->pipelining_enabled?
CURLM_STATE_WAITDO:CURLM_STATE_DO);
Daniel Stenberg
committed
result = CURLM_CALL_MULTI_PERFORM;
}
else if(easy->result) {
/* failure detected */
Curl_posttransfer(data);
Curl_done(&easy->easy_conn, easy->result, TRUE);
disconnect_conn = TRUE;
Daniel Stenberg
committed
}
break;
case CURLM_STATE_WAITDO:
/* Wait for our turn to DO when we're pipelining requests */
infof(data, "Conn %ld send pipe %zu inuse %d athead %d\n",
easy->easy_conn->connectindex,
Daniel Stenberg
committed
easy->easy_conn->send_pipe->size,
isHandleAtHead(data,
Daniel Stenberg
committed
#endif
Daniel Stenberg
committed
if(!easy->easy_conn->writechannel_inuse &&
isHandleAtHead(data,
Daniel Stenberg
committed
easy->easy_conn->send_pipe)) {
/* Grab the channel */
easy->easy_conn->writechannel_inuse = TRUE;
multistate(easy, CURLM_STATE_DO);
result = CURLM_CALL_MULTI_PERFORM;
}
break;
Daniel Stenberg
committed
case CURLM_STATE_DO:
if(data->set.connect_only) {
Daniel Stenberg
committed
/* keep connection open for application to use the socket */
easy->easy_conn->bits.close = FALSE;
multistate(easy, CURLM_STATE_DONE);
easy->result = CURLE_OK;
result = CURLM_OK;
}
else {
/* Perform the protocol's DO action */
easy->result = Curl_do(&easy->easy_conn,
&dophase_done);
Daniel Stenberg
committed
Daniel Stenberg
committed
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;
}
}
Daniel Stenberg
committed
/* DO was not completed in one function call, we must continue
DOING... */
multistate(easy, CURLM_STATE_DOING);
result = CURLM_OK;
}
/* after DO, go DO_DONE... or DO_MORE */
Daniel Stenberg
committed
else if(easy->easy_conn->bits.do_more) {
/* we're supposed to do more, but we need to sit down, relax
and wait a little while first */
multistate(easy, CURLM_STATE_DO_MORE);
result = CURLM_OK;
}
else {
/* we're done with the DO, now DO_DONE */
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_DO_DONE);
result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg
committed
}
}
else if((CURLE_SEND_ERROR == easy->result) &&
easy->easy_conn->bits.reuse) {
/*
* In this situation, a connection that we were trying to use
* may have unexpectedly died. If possible, send the connection
* back to the CONNECT phase so we can try again.
*/
followtype follow=FOLLOW_NONE;
CURLcode drc;
bool retry = FALSE;
drc = Curl_retry_request(easy->easy_conn, &newurl);
if(drc) {
/* a failure here pretty much implies an out of memory */
easy->result = drc;
disconnect_conn = TRUE;
}
else
Curl_posttransfer(data);
drc = Curl_done(&easy->easy_conn, easy->result, FALSE);
/* When set to retry the connection, we must to go back to
* the CONNECT state */
if(retry) {
if((drc == CURLE_OK) || (drc == CURLE_SEND_ERROR)) {
follow = FOLLOW_RETRY;
drc = Curl_follow(data, newurl, follow);
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
if(drc == CURLE_OK) {
multistate(easy, CURLM_STATE_CONNECT);
result = CURLM_CALL_MULTI_PERFORM;
easy->result = CURLE_OK;
}
else {
/* Follow failed */
easy->result = drc;
free(newurl);
}
}
else {
/* done didn't return OK or SEND_ERROR */
easy->result = drc;
free(newurl);
}
}
else {
/* Have error handler disconnect conn if we can't retry */
disconnect_conn = TRUE;
}
}
else {
/* failure detected */
Curl_posttransfer(data);
Daniel Stenberg
committed
Curl_done(&easy->easy_conn, easy->result, FALSE);
disconnect_conn = TRUE;
}
Daniel Stenberg
committed
}
break;
Daniel Stenberg
committed
Daniel Stenberg
committed
case CURLM_STATE_DOING:
/* we continue DOING until the DO phase is complete */
easy->result = Curl_protocol_doing(easy->easy_conn,
&dophase_done);
Daniel Stenberg
committed
if(CURLE_OK == easy->result) {
if(dophase_done) {
/* after DO, go PERFORM... or DO_MORE */
if(easy->easy_conn->bits.do_more) {
/* we're supposed to do more, but we need to sit down, relax
and wait a little while first */
multistate(easy, CURLM_STATE_DO_MORE);
result = CURLM_OK;
}
else {
/* we're done with the DO, now DO_DONE */
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_DO_DONE);
result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
} /* dophase_done */
}
else {
/* failure detected */
Curl_posttransfer(data);
Daniel Stenberg
committed
Curl_done(&easy->easy_conn, easy->result, FALSE);
disconnect_conn = TRUE;
Daniel Stenberg
committed
}
break;
case CURLM_STATE_DO_MORE:
/* Ready to do more? */
easy->result = Curl_is_connected(easy->easy_conn,
SECONDARYSOCKET,
Daniel Stenberg
committed
&connected);
if(connected) {
/*
* When we are connected, DO MORE and then go DO_DONE
Daniel Stenberg
committed
*/
easy->result = Curl_do_more(easy->easy_conn);
/* No need to remove ourselves from the send pipeline here since that
is done for us in Curl_done() */
Daniel Stenberg
committed
if(CURLE_OK == easy->result) {
multistate(easy, CURLM_STATE_DO_DONE);
Daniel Stenberg
committed
result = CURLM_CALL_MULTI_PERFORM;
else {
/* failure detected */
Curl_posttransfer(data);
Curl_done(&easy->easy_conn, easy->result, FALSE);
disconnect_conn = TRUE;
}
Daniel Stenberg
committed
}
break;
case CURLM_STATE_DO_DONE:
Daniel Stenberg
committed
/* Move ourselves from the send to recv pipeline */
moveHandleFromSendToRecvPipeline(data, easy->easy_conn);
Daniel Stenberg
committed
/* Check if we can move pending requests to send pipe */
checkPendPipeline(easy->easy_conn);
multistate(easy, CURLM_STATE_WAITPERFORM);
result = CURLM_CALL_MULTI_PERFORM;
break;
case CURLM_STATE_WAITPERFORM:
/* Wait for our turn to PERFORM */
Daniel Stenberg
committed
if(!easy->easy_conn->readchannel_inuse &&
isHandleAtHead(data,
Daniel Stenberg
committed
easy->easy_conn->recv_pipe)) {
/* Grab the channel */
easy->easy_conn->readchannel_inuse = TRUE;
multistate(easy, CURLM_STATE_PERFORM);
result = CURLM_CALL_MULTI_PERFORM;
}
Daniel Stenberg
committed
else {
infof(data, "Conn %ld recv pipe %zu inuse %d athead %d\n",
Daniel Stenberg
committed
easy->easy_conn->connectindex,
easy->easy_conn->recv_pipe->size,
isHandleAtHead(data,
Daniel Stenberg
committed
}
#endif
break;
case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */
/* if both rates are within spec, resume transfer */
Curl_pgrsUpdate(easy->easy_conn);
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)))
Daniel Stenberg
committed
case CURLM_STATE_PERFORM:
/* 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;
}
Daniel Stenberg
committed
/* read/write data if it is ready to do so */
easy->result = Curl_readwrite(easy->easy_conn, &done);
if(!(k->keepon & KEEP_RECV)) {
/* We're done receiving */
Daniel Stenberg
committed
easy->easy_conn->readchannel_inuse = FALSE;
}
if(!(k->keepon & KEEP_SEND)) {
/* We're done sending */
Daniel Stenberg
committed
easy->easy_conn->writechannel_inuse = FALSE;
}
Daniel Stenberg
committed
/* The transfer phase returned error, we mark the connection to get
Daniel Stenberg
committed
* 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
* a protocol which uses two "channels" like FTP, as then the error
* happened in the data connection.
*/
if(!(easy->easy_conn->handler->flags & PROTOPT_DUAL))
Daniel Stenberg
committed
easy->easy_conn->bits.close = TRUE;
Daniel Stenberg
committed
Curl_posttransfer(data);
Daniel Stenberg
committed
Curl_done(&easy->easy_conn, easy->result, FALSE);
Daniel Stenberg
committed
}
bool retry = FALSE;
Daniel Stenberg
committed
followtype follow=FOLLOW_NONE;
Daniel Stenberg
committed
easy->result = Curl_retry_request(easy->easy_conn, &newurl);
if(!easy->result)
Daniel Stenberg
committed
/* call this even if the readwrite function returned error */
Curl_posttransfer(data);
Daniel Stenberg
committed
moveHandleFromRecvToDonePipeline(data,
easy->easy_conn);
Daniel Stenberg
committed
/* expire the new receiving pipeline head */
if(easy->easy_conn->recv_pipe->head)
Curl_expire(easy->easy_conn->recv_pipe->head->ptr, 1);
/* Check if we can move pending requests to send pipe */
checkPendPipeline(easy->easy_conn);
/* When we follow redirects or is set to retry the connection, we must
to go back to the CONNECT state */
if(data->req.newurl || retry) {
Daniel Stenberg
committed
if(!retry) {
/* if the URL is a follow-location and not just a retried request
then figure out the URL here */
newurl = data->req.newurl;
data->req.newurl = NULL;
Daniel Stenberg
committed
follow = FOLLOW_REDIR;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
else
follow = FOLLOW_RETRY;
Daniel Stenberg
committed
easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE);
Daniel Stenberg
committed
if(easy->result == CURLE_OK)
easy->result = Curl_follow(data, newurl, follow);
Daniel Stenberg
committed
if(CURLE_OK == easy->result) {
multistate(easy, CURLM_STATE_CONNECT);
result = CURLM_CALL_MULTI_PERFORM;
else if(newurl)
Daniel Stenberg
committed
/* Since we "took it", we are in charge of freeing this on
failure */
free(newurl);
Daniel Stenberg
committed
else {
/* after the transfer is done, go DONE */
/* but first check to see if we got a location info even though we're
not following redirects */
if(data->req.location) {
newurl = data->req.location;
data->req.location = NULL;
easy->result = Curl_follow(data, newurl, FOLLOW_FAKE);
free(newurl);
}
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_DONE);
result = CURLM_CALL_MULTI_PERFORM;
}
}
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
case CURLM_STATE_DONE:
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(data,
easy->easy_conn->recv_pipe);
Curl_removeHandleFromPipeline(data,
easy->easy_conn->done_pipe);
/* Check if we can move pending requests to send pipe */
checkPendPipeline(easy->easy_conn);
if(easy->easy_conn->bits.stream_was_rewound) {
/* This request read past its response boundary so we quickly let
the other requests consume those bytes since there is no
guarantee that the socket will become active again */
result = CURLM_CALL_MULTI_PERFORM;
}
/* post-transfer command */
easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE);
/*
* If there are other handles on the pipeline, Curl_done won't set
* easy_conn to NULL. In such a case, curl_multi_remove_handle() can
* access free'd data, if the connection is free'd and the handle
* removed before we perform the processing in CURLM_STATE_COMPLETED
*/
if(easy->easy_conn)
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);
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
case CURLM_STATE_COMPLETED:
/* this is a completed transfer, it is likely to still be connected */
Daniel Stenberg
committed
/* This node should be delinked from the list now and we should post
an information message that we are complete. */
/* 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 */
Daniel Stenberg
committed
break;
case CURLM_STATE_MSGSENT:
return CURLM_OK; /* do nothing */
Daniel Stenberg
committed
default:
return CURLM_INTERNAL_ERROR;
}
if(CURLM_STATE_COMPLETED > easy->state) {
Daniel Stenberg
committed
if(CURLE_OK != easy->result) {
/*
* If an error was returned, and we aren't in completed state now,
* then we go to completed and consider this transfer aborted.
*/
/* NOTE: no attempt to disconnect connections must be made
in the case blocks above - cleanup happens only here */
data->state.pipe_broke = FALSE;
Daniel Stenberg
committed
Daniel Stenberg
committed
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(data,
easy->easy_conn->send_pipe);
Curl_removeHandleFromPipeline(data,
easy->easy_conn->recv_pipe);
Curl_removeHandleFromPipeline(data,
easy->easy_conn->done_pipe);
Daniel Stenberg
committed
/* Check if we can move pending requests to send pipe */
checkPendPipeline(easy->easy_conn);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
if(disconnect_conn) {
/* disconnect properly */
Curl_disconnect(easy->easy_conn, /* dead_connection */ FALSE);
/* This is where we make sure that the easy_conn pointer is reset.
We don't have to do this in every case block above where a
failure is detected */
easy->easy_conn = NULL;
}
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_COMPLETED);
Daniel Stenberg
committed
}
/* 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;
Daniel Stenberg
committed
}
} WHILE_FALSE; /* just to break out from! */
if(CURLM_STATE_COMPLETED == easy->state) {
if(data->dns.hostcachetype == HCACHE_MULTI) {
Daniel Stenberg
committed
/* clear out the usage of the shared DNS cache */
data->dns.hostcache = NULL;
data->dns.hostcachetype = HCACHE_NONE;
Daniel Stenberg
committed
}
/* now fill in the Curl_message with this info */
msg = &easy->msg;
Daniel Stenberg
committed
msg->extmsg.msg = CURLMSG_DONE;
msg->extmsg.easy_handle = data;
Daniel Stenberg
committed
msg->extmsg.data.result = easy->result;
result = multi_addmsg(multi, msg);
multistate(easy, CURLM_STATE_MSGSENT);
}
}
Daniel Stenberg
committed
CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
struct Curl_one_easy *easy;
CURLMcode returncode=CURLM_OK;
struct Curl_tree *t;
struct timeval now = Curl_tvnow();
Daniel Stenberg
committed
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
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;
}
}
Daniel Stenberg
committed
do
result = multi_runsingle(multi, now, easy);
while(CURLM_CALL_MULTI_PERFORM == result);
Daniel Stenberg
committed
if(easy->easy_handle->set.wildcardmatch) {
/* destruct wildcard structures if it is needed */
if(wc->state == CURLWC_DONE || result)
Curl_wildcard_dtor(wc);
}
Daniel Stenberg
committed
if(result)
returncode = result;
easy = easy->next; /* operate on next handle */
}
/*
* 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!
Daniel Stenberg
committed
*/
do {
multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
if(t)
/* the removed may have another timeout in queue */
(void)add_next_timeout(now, multi, t->payload);
Daniel Stenberg
committed
Daniel Stenberg
committed
} while(t);
Daniel Stenberg
committed
*running_handles = multi->num_alive;
if(CURLM_OK >= returncode)
update_timer(multi);
Daniel Stenberg
committed
Daniel Stenberg
committed
return returncode;
}
CURLMcode curl_multi_cleanup(CURLM *multi_handle)
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
struct Curl_one_easy *easy;
struct Curl_one_easy *nexteasy;
int i;
struct closure *cl;
struct closure *n;
if(GOOD_MULTI_HANDLE(multi)) {
multi->type = 0; /* not good anymore */
Curl_hash_destroy(multi->hostcache);
Daniel Stenberg
committed
Curl_hash_destroy(multi->sockhash);
multi->hostcache = NULL;
multi->sockhash = NULL;
/* go over all connections that have close actions */
for(i=0; i< multi->connc->num; i++) {
if(multi->connc->connects[i] &&
multi->connc->connects[i]->handler->flags & PROTOPT_CLOSEACTION) {
Curl_disconnect(multi->connc->connects[i], FALSE);
Daniel Stenberg
committed
multi->connc->connects[i] = NULL;
}
}
/* now walk through the list of handles we kept around only to be
able to close connections "properly" */
cl = multi->closure;
while(cl) {
cl->easy_handle->state.shared_conn = NULL; /* no more shared */
Daniel Stenberg
committed
if(cl->easy_handle->state.closed)
/* close handle only if curl_easy_cleanup() already has been called
for this easy handle */
Curl_close(cl->easy_handle);
n = cl->next;
free(cl);
cl= n;
}
Curl_rm_connc(multi->connc);
/* remove the pending list of messages */
Curl_llist_destroy(multi->msglist, NULL);
easy = multi->easy.next;
while(easy != &multi->easy) {
nexteasy=easy->next;
Daniel Stenberg
committed
if(easy->easy_handle->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;
}
/* Clear the pointer to the connection cache */
easy->easy_handle->state.connc = NULL;
Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */
free(easy);
easy = nexteasy;
}
free(multi);
return CURLM_OK;
}
else
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) && Curl_llist_count(multi->msglist)) {
/* there is one or more messages in the list */
struct curl_llist_element *e;
/* extract the head of the list to return */
e = multi->msglist->head;
msg = e->ptr;
/* remove the extracted entry */
Curl_llist_remove(multi->msglist, e, NULL);
*msgs_in_queue = curlx_uztosi(Curl_llist_count(multi->msglist));
}
else
Daniel Stenberg
committed
/*
Daniel Stenberg
committed
* singlesocket() checks what sockets we deal with and their "action state"
* and if we have a different state in any of those sockets from last time we
* call the callback accordingly.
Daniel Stenberg
committed
*/
static void singlesocket(struct Curl_multi *multi,
struct Curl_one_easy *easy)
{
Daniel Stenberg
committed
curl_socket_t socks[MAX_SOCKSPEREASYHANDLE];
Daniel Stenberg
committed
int i;
struct Curl_sh_entry *entry;
curl_socket_t s;
int num;
Daniel Stenberg
committed
unsigned int curraction;
Daniel Stenberg
committed
struct Curl_one_easy *easy_by_hash;
bool remove_sock_from_hash;
Daniel Stenberg
committed
for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++)
Daniel Stenberg
committed
socks[i] = CURL_SOCKET_BAD;
Daniel Stenberg
committed
/* Fill in the 'current' struct with the state as it is now: what sockets to
supervise and for what actions */
Daniel Stenberg
committed
curraction = multi_getsock(easy, socks, MAX_SOCKSPEREASYHANDLE);
Daniel Stenberg
committed
/* We have 0 .. N sockets already and we get to know about the 0 .. M
sockets we should have from now on. Detect the differences, remove no
longer supervised ones and add new ones */
Daniel Stenberg
committed
/* walk over the sockets we got right now */
for(i=0; (i< MAX_SOCKSPEREASYHANDLE) &&
Daniel Stenberg
committed
(curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i)));
i++) {
int action = CURL_POLL_NONE;
Daniel Stenberg
committed
Daniel Stenberg
committed
s = socks[i];
Daniel Stenberg
committed
/* get it from the hash */
entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
Daniel Stenberg
committed
Daniel Stenberg
committed
if(curraction & GETSOCK_READSOCK(i))
action |= CURL_POLL_IN;
Daniel Stenberg
committed
if(curraction & GETSOCK_WRITESOCK(i))
action |= CURL_POLL_OUT;
Daniel Stenberg
committed
if(entry) {
/* yeps, already present so check if it has the same action set */
if(entry->action == action)
/* same, continue */
continue;
}
else {
/* this is a socket we didn't have before, add it! */
entry = sh_addentry(multi->sockhash, s, easy->easy_handle);
if(!entry)
/* fatal */
return;
}
Daniel Stenberg
committed
/* we know (entry != NULL) at this point, see the logic above */
multi->socket_cb(easy->easy_handle,
s,
action,
multi->socket_userp,
Daniel Stenberg
committed
entry->action = action; /* store the current action state */
}
Daniel Stenberg
committed
num = i; /* number of sockets */
/* when we've walked over all the sockets we should have right now, we must
make sure to detect sockets that are removed */
for(i=0; i< easy->numsocks; i++) {
int j;
s = easy->sockets[i];
for(j=0; j<num; j++) {
Daniel Stenberg
committed
if(s == socks[j]) {
/* this is still supervised */
s = CURL_SOCKET_BAD;
break;
Daniel Stenberg
committed
}
}
if(s != CURL_SOCKET_BAD) {
Daniel Stenberg
committed
/* this socket has been removed. Tell the app to remove it */
remove_sock_from_hash = TRUE;
entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
if(entry) {
Daniel Stenberg
committed
/* check if the socket to be removed serves a connection which has
other easy-s in a pipeline. In this case the socket should not be
removed. */
struct connectdata *easy_conn;
easy_by_hash = entry->easy->multi_pos;
easy_conn = easy_by_hash->easy_conn;
if(easy_conn) {
if(easy_conn->recv_pipe && easy_conn->recv_pipe->size > 1) {
Daniel Stenberg
committed
/* the handle should not be removed from the pipe yet */
remove_sock_from_hash = FALSE;
/* Update the sockhash entry to instead point to the next in line
for the recv_pipe, or the first (in case this particular easy
isn't already) */
if(entry->easy == easy->easy_handle) {
if(isHandleAtHead(easy->easy_handle, easy_conn->recv_pipe))
Daniel Stenberg
committed
entry->easy = easy_conn->recv_pipe->head->next->ptr;
else
entry->easy = easy_conn->recv_pipe->head->ptr;
}
}
if(easy_conn->send_pipe && easy_conn->send_pipe->size > 1) {
Daniel Stenberg
committed
/* the handle should not be removed from the pipe yet */
remove_sock_from_hash = FALSE;
/* Update the sockhash entry to instead point to the next in line
for the send_pipe, or the first (in case this particular easy
isn't already) */
if(entry->easy == easy->easy_handle) {
if(isHandleAtHead(easy->easy_handle, easy_conn->send_pipe))
Daniel Stenberg
committed
entry->easy = easy_conn->send_pipe->head->next->ptr;
else
entry->easy = easy_conn->send_pipe->head->ptr;
}
}
/* Don't worry about overwriting recv_pipe head with send_pipe_head,
when action will be asked on the socket (see multi_socket()), the
head of the correct pipe will be taken according to the
action. */
}
}
else
/* just a precaution, this socket really SHOULD be in the hash already
but in case it isn't, we don't have to tell the app to remove it
either since it never got to know about it */
Daniel Stenberg
committed
remove_sock_from_hash = FALSE;
if(remove_sock_from_hash) {
Daniel Stenberg
committed
multi->socket_cb(easy->easy_handle,
s,
CURL_POLL_REMOVE,
Daniel Stenberg
committed
multi->socket_userp,