Newer
Older
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
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle,
struct connectdata *conn)
{
size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size;
struct curl_llist *pipeline;
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;
}
return Curl_addHandleToPipeline(handle, pipeline);
}
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;
}
Daniel Stenberg
committed
/* given a number of milliseconds from now to use to set the 'act before
Daniel Stenberg
committed
this'-time for the transfer, to be extracted by curl_multi_timeout()
Pass zero to clear the timeout value for this handle.
*/
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 */
rc = Curl_splayremovebyaddr(multi->timetree,
&data->state.timenode,
&multi->timetree);
if(rc)
infof(data, "Internal error clearing splay node = %d\n", rc);
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 we don't change this */
return;
/* 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) {
Daniel Stenberg
committed
if(easy->state != CURLM_STATE_COMPLETED) {
/* 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