Newer
Older
Daniel Stenberg
committed
/* check if we have the name resolved by now */
easy->result = Curl_is_resolved(easy->easy_conn, &dns);
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(easy->easy_handle->set.errorbuffer)
easy->easy_handle->set.errorbuffer[0] = '\0';
easy->easy_handle->state.errorbuf = FALSE;
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) {
/* see if we need to do any proxy magic first once we connected */
easy->result = Curl_connected_proxy(easy->easy_conn);
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(easy->easy_handle);
Daniel Stenberg
committed
Curl_done(&easy->easy_conn, easy->result, FALSE);
disconnect_conn = TRUE;
Daniel Stenberg
committed
}
break;
case CURLM_STATE_WAITDO:
/* Wait for our turn to DO when we're pipelining requests */
easy->easy_conn->connectindex,
Daniel Stenberg
committed
easy->easy_conn->send_pipe->size,
Daniel Stenberg
committed
isHandleAtHead(easy->easy_handle,
Daniel Stenberg
committed
#endif
Daniel Stenberg
committed
if(!easy->easy_conn->writechannel_inuse &&
Daniel Stenberg
committed
isHandleAtHead(easy->easy_handle,
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(easy->easy_handle->set.connect_only) {
/* 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) {
/* 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
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
Curl_posttransfer(easy->easy_handle);
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(easy->easy_handle, newurl, follow);
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(easy->easy_handle);
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(easy->easy_handle);
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(easy->easy_handle);
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(easy->easy_handle, easy->easy_conn);
/* 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 &&
Daniel Stenberg
committed
isHandleAtHead(easy->easy_handle,
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 {
Daniel Stenberg
committed
easy->easy_conn->connectindex,
easy->easy_conn->recv_pipe->size,
Daniel Stenberg
committed
isHandleAtHead(easy->easy_handle,
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);
Daniel Stenberg
committed
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 ) )
Daniel Stenberg
committed
case CURLM_STATE_PERFORM:
/* check if over speed */
Daniel Stenberg
committed
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 );
break;
}
Daniel Stenberg
committed
/* read/write data if it is ready to do so */
easy->result = Curl_readwrite(easy->easy_conn, &done);
Daniel Stenberg
committed
k = &easy->easy_handle->req;
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
if(easy->result) {
/* 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->protocol & PROT_DUALCHANNEL))
easy->easy_conn->bits.close = TRUE;
Daniel Stenberg
committed
Curl_posttransfer(easy->easy_handle);
Daniel Stenberg
committed
Curl_done(&easy->easy_conn, easy->result, FALSE);
Daniel Stenberg
committed
}
else if(TRUE == done) {
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(easy->easy_handle);
Daniel Stenberg
committed
Daniel Stenberg
committed
/* we're no longer receving */
moveHandleFromRecvToDonePipeline(easy->easy_handle,
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 */
Daniel Stenberg
committed
if(easy->easy_handle->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 */
Daniel Stenberg
committed
newurl = easy->easy_handle->req.newurl;
easy->easy_handle->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)
Daniel Stenberg
committed
easy->result = Curl_follow(easy->easy_handle, 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 (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 (easy->result)
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(easy->easy_handle,
easy->easy_conn->recv_pipe);
Curl_removeHandleFromPipeline(easy->easy_handle,
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;
}
/* 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;
Daniel Stenberg
committed
break;
Daniel Stenberg
committed
default:
return CURLM_INTERNAL_ERROR;
}
Daniel Stenberg
committed
if(CURLM_STATE_COMPLETED != easy->state) {
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 */
Daniel Stenberg
committed
easy->easy_handle->state.pipe_broke = FALSE;
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(easy->easy_handle,
easy->easy_conn->send_pipe);
Curl_removeHandleFromPipeline(easy->easy_handle,
easy->easy_conn->recv_pipe);
Curl_removeHandleFromPipeline(easy->easy_handle,
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) {
Curl_disconnect(easy->easy_conn); /* disconnect properly */
/* 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
}
Daniel Stenberg
committed
}
Daniel Stenberg
committed
} while(0);
Daniel Stenberg
committed
if((CURLM_STATE_COMPLETED == easy->state) && !easy->msg) {
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;
}
Daniel Stenberg
committed
/* now add a node to the Curl_message linked list with this info */
msg = malloc(sizeof(struct Curl_message));
Daniel Stenberg
committed
if(!msg)
return CURLM_OUT_OF_MEMORY;
Daniel Stenberg
committed
msg->extmsg.msg = CURLMSG_DONE;
msg->extmsg.easy_handle = easy->easy_handle;
msg->extmsg.data.result = easy->result;
msg->next = NULL;
Daniel Stenberg
committed
easy->msg = msg;
easy->msg_num = 1; /* there is one unread message here */
Daniel Stenberg
committed
multi->num_msgs++; /* increase message counter */
}
}
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;
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
easy=multi->easy.next;
while(easy != &multi->easy) {
CURLMcode result;
Daniel Stenberg
committed
do
result = multi_runsingle(multi, easy);
while (CURLM_CALL_MULTI_PERFORM == result);
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.
*/
do {
struct timeval now = Curl_tvnow();
multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
Daniel Stenberg
committed
if(t) {
Daniel Stenberg
committed
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;
}
Daniel Stenberg
committed
} while(t);
Daniel Stenberg
committed
*running_handles = multi->num_alive;
Daniel Stenberg
committed
if( CURLM_OK >= returncode )
update_timer(multi);
Daniel Stenberg
committed
Daniel Stenberg
committed
return returncode;
}
Daniel Stenberg
committed
/* 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;
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] &&
Daniel Stenberg
committed
multi->connc->connects[i]->protocol & PROT_CLOSEACTION) {
Curl_disconnect(multi->connc->connects[i]);
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);
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 */
Daniel Stenberg
committed
if(easy->msg)
free(easy->msg);
free(easy);
easy = nexteasy;
}
free(multi);
return CURLM_OK;
}
else
return CURLM_BAD_HANDLE;
}
CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue)
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
*msgs_in_queue = 0; /* default to none */
if(GOOD_MULTI_HANDLE(multi)) {
struct Curl_one_easy *easy;
if(!multi->num_msgs)
return NULL; /* no messages left to return */
easy=multi->easy.next;
Daniel Stenberg
committed
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 */
multi->num_msgs--;
*msgs_in_queue = multi->num_msgs;
return &easy->msg->extmsg;
}
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
multi->socket_cb(easy->easy_handle,
s,
action,
multi->socket_userp,
entry ? entry->socketp : NULL);
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
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
/* 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) {
/* 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))
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) {
/* 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))
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,
Daniel Stenberg
committed
entry ? entry->socketp : NULL);
Daniel Stenberg
committed
sh_delentry(multi->sockhash, s);
}
Daniel Stenberg
committed
Daniel Stenberg
committed
}
}
Daniel Stenberg
committed
memcpy(easy->sockets, socks, num*sizeof(curl_socket_t));
easy->numsocks = num;
Daniel Stenberg
committed
}
static CURLMcode multi_socket(struct Curl_multi *multi,
bool checkall,
Daniel Stenberg
committed
curl_socket_t s,
Daniel Stenberg
committed
int ev_bitmask,
Daniel Stenberg
committed
int *running_handles)
Daniel Stenberg
committed
{
CURLMcode result = CURLM_OK;
struct SessionHandle *data = NULL;
struct Curl_tree *t;
if(checkall) {
struct Curl_one_easy *easyp;
Daniel Stenberg
committed
/* *perform() deals with running_handles on its own */
Daniel Stenberg
committed
result = curl_multi_perform(multi, running_handles);
Daniel Stenberg
committed
/* walk through each easy handle and do the socket state change magic
and callbacks */
easyp=multi->easy.next;
while(easyp != &multi->easy) {
Daniel Stenberg
committed
singlesocket(multi, easyp);
easyp = easyp->next;
}
Daniel Stenberg
committed
/* or should we fall-through and do the timer-based stuff? */
Daniel Stenberg
committed
return result;
}
Daniel Stenberg
committed
else if(s != CURL_SOCKET_TIMEOUT) {
Daniel Stenberg
committed
struct Curl_sh_entry *entry =
Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
if(!entry)
Daniel Stenberg
committed
/* Unmatched socket, we can't act on it but we ignore this fact. In
real-world tests it has been proved that libevent can in fact give
the application actions even though the socket was just previously
asked to get removed, so thus we better survive stray socket actions
and just move on. */
;
else {
data = entry->easy;
Daniel Stenberg
committed
Daniel Stenberg
committed
if(data->magic != CURLEASY_MAGIC_NUMBER)
/* bad bad bad bad bad bad bad */
return CURLM_INTERNAL_ERROR;
Daniel Stenberg
committed
/* If the pipeline is enabled, take the handle which is in the head of
the pipeline. If we should write into the socket, take the send_pipe
head. If we should read from the socket, take the recv_pipe head. */
if(data->set.one_easy->easy_conn) {
if ((ev_bitmask & CURL_POLL_OUT) &&
data->set.one_easy->easy_conn->send_pipe &&
data->set.one_easy->easy_conn->send_pipe->head)
data = data->set.one_easy->easy_conn->send_pipe->head->ptr;
else if ((ev_bitmask & CURL_POLL_IN) &&
data->set.one_easy->easy_conn->recv_pipe &&
data->set.one_easy->easy_conn->recv_pipe->head)
Daniel Stenberg
committed
data = data->set.one_easy->easy_conn->recv_pipe->head->ptr;
}
Daniel Stenberg
committed
if(data->set.one_easy->easy_conn) /* set socket event bitmask */
data->set.one_easy->easy_conn->cselect_bits = ev_bitmask;
Daniel Stenberg
committed
Daniel Stenberg
committed
do
result = multi_runsingle(multi, data->set.one_easy);
while (CURLM_CALL_MULTI_PERFORM == result);
Daniel Stenberg
committed
Daniel Stenberg
committed
if(data->set.one_easy->easy_conn)
data->set.one_easy->easy_conn->cselect_bits = 0;
Daniel Stenberg
committed
Daniel Stenberg
committed
if(CURLM_OK >= result)
/* get the socket(s) and check if the state has been changed since
last */
singlesocket(multi, data->set.one_easy);
Daniel Stenberg
committed
Daniel Stenberg
committed
/* Now we fall-through and do the timer-based stuff, since we don't want
to force the user to have to deal with timeouts as long as at least
one connection in fact has traffic. */
Daniel Stenberg
committed
data = NULL; /* set data to NULL again to avoid calling
multi_runsingle() in case there's no need to */
}
Daniel Stenberg
committed
}
/*
* 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) {
Daniel Stenberg
committed
do
result = multi_runsingle(multi, data->set.one_easy);
while (CURLM_CALL_MULTI_PERFORM == result);
Daniel Stenberg
committed
Daniel Stenberg
committed
/* get the socket(s) and check if the state has been changed since
last */
singlesocket(multi, data->set.one_easy);
}
/* Check if there's one (more) expired timer to deal with! This function
extracts a matching node if there is one */
now = Curl_tvnow();
Daniel Stenberg
committed
now.tv_usec += 1000; /* to compensate for the truncating of 999us to 0ms,
we always add time here to make the comparison
below better */
Daniel Stenberg
committed
multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);