Newer
Older
struct Curl_sh_entry *entry;
curl_socket_t s;
int num;
Daniel Stenberg
committed
unsigned int curraction;
Daniel Stenberg
committed
struct Curl_one_easy *easy_by_hash;
bool remove_sock_from_hash;
Daniel Stenberg
committed
for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++)
Daniel Stenberg
committed
socks[i] = CURL_SOCKET_BAD;
Daniel Stenberg
committed
/* Fill in the 'current' struct with the state as it is now: what sockets to
supervise and for what actions */
Daniel Stenberg
committed
curraction = multi_getsock(easy, socks, MAX_SOCKSPEREASYHANDLE);
Daniel Stenberg
committed
/* We have 0 .. N sockets already and we get to know about the 0 .. M
sockets we should have from now on. Detect the differences, remove no
longer supervised ones and add new ones */
Daniel Stenberg
committed
/* walk over the sockets we got right now */
for(i=0; (i< MAX_SOCKSPEREASYHANDLE) &&
Daniel Stenberg
committed
(curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i)));
i++) {
int action = CURL_POLL_NONE;
Daniel Stenberg
committed
Daniel Stenberg
committed
s = socks[i];
Daniel Stenberg
committed
/* get it from the hash */
entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s));
Daniel Stenberg
committed
Daniel Stenberg
committed
if(curraction & GETSOCK_READSOCK(i))
action |= CURL_POLL_IN;
Daniel Stenberg
committed
if(curraction & GETSOCK_WRITESOCK(i))
action |= CURL_POLL_OUT;
Daniel Stenberg
committed
if(entry) {
/* yeps, already present so check if it has the same action set */
if(entry->action == action)
/* same, continue */
continue;
}
else {
/* this is a socket we didn't have before, add it! */
entry = sh_addentry(multi->sockhash, s, easy->easy_handle);
if(!entry)
/* fatal */
return;
}
Daniel Stenberg
committed
/* we know (entry != NULL) at this point, see the logic above */
if(multi->socket_cb)
multi->socket_cb(easy->easy_handle,
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. */
struct connectdata *easy_conn;
easy_by_hash = entry->easy->multi_pos;
easy_conn = easy_by_hash->easy_conn;
if(easy_conn) {
if(easy_conn->recv_pipe && easy_conn->recv_pipe->size > 1) {
Daniel Stenberg
committed
/* the handle should not be removed from the pipe yet */
remove_sock_from_hash = FALSE;
/* Update the sockhash entry to instead point to the next in line
for the recv_pipe, or the first (in case this particular easy
isn't already) */
if(entry->easy == easy->easy_handle) {
if(isHandleAtHead(easy->easy_handle, easy_conn->recv_pipe))
Daniel Stenberg
committed
entry->easy = easy_conn->recv_pipe->head->next->ptr;
else
entry->easy = easy_conn->recv_pipe->head->ptr;
}
}
if(easy_conn->send_pipe && easy_conn->send_pipe->size > 1) {
Daniel Stenberg
committed
/* the handle should not be removed from the pipe yet */
remove_sock_from_hash = FALSE;
/* Update the sockhash entry to instead point to the next in line
for the send_pipe, or the first (in case this particular easy
isn't already) */
if(entry->easy == easy->easy_handle) {
if(isHandleAtHead(easy->easy_handle, easy_conn->send_pipe))
Daniel Stenberg
committed
entry->easy = easy_conn->send_pipe->head->next->ptr;
else
entry->easy = easy_conn->send_pipe->head->ptr;
}
}
/* Don't worry about overwriting recv_pipe head with send_pipe_head,
when action will be asked on the socket (see multi_socket()), the
head of the correct pipe will be taken according to the
action. */
}
}
else
/* just a precaution, this socket really SHOULD be in the hash already
but in case it isn't, we don't have to tell the app to remove it
either since it never got to know about it */
Daniel Stenberg
committed
remove_sock_from_hash = FALSE;
if(remove_sock_from_hash) {
/* in this case 'entry' is always non-NULL */
if(multi->socket_cb)
multi->socket_cb(easy->easy_handle,
s,
CURL_POLL_REMOVE,
multi->socket_userp,
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
}
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
/*
* add_next_timeout()
*
* Each SessionHandle has a list of timeouts. The add_next_timeout() is called
* when it has just been removed from the splay tree because the timeout has
* expired. This function is then to advance in the list to pick the next
* timeout to use (skip the already expired ones) and add this node back to
* the splay tree again.
*
* The splay tree only has each sessionhandle as a single node and the nearest
* timeout is used to sort it on.
*/
static CURLMcode add_next_timeout(struct timeval now,
struct Curl_multi *multi,
struct SessionHandle *d)
{
struct timeval *tv = &d->state.expiretime;
struct curl_llist *list = d->state.timeoutlist;
struct curl_llist_element *e;
/* move over the timeout list for this specific handle and remove all
timeouts that are now passed tense and store the next pending
timeout in *tv */
for(e = list->head; e; ) {
struct curl_llist_element *n = e->next;
long diff = curlx_tvdiff(*(struct timeval *)e->ptr, now);
if(diff <= 0)
/* remove outdated entry */
Curl_llist_remove(list, e, NULL);
else
/* the list is sorted so get out on the first mismatch */
break;
e = n;
}
e = list->head;
if(!e) {
/* clear the expire times within the handles that we remove from the
splay tree */
tv->tv_sec = 0;
tv->tv_usec = 0;
}
else {
/* copy the first entry to 'tv' */
memcpy(tv, e->ptr, sizeof(*tv));
/* remove first entry from list */
Curl_llist_remove(list, e, NULL);
/* insert this node again into the splay */
multi->timetree = Curl_splayinsert(*tv, multi->timetree,
&d->state.timenode);
}
return CURLM_OK;
}
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;
struct timeval now = Curl_tvnow();
Daniel Stenberg
committed
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)
data = data->set.one_easy->easy_conn->recv_pipe->head->ptr;
Daniel Stenberg
committed
}
if(data->set.one_easy->easy_conn &&
!(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK))
/* set socket event bitmask if they're not locked */
data->set.one_easy->easy_conn->cselect_bits = ev_bitmask;
Daniel Stenberg
committed
Daniel Stenberg
committed
do
result = multi_runsingle(multi, now, data->set.one_easy);
while(CURLM_CALL_MULTI_PERFORM == result);
Daniel Stenberg
committed
if(data->set.one_easy->easy_conn &&
!(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK))
/* clear the bitmask only if not locked */
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
}
now.tv_usec += 40000; /* compensate for bad precision timers that might've
triggered too early */
if(now.tv_usec >= 1000000) {
now.tv_sec++;
now.tv_usec -= 1000000;
}
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 {
/* the first loop lap 'data' can be NULL */
if(data) {
Daniel Stenberg
committed
do
result = multi_runsingle(multi, now, 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 */
multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
if(t) {
data = t->payload; /* assign this for next loop */
(void)add_next_timeout(now, multi, t->payload);
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 = (0 != va_arg(param, long)) ? TRUE : FALSE;
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;
if(multi_timeout(multi, &timeout_ms)) {
return -1;
if(timeout_ms < 0) {
static const struct timeval none={0,0};
if(Curl_splaycomparekeys(none, multi->timer_lastcall)) {
multi->timer_lastcall = none;
/* there's no timeout now but there was one previously, tell the app to
disable it */
return multi->timer_cb((CURLM*)multi, -1, multi->timer_userp);
}
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;
}
}
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 */
Daniel Stenberg
committed
infof(conn->data, "%p is at send pipe head!\n",
conn->send_pipe->head->ptr);
Daniel Stenberg
committed
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 */
Daniel Stenberg
committed
infof(conn->data, "%p is at send pipe head B!\n",
conn->send_pipe->head->ptr);
Daniel Stenberg
committed
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 (curr->ptr == handle) ? TRUE : FALSE;
Daniel Stenberg
committed
return FALSE;
}
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
/*
* 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;
set = Curl_tvnow();
set.tv_sec += milli/1000;
set.tv_usec += (milli%1000)*1000;
if(set.tv_usec >= 1000000) {
Daniel Stenberg
committed
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;
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->handler->flags & PROTOPT_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, /* dead_connection */ FALSE);
multi->connc->connects[i] = NULL;
/* disconect the easy handle from the connection since the connection
will now remain but this easy handle is going */
}
/* 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);
if(cl->easy_handle == data)
add = FALSE;
p = cl;
cl = n;
}
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