Newer
Older
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();
now.tv_usec += 40000; /* compensate for bad precision timers */
if(now.tv_usec > 1000000) {
now.tv_sec++;
now.tv_usec -= 1000000;
}
Daniel Stenberg
committed
multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
Daniel Stenberg
committed
if(t) {
/* assign 'data' to be the easy handle we just removed from the splay
tree */
Daniel Stenberg
committed
data = t->payload;
Daniel Stenberg
committed
/* clear the expire time within the handle we removed from the
splay tree */
data->state.expiretime.tv_sec = 0;
data->state.expiretime.tv_usec = 0;
}
Daniel Stenberg
committed
} while(t);
Daniel Stenberg
committed
*running_handles = multi->num_alive;
Daniel Stenberg
committed
return result;
}
Daniel Stenberg
committed
CURLMcode curl_multi_setopt(CURLM *multi_handle,
CURLMoption option, ...)
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
CURLMcode res = CURLM_OK;
va_list param;
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
va_start(param, option);
switch(option) {
case CURLMOPT_SOCKETFUNCTION:
multi->socket_cb = va_arg(param, curl_socket_callback);
break;
case CURLMOPT_SOCKETDATA:
multi->socket_userp = va_arg(param, void *);
break;
case CURLMOPT_PIPELINING:
multi->pipelining_enabled = (bool)(0 != va_arg(param, long));
break;
case CURLMOPT_TIMERFUNCTION:
multi->timer_cb = va_arg(param, curl_multi_timer_callback);
break;
case CURLMOPT_TIMERDATA:
multi->timer_userp = va_arg(param, void *);
break;
Daniel Stenberg
committed
case CURLMOPT_MAXCONNECTS:
multi->maxconnects = va_arg(param, long);
break;
Daniel Stenberg
committed
default:
res = CURLM_UNKNOWN_OPTION;
break;
Daniel Stenberg
committed
}
va_end(param);
return res;
}
Daniel Stenberg
committed
/* we define curl_multi_socket() in the public multi.h header */
#undef curl_multi_socket
Daniel Stenberg
committed
Daniel Stenberg
committed
CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s,
int *running_handles)
Daniel Stenberg
committed
{
CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s,
Daniel Stenberg
committed
0, running_handles);
Daniel Stenberg
committed
if(CURLM_OK >= result)
Daniel Stenberg
committed
update_timer((struct Curl_multi *)multi_handle);
return result;
}
CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s,
Daniel Stenberg
committed
{
CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s,
ev_bitmask, running_handles);
Daniel Stenberg
committed
if(CURLM_OK >= result)
update_timer((struct Curl_multi *)multi_handle);
return result;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles)
Daniel Stenberg
committed
{
CURLMcode result = multi_socket((struct Curl_multi *)multi_handle,
Daniel Stenberg
committed
TRUE, CURL_SOCKET_BAD, 0, running_handles);
Daniel Stenberg
committed
if(CURLM_OK >= result)
update_timer((struct Curl_multi *)multi_handle);
return result;
Daniel Stenberg
committed
}
static CURLMcode multi_timeout(struct Curl_multi *multi,
long *timeout_ms)
Daniel Stenberg
committed
{
static struct timeval tv_zero = {0,0};
Daniel Stenberg
committed
if(multi->timetree) {
/* we have a tree of expire times */
struct timeval now = Curl_tvnow();
/* splay the lowest to the bottom */
multi->timetree = Curl_splay(tv_zero, multi->timetree);
Daniel Stenberg
committed
Daniel Stenberg
committed
if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) {
/* some time left before expiration */
*timeout_ms = curlx_tvdiff(multi->timetree->key, now);
Daniel Stenberg
committed
if(!*timeout_ms)
/*
* Since we only provide millisecond resolution on the returned value
* and the diff might be less than one millisecond here, we don't
* return zero as that may cause short bursts of busyloops on fast
* processors while the diff is still present but less than one
* millisecond! instead we return 1 until the time is ripe.
*/
*timeout_ms=1;
}
Daniel Stenberg
committed
/* 0 means immediately */
*timeout_ms = 0;
}
else
*timeout_ms = -1;
return CURLM_OK;
}
CURLMcode curl_multi_timeout(CURLM *multi_handle,
long *timeout_ms)
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
/* First, make some basic checks that the CURLM handle is a good handle */
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
return multi_timeout(multi, timeout_ms);
}
/*
* Tell the application it should update its timers, if it subscribes to the
* update timer callback.
*/
static int update_timer(struct Curl_multi *multi)
{
long timeout_ms;
Daniel Stenberg
committed
if(!multi->timer_cb)
return 0;
Daniel Stenberg
committed
if( multi_timeout(multi, &timeout_ms) != CURLM_OK )
return -1;
Daniel Stenberg
committed
if( timeout_ms < 0 )
return 0;
/* When multi_timeout() is done, multi->timetree points to the node with the
* timeout we got the (relative) time-out time for. We can thus easily check
* if this is the same (fixed) time as we got in a previous call and then
* avoid calling the callback again. */
if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0)
return 0;
multi->timer_lastcall = multi->timetree->key;
return multi->timer_cb((CURLM*)multi, timeout_ms, multi->timer_userp);
}
Daniel Stenberg
committed
static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle,
struct connectdata *conn)
{
size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size;
struct curl_llist_element *sendhead = conn->send_pipe->head;
Daniel Stenberg
committed
struct curl_llist *pipeline;
CURLcode rc;
Daniel Stenberg
committed
if(!Curl_isPipeliningEnabled(handle) ||
pipeLen == 0)
pipeline = conn->send_pipe;
else {
if(conn->server_supports_pipelining &&
pipeLen < MAX_PIPELINE_LENGTH)
pipeline = conn->send_pipe;
else
pipeline = conn->pend_pipe;
}
rc = Curl_addHandleToPipeline(handle, pipeline);
if (pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) {
/* this is a new one as head, expire it */
conn->writechannel_inuse = FALSE; /* not in use yet */
infof(conn->data, "%p is at send pipe head!\n",
conn->send_pipe->head->ptr);
Curl_expire(conn->send_pipe->head->ptr, 1);
}
return rc;
Daniel Stenberg
committed
}
static int checkPendPipeline(struct connectdata *conn)
{
int result = 0;
Daniel Stenberg
committed
struct curl_llist_element *sendhead = conn->send_pipe->head;
Daniel Stenberg
committed
size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size;
if (conn->server_supports_pipelining || pipeLen == 0) {
Daniel Stenberg
committed
struct curl_llist_element *curr = conn->pend_pipe->head;
const size_t maxPipeLen =
conn->server_supports_pipelining ? MAX_PIPELINE_LENGTH : 1;
Daniel Stenberg
committed
while(pipeLen < maxPipeLen && curr) {
Daniel Stenberg
committed
Curl_llist_move(conn->pend_pipe, curr,
conn->send_pipe, conn->send_pipe->tail);
Daniel Stenberg
committed
Curl_pgrsTime(curr->ptr, TIMER_PRETRANSFER);
Daniel Stenberg
committed
++result; /* count how many handles we moved */
curr = conn->pend_pipe->head;
++pipeLen;
}
}
if (result) {
conn->now = Curl_tvnow();
Daniel Stenberg
committed
/* something moved, check for a new send pipeline leader */
if(sendhead != conn->send_pipe->head) {
/* this is a new one as head, expire it */
conn->writechannel_inuse = FALSE; /* not in use yet */
infof(conn->data, "%p is at send pipe head!\n",
conn->send_pipe->head->ptr);
Curl_expire(conn->send_pipe->head->ptr, 1);
}
}
Daniel Stenberg
committed
return result;
}
Daniel Stenberg
committed
/* Move this transfer from the sending list to the receiving list.
Pay special attention to the new sending list "leader" as it needs to get
checked to update what sockets it acts on.
Daniel Stenberg
committed
static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle,
Daniel Stenberg
committed
{
struct curl_llist_element *curr;
curr = conn->send_pipe->head;
while(curr) {
if(curr->ptr == handle) {
Curl_llist_move(conn->send_pipe, curr,
conn->recv_pipe, conn->recv_pipe->tail);
Daniel Stenberg
committed
if(conn->send_pipe->head) {
/* Since there's a new easy handle at the start of the send pipeline,
set its timeout value to 1ms to make it trigger instantly */
conn->writechannel_inuse = FALSE; /* not used now */
infof(conn->data, "%p is at send pipe head B!\n",
conn->send_pipe->head->ptr);
Curl_expire(conn->send_pipe->head->ptr, 1);
}
/* The receiver's list is not really interesting here since either this
handle is now first in the list and we'll deal with it soon, or
another handle is already first and thus is already taken care of */
break; /* we're done! */
Daniel Stenberg
committed
}
curr = curr->next;
}
}
static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle,
struct connectdata *conn)
{
struct curl_llist_element *curr;
curr = conn->recv_pipe->head;
while(curr) {
if(curr->ptr == handle) {
Curl_llist_move(conn->recv_pipe, curr,
conn->done_pipe, conn->done_pipe->tail);
break;
}
curr = curr->next;
}
}
Daniel Stenberg
committed
static bool isHandleAtHead(struct SessionHandle *handle,
struct curl_llist *pipeline)
{
struct curl_llist_element *curr = pipeline->head;
if(curr)
return (bool)(curr->ptr == handle);
return FALSE;
}
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
/*
* multi_freetimeout()
*
* Callback used by the llist system when a single timeout list entry is
* destroyed.
*/
static void multi_freetimeout(void *user, void *entryptr)
{
(void)user;
/* the entry was plain malloc()'ed */
free(entryptr);
}
/*
* multi_addtimeout()
*
* Add a timestamp to the list of timeouts. Keep the list sorted so that head
* of list is always the timeout nearest in time.
*
*/
static CURLMcode
multi_addtimeout(struct curl_llist *timeoutlist,
struct timeval *stamp)
{
struct curl_llist_element *e;
struct timeval *timedup;
struct curl_llist_element *prev = NULL;
timedup = malloc(sizeof(*timedup));
if(!timedup)
return CURLM_OUT_OF_MEMORY;
/* copy the timestamp */
memcpy(timedup, stamp, sizeof(*timedup));
if(Curl_llist_count(timeoutlist)) {
/* find the correct spot in the list */
for(e = timeoutlist->head; e; e = e->next) {
struct timeval *checktime = e->ptr;
long diff = curlx_tvdiff(*checktime, *timedup);
if(diff > 0)
break;
prev = e;
}
}
/* else
this is the first timeout on the list */
if(!Curl_llist_insert_next(timeoutlist, prev, timedup)) {
free(timedup);
Daniel Stenberg
committed
/*
* Curl_expire()
*
* given a number of milliseconds from now to use to set the 'act before
* this'-time for the transfer, to be extracted by curl_multi_timeout()
*
* Note that the timeout will be added to a queue of timeouts if it defines a
* moment in time that is later than the current head of queue.
*
* Pass zero to clear all timeout values for this handle.
Daniel Stenberg
committed
*/
Daniel Stenberg
committed
void Curl_expire(struct SessionHandle *data, long milli)
{
struct Curl_multi *multi = data->multi;
struct timeval *nowp = &data->state.expiretime;
Daniel Stenberg
committed
/* this is only interesting for multi-interface using libcurl, and only
while there is still a multi interface struct remaining! */
if(!multi)
return;
if(!milli) {
/* No timeout, clear the time data. */
if(nowp->tv_sec || nowp->tv_usec) {
Daniel Stenberg
committed
/* Since this is an cleared time, we must remove the previous entry from
the splay tree */
struct curl_llist *list = data->state.timeoutlist;
rc = Curl_splayremovebyaddr(multi->timetree,
&data->state.timenode,
&multi->timetree);
if(rc)
infof(data, "Internal error clearing splay node = %d\n", rc);
/* flush the timeout list too */
while(list->size > 0)
Curl_llist_remove(list, list->tail, NULL);
Daniel Stenberg
committed
infof(data, "Expire cleared\n");
Daniel Stenberg
committed
}
}
else {
struct timeval set;
int rest;
set = Curl_tvnow();
set.tv_sec += milli/1000;
set.tv_usec += (milli%1000)*1000;
rest = (int)(set.tv_usec - 1000000);
if(rest > 0) {
/* bigger than a full microsec */
set.tv_sec++;
set.tv_usec -= 1000000;
}
if(nowp->tv_sec || nowp->tv_usec) {
/* This means that the struct is added as a node in the splay tree.
Compare if the new time is earlier, and only remove-old/add-new if it
is. */
Daniel Stenberg
committed
long diff = curlx_tvdiff(set, *nowp);
if(diff > 0) {
/* the new expire time was later so just add it to the queue
and get out */
multi_addtimeout(data->state.timeoutlist, &set);
Daniel Stenberg
committed
return;
}
/* the new time is newer than the presently set one, so add the current
to the queue and update the head */
multi_addtimeout(data->state.timeoutlist, nowp);
Daniel Stenberg
committed
/* Since this is an updated time, we must remove the previous entry from
the splay tree first and then re-add the new value */
rc = Curl_splayremovebyaddr(multi->timetree,
&data->state.timenode,
&multi->timetree);
if(rc)
infof(data, "Internal error removing splay node = %d\n", rc);
Daniel Stenberg
committed
}
*nowp = set;
Daniel Stenberg
committed
infof(data, "Expire at %ld / %ld (%ldms) %p\n",
(long)nowp->tv_sec, (long)nowp->tv_usec, milli, data);
#endif
Daniel Stenberg
committed
data->state.timenode.payload = data;
multi->timetree = Curl_splayinsert(*nowp,
Daniel Stenberg
committed
multi->timetree,
&data->state.timenode);
}
#if 0
Curl_splayprint(multi->timetree, 0, TRUE);
#endif
}
Daniel Stenberg
committed
CURLMcode curl_multi_assign(CURLM *multi_handle,
curl_socket_t s, void *hashp)
{
struct Curl_sh_entry *there = NULL;
struct Curl_multi *multi = (struct Curl_multi *)multi_handle;
if(s != CURL_SOCKET_BAD)
there = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(curl_socket_t));
if(!there)
return CURLM_BAD_SOCKET;
there->socketp = hashp;
return CURLM_OK;
}
static void multi_connc_remove_handle(struct Curl_multi *multi,
struct SessionHandle *data)
{
/* a connection in the connection cache pointing to the given 'data' ? */
int i;
for(i=0; i< multi->connc->num; i++) {
struct connectdata * conn = multi->connc->connects[i];
if(conn && conn->data == data) {
/* If this easy_handle was the last one in charge for one or more
connections in the shared connection cache, we might need to keep
this handle around until either A) the connection is closed and
killed properly, or B) another easy_handle uses the connection.
The reason why we need to have a easy_handle associated with a live
connection is simply that some connections will need a handle to get
closed down properly. Currently, the only connections that need to
keep a easy_handle handle around are using FTP(S). Such connections
have the PROT_CLOSEACTION bit set.
Thus, we need to check for all connections in the shared cache that
points to this handle and are using PROT_CLOSEACTION. If there's any,
we need to add this handle to the list of "easy handles kept around
for nice connection closures".
*/
if(conn->protocol & PROT_CLOSEACTION) {
/* this handle is still being used by a shared connection and
thus we leave it around for now */
if(add_closure(multi, data) == CURLM_OK)
data->state.shared_conn = multi;
else {
/* out of memory - so much for graceful shutdown */
Curl_disconnect(conn);
multi->connc->connects[i] = NULL;
}
}
else
/* disconect the easy handle from the connection since the connection
will now remain but this easy handle is going */
conn->data = NULL;
}
}
}
/* Add the given data pointer to the list of 'closure handles' that are kept
around only to be able to close some connections nicely - just make sure
that this handle isn't already added, like for the cases when an easy
handle is removed, added and removed again... */
static CURLMcode add_closure(struct Curl_multi *multi,
struct SessionHandle *data)
{
struct closure *cl = multi->closure;
struct closure *p = NULL;
bool add = TRUE;
/* Before adding, scan through all the other currently kept handles and see
if there are any connections still referring to them and kill them if
not. */
while(cl) {
bool inuse = FALSE;
for(i=0; i< multi->connc->num; i++) {
if(multi->connc->connects[i] &&
(multi->connc->connects[i]->data == cl->easy_handle)) {
inuse = TRUE;
break;
}
}
n = cl->next;
if(!inuse) {
/* cl->easy_handle is now killable */
/* unmark it as not having a connection around that uses it anymore */
cl->easy_handle->state.shared_conn= NULL;
if(cl->easy_handle->state.closed) {
infof(data, "Delayed kill of easy handle %p\n", cl->easy_handle);
/* close handle only if curl_easy_cleanup() already has been called
for this easy handle */
Curl_close(cl->easy_handle);
}
if(p)
p->next = n;
else
multi->closure = n;
free(cl);
} else {
if(cl->easy_handle == data)
add = FALSE;
p = cl;
cl = n;
}
if (add) {
cl = calloc(1, sizeof(struct closure));
if(!cl)
return CURLM_OUT_OF_MEMORY;
cl->easy_handle = data;
cl->next = multi->closure;
multi->closure = cl;
}
return CURLM_OK;
}
Daniel Stenberg
committed
void Curl_multi_dump(const struct Curl_multi *multi_handle)
Daniel Stenberg
committed
{
struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
struct Curl_one_easy *easy;
int i;
fprintf(stderr, "* Multi status: %d handles, %d alive\n",
multi->num_easy, multi->num_alive);
Daniel Stenberg
committed
for(easy=multi->easy.next; easy != &multi->easy; easy = easy->next) {
if(easy->state < CURLM_STATE_COMPLETED) {
Daniel Stenberg
committed
/* only display handles that are not completed */
fprintf(stderr, "handle %p, state %s, %d sockets\n",
Daniel Stenberg
committed
(void *)easy->easy_handle,
statename[easy->state], easy->numsocks);
Daniel Stenberg
committed
for(i=0; i < easy->numsocks; i++) {
curl_socket_t s = easy->sockets[i];
struct Curl_sh_entry *entry =
Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
fprintf(stderr, "%d ", (int)s);
if(!entry) {
fprintf(stderr, "INTERNAL CONFUSION\n");
continue;
}
fprintf(stderr, "[%s %s] ",
entry->action&CURL_POLL_IN?"RECVING":"",
entry->action&CURL_POLL_OUT?"SENDING":"");
}
if(easy->numsocks)
fprintf(stderr, "\n");
}
}
}
#endif