Newer
Older
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
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;
}
break;
Daniel Stenberg
committed
case CURLM_STATE_CONNECT_PEND:
/* We will stay here until there is a connection available. Then
we try again in the CURLM_STATE_CONNECT state. */
break;
Daniel Stenberg
committed
case CURLM_STATE_CONNECT:
/* Connect. We want to 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);
if(CURLE_NO_CONNECTION_AVAILABLE == easy->result) {
/* There was no connection available. We will go to the pending
state and wait for an available connection. */
multistate(easy, CURLM_STATE_CONNECT_PEND);
break;
}
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 = Curl_add_handle_to_pipeline(data, 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, "WAITDO: Conn %ld send pipe %zu inuse %s athead %s\n",
easy->easy_conn->connection_id,
Daniel Stenberg
committed
easy->easy_conn->send_pipe->size,
easy->easy_conn->writechannel_inuse?"TRUE":"FALSE",
isHandleAtHead(data,
easy->easy_conn->send_pipe)?"TRUE":"FALSE");
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);
/* When Curl_do() returns failure, easy->easy_conn might be NULL! */
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);
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
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);
if(easy->easy_conn)
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, &control);
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(control) {
/* if positive, advance to DO_DONE
if negative, go back to DOING */
multistate(easy, control==1?
CURLM_STATE_DO_DONE:
CURLM_STATE_DOING);
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 */
Curl_move_handle_from_send_to_recv_pipe(data, easy->easy_conn);
Daniel Stenberg
committed
/* Check if we can move pending requests to send pipe */
Curl_multi_process_pending_handles(multi);
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, "WAITPERFORM: Conn %ld recv pipe %zu inuse %s athead %s\n",
easy->easy_conn->connection_id,
Daniel Stenberg
committed
easy->easy_conn->recv_pipe->size,
easy->easy_conn->readchannel_inuse?"TRUE":"FALSE",
isHandleAtHead(data,
easy->easy_conn->recv_pipe)?"TRUE":"FALSE");
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:
{
char *newurl = NULL;
bool retry = FALSE;
/* 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;
}
if(done || (easy->result == CURLE_RECV_ERROR)) {
/* If CURLE_RECV_ERROR happens early enough, we assume it was a race
* condition and the server closed the re-used connection exactly when
* we wanted to use it, so figure out if that is indeed the case.
*/
CURLcode ret = Curl_retry_request(easy->easy_conn, &newurl);
if(!ret)
retry = (newurl)?TRUE:FALSE;
if(retry) {
/* if we are to retry, set the result to OK and consider the
request as done */
easy->result = CURLE_OK;
/*
* 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
}
Daniel Stenberg
committed
followtype follow=FOLLOW_NONE;
Daniel Stenberg
committed
Daniel Stenberg
committed
/* call this even if the readwrite function returned error */
Curl_posttransfer(data);
Daniel Stenberg
committed
Curl_removeHandleFromPipeline(data, easy->easy_conn->recv_pipe);
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 */
Curl_multi_process_pending_handles(multi);
Daniel Stenberg
committed
/* 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);
if(CURLE_OK == easy->result) {
easy->result = Curl_follow(data, newurl, follow);
if(CURLE_OK == easy->result) {
multistate(easy, CURLM_STATE_CONNECT);
result = CURLM_CALL_MULTI_PERFORM;
newurl = NULL; /* handed over the memory ownership to
Curl_follow(), make sure we don't free() it
here */
}
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) {
if(newurl)
free(newurl);
newurl = data->req.location;
data->req.location = NULL;
easy->result = Curl_follow(data, newurl, FOLLOW_FAKE);
if(CURLE_OK == easy->result)
newurl = NULL; /* allocation was handed over Curl_follow() */
else
disconnect_conn = TRUE;
}
Daniel Stenberg
committed
multistate(easy, CURLM_STATE_DONE);
result = CURLM_CALL_MULTI_PERFORM;
}
}
if(newurl)
free(newurl);
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
case CURLM_STATE_DONE:
if(easy->easy_conn) {
/* Remove ourselves from the receive pipeline, if we are there. */
Curl_removeHandleFromPipeline(data,
easy->easy_conn->recv_pipe);
/* Check if we can move pending requests to send pipe */
Curl_multi_process_pending_handles(multi);
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(easy->mstate < CURLM_STATE_COMPLETED) {
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);
Daniel Stenberg
committed
/* Check if we can move pending requests to send pipe */
Curl_multi_process_pending_handles(multi);
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->mstate == 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->mstate < 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->mstate) {
/* 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 SessionHandle *easy;
Daniel Stenberg
committed
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->easyp;
while(easy) {
CURLMcode result;
struct WildcardData *wc = &easy->wildcard;
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
/* 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;
}
static void close_all_connections(struct Curl_multi *multi)
{
struct connectdata *conn;
conn = Curl_conncache_find_first_connection(multi->conn_cache);
while(conn) {
conn->data = multi->closure_handle;
/* This will remove the connection from the cache */
(void)Curl_disconnect(conn, FALSE);
conn = Curl_conncache_find_first_connection(multi->conn_cache);
}
}
CURLMcode curl_multi_cleanup(CURLM *multi_handle)
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
struct SessionHandle *easy;
struct SessionHandle *nexteasy;
if(GOOD_MULTI_HANDLE(multi)) {
multi->type = 0; /* not good anymore */
/* Close all the connections in the connection cache */
close_all_connections(multi);
if(multi->closure_handle) {
multi->closure_handle->dns.hostcache = multi->hostcache;
Curl_hostcache_clean(multi->closure_handle,
multi->closure_handle->dns.hostcache);
Curl_close(multi->closure_handle);
multi->closure_handle = NULL;
Curl_hash_destroy(multi->sockhash);
multi->sockhash = NULL;
Curl_conncache_destroy(multi->conn_cache);
multi->conn_cache = NULL;
/* remove the pending list of messages */
Curl_llist_destroy(multi->msglist, NULL);
easy = multi->easyp;
while(easy) {
nexteasy=easy->next;
if(easy->dns.hostcachetype == HCACHE_MULTI) {
Daniel Stenberg
committed
/* clear out the usage of the shared DNS cache */
Curl_hostcache_clean(easy, easy->dns.hostcache);
easy->dns.hostcache = NULL;
easy->dns.hostcachetype = HCACHE_NONE;
Daniel Stenberg
committed
}
/* Clear the pointer to the connection cache */
easy->state.conn_cache = NULL;
Curl_easy_addmulti(easy, NULL); /* clear the association */
easy = nexteasy;
}
Curl_hash_destroy(multi->hostcache);
multi->hostcache = NULL;
/* Free the blacklists by setting them to NULL */
Curl_pipeline_set_site_blacklist(NULL, &multi->pipelining_site_bl);
Curl_pipeline_set_server_blacklist(NULL, &multi->pipelining_server_bl);
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 SessionHandle *easy)
Daniel Stenberg
committed
{
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
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);
if(!entry)
/* fatal */
return;
}
Daniel Stenberg
committed
/* we know (entry != NULL) at this point, see the logic above */
if(multi->socket_cb)
s,
action,
multi->socket_userp,
entry->socketp);
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. */