Newer
Older
s = sockbunch[i];
}
if(bitmap & GETSOCK_WRITESOCK(i)) {
ufds[nfds].fd = sockbunch[i];
ufds[nfds].events = POLLOUT;
++nfds;
s = sockbunch[i];
}
if(s == CURL_SOCKET_BAD) {
break;
}
}
easy = easy->next; /* check next handle */
}
/* Add external file descriptions from poll-like struct curl_waitfd */
for(i = 0; i < extra_nfds; i++) {
ufds[nfds].fd = extra_fds[i].fd;
ufds[nfds].events = 0;
if(extra_fds[i].events & CURL_WAIT_POLLIN)
ufds[nfds].events |= POLLIN;
if(extra_fds[i].events & CURL_WAIT_POLLPRI)
ufds[nfds].events |= POLLPRI;
if(extra_fds[i].events & CURL_WAIT_POLLOUT)
ufds[nfds].events |= POLLOUT;
if(nfds)
/* wait... */
i = Curl_poll(ufds, nfds, timeout_ms);
else
i = 0;
if(ret)
*ret = i;
Daniel Stenberg
committed
static CURLMcode multi_runsingle(struct Curl_multi *multi,
Daniel Stenberg
committed
struct Curl_one_easy *easy)
{
struct Curl_message *msg = NULL;
bool connected;
Daniel Stenberg
committed
bool async;
bool dophase_done;
bool done = FALSE;
Daniel Stenberg
committed
CURLMcode result = CURLM_OK;
Daniel Stenberg
committed
struct SingleRequest *k;
struct SessionHandle *data;
Daniel Stenberg
committed
Daniel Stenberg
committed
if(!GOOD_EASY_HANDLE(easy->easy_handle))
return CURLM_BAD_EASY_HANDLE;
data = easy->easy_handle;
Daniel Stenberg
committed
do {
/* this is a single-iteration do-while loop just to allow a
break to skip to the end of it */
bool disconnect_conn = FALSE;
/* Handle the case when the pipe breaks, i.e., the connection
we're using gets cleaned up and we're left with nothing. */
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) {
/* Head back to the CONNECT state */
multistate(easy, CURLM_STATE_CONNECT);
result = CURLM_CALL_MULTI_PERFORM;
easy->result = CURLE_OK;
Daniel Stenberg
committed
}
data->state.pipe_broke = FALSE;
easy->easy_conn = NULL;
break;
}
if(!easy->easy_conn &&
easy->state > CURLM_STATE_CONNECT &&
easy->state < CURLM_STATE_DONE) {
/* In all these states, the code will blindly access 'easy->easy_conn'
so this is precaution that it isn't NULL. And it silences static
analyzers. */
failf(data, "In state %d with no easy_conn, bail out!\n", easy->state);
return CURLM_INTERNAL_ERROR;
}
if(easy->easy_conn && easy->state > CURLM_STATE_CONNECT &&
Daniel Stenberg
committed
easy->state < CURLM_STATE_COMPLETED)
/* Make sure we set the connection's current owner */
easy->easy_conn->data = data;
Daniel Stenberg
committed
if(easy->easy_conn &&
(easy->state >= CURLM_STATE_CONNECT) &&
(easy->state < CURLM_STATE_COMPLETED)) {
/* we need to wait for the connect state as only then is the start time
stored, but we must not check already completed handles */
timeout_ms = Curl_timeleft(data, &now,
(easy->state <= CURLM_STATE_WAITDO)?
TRUE:FALSE);
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);
}
/* 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)
disconnect_conn = TRUE;
else {
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
if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
Daniel Stenberg
committed
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
/* Update sockets here, 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. If the name has not yet been resolved, it is likely
that new sockets have been opened in an attempt to contact
another resolver. */
singlesocket(multi, easy);
Daniel Stenberg
committed
if(dns) {
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
if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
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) {
if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_COMPLETE)
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
if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)
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_CALL_MULTI_PERFORM;
Daniel Stenberg
committed
}
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);
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
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 DO_DONE or DO_MORE */
multistate(easy, easy->easy_conn->bits.do_more?
CURLM_STATE_DO_MORE:
CURLM_STATE_DO_DONE);
result = CURLM_CALL_MULTI_PERFORM;
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:
/*
* When we are connected, DO MORE and then go DO_DONE
*/
easy->result = Curl_do_more(easy->easy_conn, &dophase_done);
Daniel Stenberg
committed
/* No need to remove this handle from the send pipeline here since that
is done in Curl_done() */
if(CURLE_OK == easy->result) {
if(dophase_done) {
multistate(easy, CURLM_STATE_DO_DONE);
Daniel Stenberg
committed
result = CURLM_CALL_MULTI_PERFORM;
else
/* stay in DO_MORE */
result = CURLM_OK;
}
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 */
if(Curl_pgrsUpdate(easy->easy_conn))
easy->result = CURLE_ABORTED_BY_CALLBACK;
else
easy->result = Curl_speedcheck(data, now);
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);
if(easy->result) {
disconnect_conn = TRUE;
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;
}
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);
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;
}
}
else if(easy->state == CURLM_STATE_CONNECT) {
/* Curl_connect() failed */
(void)Curl_posttransfer(data);
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)) {
/* aborted due to progress callback return code must close the
connection */
easy->easy_conn->bits.close = TRUE;
/* if not yet in DONE state, go there, otherwise COMPLETED */
multistate(easy, (easy->state < CURLM_STATE_DONE)?
CURLM_STATE_DONE: CURLM_STATE_COMPLETED);
result = CURLM_CALL_MULTI_PERFORM;
Daniel Stenberg
committed
}
} WHILE_FALSE; /* just to break out from! */
if(CURLM_STATE_COMPLETED == easy->state) {
/* 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 */
/* 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; /* allow cleanup */
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_hash_destroy(multi->sockhash);
multi->sockhash = NULL;
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 */
Curl_hostcache_clean(easy->easy_handle);
Daniel Stenberg
committed
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;
}
Curl_hash_destroy(multi->hostcache);
multi->hostcache = NULL;
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;