| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://curl.haxx.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ***************************************************************************/ |
| |
| #include "setup.h" |
| |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #include <curl/curl.h> |
| |
| #include "urldata.h" |
| #include "transfer.h" |
| #include "url.h" |
| #include "connect.h" |
| #include "progress.h" |
| #include "easyif.h" |
| #include "multiif.h" |
| #include "sendf.h" |
| #include "timeval.h" |
| #include "http.h" |
| #include "select.h" |
| #include "warnless.h" |
| |
| #define _MPRINTF_REPLACE /* use our functions only */ |
| #include <curl/mprintf.h> |
| |
| #include "curl_memory.h" |
| /* The last #include file should be: */ |
| #include "memdebug.h" |
| |
| /* |
| CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 |
| to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every |
| CURL handle takes 45-50 K memory, therefore this 3K are not significant. |
| */ |
| #ifndef CURL_SOCKET_HASH_TABLE_SIZE |
| #define CURL_SOCKET_HASH_TABLE_SIZE 911 |
| #endif |
| |
| struct Curl_message { |
| /* the 'CURLMsg' is the part that is visible to the external user */ |
| struct CURLMsg extmsg; |
| }; |
| |
| /* NOTE: if you add a state here, add the name to the statename[] array as |
| well! |
| */ |
| typedef enum { |
| CURLM_STATE_INIT, /* 0 - start in this state */ |
| CURLM_STATE_CONNECT, /* 1 - resolve/connect has been sent off */ |
| CURLM_STATE_WAITRESOLVE, /* 2 - awaiting the resolve to finalize */ |
| CURLM_STATE_WAITCONNECT, /* 3 - awaiting the connect to finalize */ |
| CURLM_STATE_WAITPROXYCONNECT, /* 4 - awaiting proxy CONNECT to finalize */ |
| CURLM_STATE_PROTOCONNECT, /* 5 - completing the protocol-specific connect |
| phase */ |
| CURLM_STATE_WAITDO, /* 6 - wait for our turn to send the request */ |
| CURLM_STATE_DO, /* 7 - start send off the request (part 1) */ |
| CURLM_STATE_DOING, /* 8 - sending off the request (part 1) */ |
| CURLM_STATE_DO_MORE, /* 9 - send off the request (part 2) */ |
| CURLM_STATE_DO_DONE, /* 10 - done sending off request */ |
| CURLM_STATE_WAITPERFORM, /* 11 - wait for our turn to read the response */ |
| CURLM_STATE_PERFORM, /* 12 - transfer data */ |
| CURLM_STATE_TOOFAST, /* 13 - wait because limit-rate exceeded */ |
| CURLM_STATE_DONE, /* 14 - post data transfer operation */ |
| CURLM_STATE_COMPLETED, /* 15 - operation complete */ |
| CURLM_STATE_MSGSENT, /* 16 - the operation complete message is sent */ |
| CURLM_STATE_LAST /* 17 - not a true state, never use this */ |
| } CURLMstate; |
| |
| /* we support N sockets per easy handle. Set the corresponding bit to what |
| action we should wait for */ |
| #define MAX_SOCKSPEREASYHANDLE 5 |
| #define GETSOCK_READABLE (0x00ff) |
| #define GETSOCK_WRITABLE (0xff00) |
| |
| struct closure { |
| struct closure *next; /* a simple one-way list of structs */ |
| struct SessionHandle *easy_handle; |
| }; |
| |
| struct Curl_one_easy { |
| /* first, two fields for the linked list of these */ |
| struct Curl_one_easy *next; |
| struct Curl_one_easy *prev; |
| |
| struct SessionHandle *easy_handle; /* the easy handle for this unit */ |
| struct connectdata *easy_conn; /* the "unit's" connection */ |
| |
| CURLMstate state; /* the handle's state */ |
| CURLcode result; /* previous result */ |
| |
| struct Curl_message msg; /* A single posted message. */ |
| |
| /* Array with the plain socket numbers this handle takes care of, in no |
| particular order. Note that all sockets are added to the sockhash, where |
| the state etc are also kept. This array is mostly used to detect when a |
| socket is to be removed from the hash. See singlesocket(). */ |
| curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; |
| int numsocks; |
| }; |
| |
| #define CURL_MULTI_HANDLE 0x000bab1e |
| |
| #define GOOD_MULTI_HANDLE(x) \ |
| ((x) && (((struct Curl_multi *)(x))->type == CURL_MULTI_HANDLE)) |
| #define GOOD_EASY_HANDLE(x) \ |
| ((x) && (((struct SessionHandle *)(x))->magic == CURLEASY_MAGIC_NUMBER)) |
| |
| /* This is the struct known as CURLM on the outside */ |
| struct Curl_multi { |
| /* First a simple identifier to easier detect if a user mix up |
| this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ |
| long type; |
| |
| /* We have a doubly-linked circular list with easy handles */ |
| struct Curl_one_easy easy; |
| |
| int num_easy; /* amount of entries in the linked list above. */ |
| int num_alive; /* amount of easy handles that are added but have not yet |
| reached COMPLETE state */ |
| |
| struct curl_llist *msglist; /* a list of messages from completed transfers */ |
| |
| /* callback function and user data pointer for the *socket() API */ |
| curl_socket_callback socket_cb; |
| void *socket_userp; |
| |
| /* Hostname cache */ |
| struct curl_hash *hostcache; |
| |
| /* timetree points to the splay-tree of time nodes to figure out expire |
| times of all currently set timers */ |
| struct Curl_tree *timetree; |
| |
| /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note |
| the pluralis form, there can be more than one easy handle waiting on the |
| same actual socket) */ |
| struct curl_hash *sockhash; |
| |
| /* Whether pipelining is enabled for this multi handle */ |
| bool pipelining_enabled; |
| |
| /* shared connection cache */ |
| struct conncache *connc; |
| long maxconnects; /* if >0, a fixed limit of the maximum number of entries |
| we're allowed to grow the connection cache to */ |
| |
| /* list of easy handles kept around for doing nice connection closures */ |
| struct closure *closure; |
| |
| /* timer callback and user data pointer for the *socket() API */ |
| curl_multi_timer_callback timer_cb; |
| void *timer_userp; |
| struct timeval timer_lastcall; /* the fixed time for the timeout for the |
| previous callback */ |
| }; |
| |
| static void multi_connc_remove_handle(struct Curl_multi *multi, |
| struct SessionHandle *data); |
| static void singlesocket(struct Curl_multi *multi, |
| struct Curl_one_easy *easy); |
| static CURLMcode add_closure(struct Curl_multi *multi, |
| struct SessionHandle *data); |
| static int update_timer(struct Curl_multi *multi); |
| |
| static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, |
| struct connectdata *conn); |
| static int checkPendPipeline(struct connectdata *conn); |
| static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle, |
| struct connectdata *conn); |
| static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle, |
| struct connectdata *conn); |
| static bool isHandleAtHead(struct SessionHandle *handle, |
| struct curl_llist *pipeline); |
| static CURLMcode add_next_timeout(struct timeval now, |
| struct Curl_multi *multi, |
| struct SessionHandle *d); |
| |
| #ifdef DEBUGBUILD |
| static const char * const statename[]={ |
| "INIT", |
| "CONNECT", |
| "WAITRESOLVE", |
| "WAITCONNECT", |
| "WAITPROXYCONNECT", |
| "PROTOCONNECT", |
| "WAITDO", |
| "DO", |
| "DOING", |
| "DO_MORE", |
| "DO_DONE", |
| "WAITPERFORM", |
| "PERFORM", |
| "TOOFAST", |
| "DONE", |
| "COMPLETED", |
| "MSGSENT", |
| }; |
| #endif |
| |
| static void multi_freetimeout(void *a, void *b); |
| |
| /* always use this function to change state, to make debugging easier */ |
| static void multistate(struct Curl_one_easy *easy, CURLMstate state) |
| { |
| #ifdef DEBUGBUILD |
| long connectindex = -5000; |
| #endif |
| CURLMstate oldstate = easy->state; |
| |
| if(oldstate == state) |
| /* don't bother when the new state is the same as the old state */ |
| return; |
| |
| easy->state = state; |
| |
| #ifdef DEBUGBUILD |
| if(easy->easy_conn) { |
| if(easy->state > CURLM_STATE_CONNECT && |
| easy->state < CURLM_STATE_COMPLETED) |
| connectindex = easy->easy_conn->connectindex; |
| |
| infof(easy->easy_handle, |
| "STATE: %s => %s handle %p; (connection #%ld) \n", |
| statename[oldstate], statename[easy->state], |
| (char *)easy, connectindex); |
| } |
| #endif |
| if(state == CURLM_STATE_COMPLETED) |
| /* changing to COMPLETED means there's one less easy handle 'alive' */ |
| easy->easy_handle->multi->num_alive--; |
| } |
| |
| /* |
| * We add one of these structs to the sockhash for a particular socket |
| */ |
| |
| struct Curl_sh_entry { |
| struct SessionHandle *easy; |
| time_t timestamp; |
| int action; /* what action READ/WRITE this socket waits for */ |
| curl_socket_t socket; /* mainly to ease debugging */ |
| void *socketp; /* settable by users with curl_multi_assign() */ |
| }; |
| /* bits for 'action' having no bits means this socket is not expecting any |
| action */ |
| #define SH_READ 1 |
| #define SH_WRITE 2 |
| |
| /* make sure this socket is present in the hash for this handle */ |
| static struct Curl_sh_entry *sh_addentry(struct curl_hash *sh, |
| curl_socket_t s, |
| struct SessionHandle *data) |
| { |
| struct Curl_sh_entry *there = |
| Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); |
| struct Curl_sh_entry *check; |
| |
| if(there) |
| /* it is present, return fine */ |
| return there; |
| |
| /* not present, add it */ |
| check = calloc(1, sizeof(struct Curl_sh_entry)); |
| if(!check) |
| return NULL; /* major failure */ |
| check->easy = data; |
| check->socket = s; |
| |
| /* make/add new hash entry */ |
| if(NULL == Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { |
| free(check); |
| return NULL; /* major failure */ |
| } |
| |
| return check; /* things are good in sockhash land */ |
| } |
| |
| |
| /* delete the given socket + handle from the hash */ |
| static void sh_delentry(struct curl_hash *sh, curl_socket_t s) |
| { |
| struct Curl_sh_entry *there = |
| Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); |
| |
| if(there) { |
| /* this socket is in the hash */ |
| /* We remove the hash entry. (This'll end up in a call to |
| sh_freeentry().) */ |
| Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); |
| } |
| } |
| |
| /* |
| * free a sockhash entry |
| */ |
| static void sh_freeentry(void *freethis) |
| { |
| struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; |
| |
| if(p) |
| free(p); |
| } |
| |
| static size_t fd_key_compare(void*k1, size_t k1_len, void*k2, size_t k2_len) |
| { |
| (void) k1_len; (void) k2_len; |
| |
| return (*((int* ) k1)) == (*((int* ) k2)); |
| } |
| |
| static size_t hash_fd(void* key, size_t key_length, size_t slots_num) |
| { |
| int fd = * ((int* ) key); |
| (void) key_length; |
| |
| return (fd % (int)slots_num); |
| } |
| |
| /* |
| * sh_init() creates a new socket hash and returns the handle for it. |
| * |
| * Quote from README.multi_socket: |
| * |
| * "Some tests at 7000 and 9000 connections showed that the socket hash lookup |
| * is somewhat of a bottle neck. Its current implementation may be a bit too |
| * limiting. It simply has a fixed-size array, and on each entry in the array |
| * it has a linked list with entries. So the hash only checks which list to |
| * scan through. The code I had used so for used a list with merely 7 slots |
| * (as that is what the DNS hash uses) but with 7000 connections that would |
| * make an average of 1000 nodes in each list to run through. I upped that to |
| * 97 slots (I believe a prime is suitable) and noticed a significant speed |
| * increase. I need to reconsider the hash implementation or use a rather |
| * large default value like this. At 9000 connections I was still below 10us |
| * per call." |
| * |
| */ |
| static struct curl_hash *sh_init(void) |
| { |
| return Curl_hash_alloc(CURL_SOCKET_HASH_TABLE_SIZE, hash_fd, fd_key_compare, |
| sh_freeentry); |
| } |
| |
| /* |
| * multi_addmsg() |
| * |
| * Called when a transfer is completed. Adds the given msg pointer to |
| * the list kept in the multi handle. |
| */ |
| static CURLMcode multi_addmsg(struct Curl_multi *multi, |
| struct Curl_message *msg) |
| { |
| if(!Curl_llist_insert_next(multi->msglist, multi->msglist->tail, msg)) |
| return CURLM_OUT_OF_MEMORY; |
| |
| return CURLM_OK; |
| } |
| |
| /* |
| * multi_freeamsg() |
| * |
| * Callback used by the llist system when a single list entry is destroyed. |
| */ |
| static void multi_freeamsg(void *a, void *b) |
| { |
| (void)a; |
| (void)b; |
| } |
| |
| |
| CURLM *curl_multi_init(void) |
| { |
| struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); |
| |
| if(!multi) |
| return NULL; |
| |
| multi->type = CURL_MULTI_HANDLE; |
| |
| multi->hostcache = Curl_mk_dnscache(); |
| if(!multi->hostcache) |
| goto error; |
| |
| multi->sockhash = sh_init(); |
| if(!multi->sockhash) |
| goto error; |
| |
| multi->connc = Curl_mk_connc(CONNCACHE_MULTI, -1L); |
| if(!multi->connc) |
| goto error; |
| |
| multi->msglist = Curl_llist_alloc(multi_freeamsg); |
| if(!multi->msglist) |
| goto error; |
| |
| /* Let's make the doubly-linked list a circular list. This makes |
| the linked list code simpler and allows inserting at the end |
| with less work (we didn't keep a tail pointer before). */ |
| multi->easy.next = &multi->easy; |
| multi->easy.prev = &multi->easy; |
| |
| return (CURLM *) multi; |
| |
| error: |
| |
| Curl_hash_destroy(multi->sockhash); |
| multi->sockhash = NULL; |
| Curl_hash_destroy(multi->hostcache); |
| multi->hostcache = NULL; |
| Curl_rm_connc(multi->connc); |
| multi->connc = NULL; |
| |
| free(multi); |
| return NULL; |
| } |
| |
| CURLMcode curl_multi_add_handle(CURLM *multi_handle, |
| CURL *easy_handle) |
| { |
| struct curl_llist *timeoutlist; |
| struct Curl_one_easy *easy; |
| struct closure *cl; |
| struct closure *prev = NULL; |
| struct Curl_multi *multi = (struct Curl_multi *)multi_handle; |
| struct SessionHandle *data = (struct SessionHandle *)easy_handle; |
| |
| /* First, make some basic checks that the CURLM handle is a good handle */ |
| if(!GOOD_MULTI_HANDLE(multi)) |
| return CURLM_BAD_HANDLE; |
| |
| /* Verify that we got a somewhat good easy handle too */ |
| if(!GOOD_EASY_HANDLE(easy_handle)) |
| return CURLM_BAD_EASY_HANDLE; |
| |
| /* Prevent users from adding same easy handle more than |
| once and prevent adding to more than one multi stack */ |
| if(data->multi) |
| /* possibly we should create a new unique error code for this condition */ |
| return CURLM_BAD_EASY_HANDLE; |
| |
| /* We want the connection cache to have plenty of room. Before we supported |
| the shared cache every single easy handle had 5 entries in their cache |
| by default. */ |
| if(((multi->num_easy + 1) * 4) > multi->connc->num) { |
| long newmax = (multi->num_easy + 1) * 4; |
| |
| if(multi->maxconnects && (newmax > multi->maxconnects)) |
| /* don't grow beyond the allowed size */ |
| newmax = multi->maxconnects; |
| |
| if(newmax > multi->connc->num) { |
| /* we only do this is we can in fact grow the cache */ |
| CURLcode res = Curl_ch_connc(data, multi->connc, newmax); |
| if(res) |
| return CURLM_OUT_OF_MEMORY; |
| } |
| } |
| |
| /* Allocate and initialize timeout list for easy handle */ |
| timeoutlist = Curl_llist_alloc(multi_freetimeout); |
| if(!timeoutlist) |
| return CURLM_OUT_OF_MEMORY; |
| |
| /* Allocate new node for the doubly-linked circular list of |
| Curl_one_easy structs that holds pointers to easy handles */ |
| easy = calloc(1, sizeof(struct Curl_one_easy)); |
| if(!easy) { |
| Curl_llist_destroy(timeoutlist, NULL); |
| return CURLM_OUT_OF_MEMORY; |
| } |
| |
| /* |
| ** No failure allowed in this function beyond this point. And |
| ** no modification of easy nor multi handle allowed before this |
| ** except for potential multi's connection cache growing which |
| ** won't be undone in this function no matter what. |
| */ |
| |
| /* Make easy handle use timeout list initialized above */ |
| data->state.timeoutlist = timeoutlist; |
| timeoutlist = NULL; |
| |
| /* Remove handle from the list of 'closure handles' in case it is there */ |
| cl = multi->closure; |
| while(cl) { |
| struct closure *next = cl->next; |
| if(cl->easy_handle == data) { |
| /* Remove node from list */ |
| free(cl); |
| if(prev) |
| prev->next = next; |
| else |
| multi->closure = next; |
| /* removed from closure list now, this might reuse an existing |
| existing connection but we don't know that at this point */ |
| data->state.shared_conn = NULL; |
| /* No need to continue, handle can only be present once in the list */ |
| break; |
| } |
| prev = cl; |
| cl = next; |
| } |
| |
| /* set the easy handle */ |
| easy->easy_handle = data; |
| multistate(easy, CURLM_STATE_INIT); |
| |
| /* set the back pointer to one_easy to assist in removal */ |
| easy->easy_handle->multi_pos = easy; |
| |
| /* for multi interface connections, we share DNS cache automatically if the |
| easy handle's one is currently private. */ |
| if(easy->easy_handle->dns.hostcache && |
| (easy->easy_handle->dns.hostcachetype == HCACHE_PRIVATE)) { |
| Curl_hash_destroy(easy->easy_handle->dns.hostcache); |
| easy->easy_handle->dns.hostcache = NULL; |
| easy->easy_handle->dns.hostcachetype = HCACHE_NONE; |
| } |
| |
| if(!easy->easy_handle->dns.hostcache || |
| (easy->easy_handle->dns.hostcachetype == HCACHE_NONE)) { |
| easy->easy_handle->dns.hostcache = multi->hostcache; |
| easy->easy_handle->dns.hostcachetype = HCACHE_MULTI; |
| } |
| |
| /* On a multi stack the connection cache, owned by the multi handle, |
| is shared between all easy handles within the multi handle. */ |
| if(easy->easy_handle->state.connc && |
| (easy->easy_handle->state.connc->type == CONNCACHE_PRIVATE)) { |
| /* kill old private connection cache */ |
| Curl_rm_connc(easy->easy_handle->state.connc); |
| easy->easy_handle->state.connc = NULL; |
| } |
| /* Point now to this multi's connection cache */ |
| easy->easy_handle->state.connc = multi->connc; |
| easy->easy_handle->state.connc->type = CONNCACHE_MULTI; |
| |
| /* This adds the new entry at the 'end' of the doubly-linked circular |
| list of Curl_one_easy structs to try and maintain a FIFO queue so |
| the pipelined requests are in order. */ |
| |
| /* We add this new entry last in the list. We make our 'next' point to the |
| 'first' struct and our 'prev' point to the previous 'prev' */ |
| easy->next = &multi->easy; |
| easy->prev = multi->easy.prev; |
| |
| /* make 'easy' the last node in the chain */ |
| multi->easy.prev = easy; |
| |
| /* if there was a prev node, make sure its 'next' pointer links to |
| the new node */ |
| easy->prev->next = easy; |
| |
| /* make the SessionHandle refer back to this multi handle */ |
| Curl_easy_addmulti(easy_handle, multi_handle); |
| |
| /* make the SessionHandle struct refer back to this struct */ |
| easy->easy_handle->set.one_easy = easy; |
| |
| /* Set the timeout for this handle to expire really soon so that it will |
| be taken care of even when this handle is added in the midst of operation |
| when only the curl_multi_socket() API is used. During that flow, only |
| sockets that time-out or have actions will be dealt with. Since this |
| handle has no action yet, we make sure it times out to get things to |
| happen. */ |
| Curl_expire(easy->easy_handle, 1); |
| |
| /* increase the node-counter */ |
| multi->num_easy++; |
| |
| /* increase the alive-counter */ |
| multi->num_alive++; |
| |
| /* A somewhat crude work-around for a little glitch in update_timer() that |
| happens if the lastcall time is set to the same time when the handle is |
| removed as when the next handle is added, as then the check in |
| update_timer() that prevents calling the application multiple times with |
| the same timer infor will not trigger and then the new handle's timeout |
| will not be notified to the app. |
| |
| The work-around is thus simply to clear the 'lastcall' variable to force |
| update_timer() to always trigger a callback to the app when a new easy |
| handle is added */ |
| memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); |
| |
| update_timer(multi); |
| return CURLM_OK; |
| } |
| |
| #if 0 |
| /* Debug-function, used like this: |
| * |
| * Curl_hash_print(multi->sockhash, debug_print_sock_hash); |
| * |
| * Enable the hash print function first by editing hash.c |
| */ |
| static void debug_print_sock_hash(void *p) |
| { |
| struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; |
| |
| fprintf(stderr, " [easy %p/magic %x/socket %d]", |
| (void *)sh->easy, sh->easy->magic, (int)sh->socket); |
| } |
| #endif |
| |
| CURLMcode curl_multi_remove_handle(CURLM *multi_handle, |
| CURL *curl_handle) |
| { |
| struct Curl_multi *multi=(struct Curl_multi *)multi_handle; |
| struct Curl_one_easy *easy; |
| struct SessionHandle *data = curl_handle; |
| |
| /* First, make some basic checks that the CURLM handle is a good handle */ |
| if(!GOOD_MULTI_HANDLE(multi)) |
| return CURLM_BAD_HANDLE; |
| |
| /* Verify that we got a somewhat good easy handle too */ |
| if(!GOOD_EASY_HANDLE(curl_handle)) |
| return CURLM_BAD_EASY_HANDLE; |
| |
| /* pick-up from the 'curl_handle' the kept position in the list */ |
| easy = data->multi_pos; |
| |
| if(easy) { |
| bool premature = (easy->state < CURLM_STATE_COMPLETED) ? TRUE : FALSE; |
| bool easy_owns_conn = (easy->easy_conn && |
| (easy->easy_conn->data == easy->easy_handle)) ? |
| TRUE : FALSE; |
| |
| /* If the 'state' is not INIT or COMPLETED, we might need to do something |
| nice to put the easy_handle in a good known state when this returns. */ |
| if(premature) |
| /* this handle is "alive" so we need to count down the total number of |
| alive connections when this is removed */ |
| multi->num_alive--; |
| |
| if(easy->easy_conn && |
| (easy->easy_conn->send_pipe->size + |
| easy->easy_conn->recv_pipe->size > 1) && |
| easy->state > CURLM_STATE_WAITDO && |
| easy->state < CURLM_STATE_COMPLETED) { |
| /* If the handle is in a pipeline and has started sending off its |
| request but not received its response yet, we need to close |
| connection. */ |
| easy->easy_conn->bits.close = TRUE; |
| /* Set connection owner so that Curl_done() closes it. |
| We can sefely do this here since connection is killed. */ |
| easy->easy_conn->data = easy->easy_handle; |
| } |
| |
| /* The timer must be shut down before easy->multi is set to NULL, |
| else the timenode will remain in the splay tree after |
| curl_easy_cleanup is called. */ |
| Curl_expire(easy->easy_handle, 0); |
| |
| /* destroy the timeout list that is held in the easy handle */ |
| if(data->state.timeoutlist) { |
| Curl_llist_destroy(data->state.timeoutlist, NULL); |
| data->state.timeoutlist = NULL; |
| } |
| |
| 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; |
| } |
| |
| if(easy->easy_conn) { |
| |
| /* we must call Curl_done() here (if we still "own it") so that we don't |
| leave a half-baked one around */ |
| if(easy_owns_conn) { |
| |
| /* Curl_done() clears the conn->data field to lose the association |
| between the easy handle and the connection |
| |
| Note that this ignores the return code simply because there's |
| nothing really useful to do with it anyway! */ |
| (void)Curl_done(&easy->easy_conn, easy->result, premature); |
| |
| if(easy->easy_conn) |
| /* the connection is still alive, set back the association to enable |
| the check below to trigger TRUE */ |
| easy->easy_conn->data = easy->easy_handle; |
| } |
| else |
| /* Clear connection pipelines, if Curl_done above was not called */ |
| Curl_getoff_all_pipelines(easy->easy_handle, easy->easy_conn); |
| } |
| |
| /* figure out if the easy handle is used by one or more connections in the |
| cache */ |
| multi_connc_remove_handle(multi, easy->easy_handle); |
| |
| if(easy->easy_handle->state.connc->type == CONNCACHE_MULTI) { |
| /* if this was using the shared connection cache we clear the pointer |
| to that since we're not part of that handle anymore */ |
| easy->easy_handle->state.connc = NULL; |
| |
| /* Since we return the connection back to the communal connection pool |
| we mark the last connection as inaccessible */ |
| easy->easy_handle->state.lastconnect = -1; |
| |
| /* Modify the connectindex since this handle can't point to the |
| connection cache anymore. |
| |
| TODO: consider if this is really what we want. The connection cache |
| is within the multi handle and that owns the connections so we should |
| not need to touch connections like this when we just remove an easy |
| handle... |
| */ |
| if(easy->easy_conn && easy_owns_conn && |
| (easy->easy_conn->send_pipe->size + |
| easy->easy_conn->recv_pipe->size == 0)) |
| easy->easy_conn->connectindex = -1; |
| } |
| |
| /* change state without using multistate(), only to make singlesocket() do |
| what we want */ |
| easy->state = CURLM_STATE_COMPLETED; |
| singlesocket(multi, easy); /* to let the application know what sockets |
| that vanish with this handle */ |
| |
| Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association |
| to this multi handle */ |
| |
| { |
| /* make sure there's no pending message in the queue sent from this easy |
| handle */ |
| struct curl_llist_element *e; |
| |
| for(e = multi->msglist->head; e; e = e->next) { |
| struct Curl_message *msg = e->ptr; |
| |
| if(msg->extmsg.easy_handle == easy->easy_handle) { |
| Curl_llist_remove(multi->msglist, e, NULL); |
| /* there can only be one from this specific handle */ |
| break; |
| } |
| } |
| } |
| |
| /* make the previous node point to our next */ |
| if(easy->prev) |
| easy->prev->next = easy->next; |
| /* make our next point to our previous node */ |
| if(easy->next) |
| easy->next->prev = easy->prev; |
| |
| easy->easy_handle->set.one_easy = NULL; /* detached */ |
| |
| /* Null the position in the controlling structure */ |
| easy->easy_handle->multi_pos = NULL; |
| |
| /* NOTE NOTE NOTE |
| We do not touch the easy handle here! */ |
| free(easy); |
| |
| multi->num_easy--; /* one less to care about now */ |
| |
| update_timer(multi); |
| return CURLM_OK; |
| } |
| else |
| return CURLM_BAD_EASY_HANDLE; /* twasn't found */ |
| } |
| |
| bool Curl_multi_canPipeline(const struct Curl_multi* multi) |
| { |
| return multi->pipelining_enabled; |
| } |
| |
| void Curl_multi_handlePipeBreak(struct SessionHandle *data) |
| { |
| struct Curl_one_easy *one_easy = data->set.one_easy; |
| |
| if(one_easy) |
| one_easy->easy_conn = NULL; |
| } |
| |
| static int waitconnect_getsock(struct connectdata *conn, |
| curl_socket_t *sock, |
| int numsocks) |
| { |
| if(!numsocks) |
| return GETSOCK_BLANK; |
| |
| sock[0] = conn->sock[FIRSTSOCKET]; |
| |
| /* when we've sent a CONNECT to a proxy, we should rather wait for the |
| socket to become readable to be able to get the response headers */ |
| if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) |
| return GETSOCK_READSOCK(0); |
| |
| return GETSOCK_WRITESOCK(0); |
| } |
| |
| static int domore_getsock(struct connectdata *conn, |
| curl_socket_t *socks, |
| int numsocks) |
| { |
| if(conn && conn->handler->domore_getsock) |
| return conn->handler->domore_getsock(conn, socks, numsocks); |
| return GETSOCK_BLANK; |
| } |
| |
| /* returns bitmapped flags for this handle and its sockets */ |
| static int multi_getsock(struct Curl_one_easy *easy, |
| curl_socket_t *socks, /* points to numsocks number |
| of sockets */ |
| int numsocks) |
| { |
| /* If the pipe broke, or if there's no connection left for this easy handle, |
| then we MUST bail out now with no bitmask set. The no connection case can |
| happen when this is called from curl_multi_remove_handle() => |
| singlesocket() => multi_getsock(). |
| */ |
| if(easy->easy_handle->state.pipe_broke || !easy->easy_conn) |
| return 0; |
| |
| if(easy->state > CURLM_STATE_CONNECT && |
| easy->state < CURLM_STATE_COMPLETED) { |
| /* Set up ownership correctly */ |
| easy->easy_conn->data = easy->easy_handle; |
| } |
| |
| switch(easy->state) { |
| default: |
| #if 0 /* switch back on these cases to get the compiler to check for all enums |
| to be present */ |
| case CURLM_STATE_TOOFAST: /* returns 0, so will not select. */ |
| case CURLM_STATE_COMPLETED: |
| case CURLM_STATE_MSGSENT: |
| case CURLM_STATE_INIT: |
| case CURLM_STATE_CONNECT: |
| case CURLM_STATE_WAITDO: |
| case CURLM_STATE_DONE: |
| case CURLM_STATE_LAST: |
| /* this will get called with CURLM_STATE_COMPLETED when a handle is |
| removed */ |
| #endif |
| return 0; |
| |
| case CURLM_STATE_WAITRESOLVE: |
| return Curl_resolver_getsock(easy->easy_conn, socks, numsocks); |
| |
| case CURLM_STATE_PROTOCONNECT: |
| return Curl_protocol_getsock(easy->easy_conn, socks, numsocks); |
| |
| case CURLM_STATE_DO: |
| case CURLM_STATE_DOING: |
| return Curl_doing_getsock(easy->easy_conn, socks, numsocks); |
| |
| case CURLM_STATE_WAITPROXYCONNECT: |
| case CURLM_STATE_WAITCONNECT: |
| return waitconnect_getsock(easy->easy_conn, socks, numsocks); |
| |
| case CURLM_STATE_DO_MORE: |
| return domore_getsock(easy->easy_conn, socks, numsocks); |
| |
| case CURLM_STATE_DO_DONE: /* since is set after DO is completed, we switch |
| to waiting for the same as the *PERFORM |
| states */ |
| case CURLM_STATE_PERFORM: |
| case CURLM_STATE_WAITPERFORM: |
| return Curl_single_getsock(easy->easy_conn, socks, numsocks); |
| } |
| |
| } |
| |
| CURLMcode curl_multi_fdset(CURLM *multi_handle, |
| fd_set *read_fd_set, fd_set *write_fd_set, |
| fd_set *exc_fd_set, int *max_fd) |
| { |
| /* Scan through all the easy handles to get the file descriptors set. |
| Some easy handles may not have connected to the remote host yet, |
| and then we must make sure that is done. */ |
| struct Curl_multi *multi=(struct Curl_multi *)multi_handle; |
| struct Curl_one_easy *easy; |
| int this_max_fd=-1; |
| curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; |
| int bitmap; |
| int i; |
| (void)exc_fd_set; /* not used */ |
| |
| if(!GOOD_MULTI_HANDLE(multi)) |
| return CURLM_BAD_HANDLE; |
| |
| easy=multi->easy.next; |
| while(easy != &multi->easy) { |
| bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); |
| |
| for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { |
| curl_socket_t s = CURL_SOCKET_BAD; |
| |
| if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { |
| FD_SET(sockbunch[i], read_fd_set); |
| s = sockbunch[i]; |
| } |
| if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { |
| FD_SET(sockbunch[i], write_fd_set); |
| s = sockbunch[i]; |
| } |
| if(s == CURL_SOCKET_BAD) |
| /* this socket is unused, break out of loop */ |
| break; |
| else { |
| if((int)s > this_max_fd) |
| this_max_fd = (int)s; |
| } |
| } |
| |
| easy = easy->next; /* check next handle */ |
| } |
| |
| *max_fd = this_max_fd; |
| |
| return CURLM_OK; |
| } |
| |
| static CURLMcode multi_runsingle(struct Curl_multi *multi, |
| struct timeval now, |
| struct Curl_one_easy *easy) |
| { |
| struct Curl_message *msg = NULL; |
| bool connected; |
| bool async; |
| bool protocol_connect = FALSE; |
| bool dophase_done; |
| bool done = FALSE; |
| CURLMcode result = CURLM_OK; |
| struct SingleRequest *k; |
| struct SessionHandle *data; |
| long timeout_ms; |
| |
| if(!GOOD_EASY_HANDLE(easy->easy_handle)) |
| return CURLM_BAD_EASY_HANDLE; |
| |
| data = easy->easy_handle; |
| |
| do { |
| /* this is a single-iteration do-while loop just to allow a |
| break to skip to the end of it */ |
| bool disconnect_conn = FALSE; |
| |
| /* Handle the case when the pipe breaks, i.e., the connection |
| we're using gets cleaned up and we're left with nothing. */ |
| if(data->state.pipe_broke) { |
| infof(data, "Pipe broke: handle 0x%p, url = %s\n", |
| easy, data->state.path); |
| |
| if(easy->state < CURLM_STATE_COMPLETED) { |
| /* Head back to the CONNECT state */ |
| multistate(easy, CURLM_STATE_CONNECT); |
| result = CURLM_CALL_MULTI_PERFORM; |
| easy->result = CURLE_OK; |
| } |
| |
| data->state.pipe_broke = FALSE; |
| easy->easy_conn = NULL; |
| break; |
| } |
| |
| if(easy->easy_conn && easy->state > CURLM_STATE_CONNECT && |
| easy->state < CURLM_STATE_COMPLETED) |
| /* Make sure we set the connection's current owner */ |
| easy->easy_conn->data = data; |
| |
| if(easy->easy_conn && |
| (easy->state >= CURLM_STATE_CONNECT) && |
| (easy->state < CURLM_STATE_COMPLETED)) { |
| /* we need to wait for the connect state as only then is the start time |
| stored, but we must not check already completed handles */ |
| |
| timeout_ms = Curl_timeleft(data, &now, |
| (easy->state <= CURLM_STATE_WAITDO)? |
| TRUE:FALSE); |
| |
| if(timeout_ms < 0) { |
| /* Handle timed out */ |
| if(easy->state == CURLM_STATE_WAITRESOLVE) |
| failf(data, "Resolving timed out after %ld milliseconds", |
| Curl_tvdiff(now, data->progress.t_startsingle)); |
| else if(easy->state == CURLM_STATE_WAITCONNECT) |
| failf(data, "Connection timed out after %ld milliseconds", |
| Curl_tvdiff(now, data->progress.t_startsingle)); |
| else { |
| k = &data->req; |
| failf(data, "Operation timed out after %ld milliseconds with %" |
| FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received", |
| Curl_tvdiff(now, data->progress.t_startsingle), k->bytecount, |
| k->size); |
| } |
| |
| /* Force the connection closed because the server could continue to |
| send us stuff at any time. (The disconnect_conn logic used below |
| doesn't work at this point). */ |
| easy->easy_conn->bits.close = TRUE; |
| easy->result = CURLE_OPERATION_TIMEDOUT; |
| multistate(easy, CURLM_STATE_COMPLETED); |
| break; |
| } |
| } |
| |
| switch(easy->state) { |
| case CURLM_STATE_INIT: |
| /* init this transfer. */ |
| easy->result=Curl_pretransfer(data); |
| |
| if(CURLE_OK == easy->result) { |
| /* after init, go CONNECT */ |
| multistate(easy, CURLM_STATE_CONNECT); |
| result = CURLM_CALL_MULTI_PERFORM; |
| |
| data->state.used_interface = Curl_if_multi; |
| } |
| break; |
| |
| case CURLM_STATE_CONNECT: |
| /* Connect. We get a connection identifier filled in. */ |
| Curl_pgrsTime(data, TIMER_STARTSINGLE); |
| easy->result = Curl_connect(data, &easy->easy_conn, |
| &async, &protocol_connect); |
| |
| if(CURLE_OK == easy->result) { |
| /* Add this handle to the send or pend pipeline */ |
| easy->result = addHandleToSendOrPendPipeline(data, |
| easy->easy_conn); |
| if(CURLE_OK != easy->result) |
| disconnect_conn = TRUE; |
| else { |
| if(async) |
| /* We're now waiting for an asynchronous name lookup */ |
| multistate(easy, CURLM_STATE_WAITRESOLVE); |
| else { |
| /* after the connect has been sent off, go WAITCONNECT unless the |
| protocol connect is already done and we can go directly to |
| WAITDO or DO! */ |
| result = CURLM_CALL_MULTI_PERFORM; |
| |
| if(protocol_connect) |
| multistate(easy, multi->pipelining_enabled? |
| CURLM_STATE_WAITDO:CURLM_STATE_DO); |
| else { |
| #ifndef CURL_DISABLE_HTTP |
| if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) |
| multistate(easy, CURLM_STATE_WAITPROXYCONNECT); |
| else |
| #endif |
| multistate(easy, CURLM_STATE_WAITCONNECT); |
| } |
| } |
| } |
| } |
| break; |
| |
| case CURLM_STATE_WAITRESOLVE: |
| /* awaiting an asynch name resolve to complete */ |
| { |
| struct Curl_dns_entry *dns = NULL; |
| |
| /* check if we have the name resolved by now */ |
| easy->result = Curl_resolver_is_resolved(easy->easy_conn, &dns); |
| |
| /* Update sockets here, because the socket(s) may have been |
| closed and the application thus needs to be told, even if it |
| is likely that the same socket(s) will again be used further |
| down. If the name has not yet been resolved, it is likely |
| that new sockets have been opened in an attempt to contact |
| another resolver. */ |
| singlesocket(multi, easy); |
| |
| if(dns) { |
| /* 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 */ |
| easy->easy_conn = NULL; /* no more connection */ |
| else { |
| /* call again please so that we get the next socket setup */ |
| result = CURLM_CALL_MULTI_PERFORM; |
| if(protocol_connect) |
| multistate(easy, multi->pipelining_enabled? |
| CURLM_STATE_WAITDO:CURLM_STATE_DO); |
| else { |
| #ifndef CURL_DISABLE_HTTP |
| if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) |
| multistate(easy, CURLM_STATE_WAITPROXYCONNECT); |
| else |
| #endif |
| multistate(easy, CURLM_STATE_WAITCONNECT); |
| } |
| } |
| } |
| |
| if(CURLE_OK != easy->result) { |
| /* failure detected */ |
| disconnect_conn = TRUE; |
| break; |
| } |
| } |
| break; |
| |
| #ifndef CURL_DISABLE_HTTP |
| 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); |
| |
| if(easy->easy_conn->bits.proxy_connect_closed) { |
| /* reset the error buffer */ |
| if(data->set.errorbuffer) |
| data->set.errorbuffer[0] = '\0'; |
| data->state.errorbuf = FALSE; |
| |
| easy->result = CURLE_OK; |
| result = CURLM_CALL_MULTI_PERFORM; |
| multistate(easy, CURLM_STATE_CONNECT); |
| } |
| else if(CURLE_OK == easy->result) { |
| if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_COMPLETE) |
| multistate(easy, CURLM_STATE_WAITCONNECT); |
| } |
| break; |
| #endif |
| |
| case CURLM_STATE_WAITCONNECT: |
| /* awaiting a completion of an asynch connect */ |
| easy->result = Curl_is_connected(easy->easy_conn, |
| FIRSTSOCKET, |
| &connected); |
| if(connected) { |
| |
| if(!easy->result) |
| /* if everything is still fine we do the protocol-specific connect |
| setup */ |
| easy->result = Curl_protocol_connect(easy->easy_conn, |
| &protocol_connect); |
| } |
| |
| if(CURLE_OK != easy->result) { |
| /* failure detected */ |
| /* Just break, the cleaning up is handled all in one place */ |
| disconnect_conn = TRUE; |
| break; |
| } |
| |
| 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 |
| connect is TRUE, we move on to STATE_DO. |
| BUT if we are using a proxy we must change to WAITPROXYCONNECT |
| */ |
| #ifndef CURL_DISABLE_HTTP |
| if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) |
| multistate(easy, CURLM_STATE_WAITPROXYCONNECT); |
| else |
| #endif |
| multistate(easy, CURLM_STATE_PROTOCONNECT); |
| |
| } |
| else |
| /* after the connect has completed, go WAITDO or DO */ |
| multistate(easy, multi->pipelining_enabled? |
| CURLM_STATE_WAITDO:CURLM_STATE_DO); |
| |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| break; |
| |
| 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) { |
| /* after the connect has completed, go WAITDO or DO */ |
| multistate(easy, multi->pipelining_enabled? |
| CURLM_STATE_WAITDO:CURLM_STATE_DO); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| else if(easy->result) { |
| /* failure detected */ |
| Curl_posttransfer(data); |
| Curl_done(&easy->easy_conn, easy->result, TRUE); |
| disconnect_conn = TRUE; |
| } |
| break; |
| |
| case CURLM_STATE_WAITDO: |
| /* Wait for our turn to DO when we're pipelining requests */ |
| #ifdef DEBUGBUILD |
| infof(data, "Conn %ld send pipe %zu inuse %d athead %d\n", |
| easy->easy_conn->connectindex, |
| easy->easy_conn->send_pipe->size, |
| easy->easy_conn->writechannel_inuse?1:0, |
| isHandleAtHead(data, |
| easy->easy_conn->send_pipe)?1:0); |
| #endif |
| if(!easy->easy_conn->writechannel_inuse && |
| isHandleAtHead(data, |
| 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; |
| |
| case CURLM_STATE_DO: |
| if(data->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_CALL_MULTI_PERFORM; |
| } |
| else { |
| /* Perform the protocol's DO action */ |
| easy->result = Curl_do(&easy->easy_conn, |
| &dophase_done); |
| |
| if(CURLE_OK == easy->result) { |
| if(!dophase_done) { |
| /* some steps needed for wildcard matching */ |
| if(data->set.wildcardmatch) { |
| struct WildcardData *wc = &data->wildcard; |
| if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { |
| /* skip some states if it is important */ |
| Curl_done(&easy->easy_conn, CURLE_OK, FALSE); |
| multistate(easy, CURLM_STATE_DONE); |
| result = CURLM_CALL_MULTI_PERFORM; |
| break; |
| } |
| } |
| /* 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 */ |
| 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 */ |
| multistate(easy, CURLM_STATE_DO_DONE); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| } |
| 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. |
| */ |
| char *newurl = NULL; |
| 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 |
| retry = (newurl)?TRUE:FALSE; |
| |
| Curl_posttransfer(data); |
| drc = Curl_done(&easy->easy_conn, easy->result, FALSE); |
| |
| /* When set to retry the connection, we must to go back to |
| * the CONNECT state */ |
| if(retry) { |
| if((drc == CURLE_OK) || (drc == CURLE_SEND_ERROR)) { |
| follow = FOLLOW_RETRY; |
| drc = Curl_follow(data, newurl, follow); |
| if(drc == CURLE_OK) { |
| multistate(easy, CURLM_STATE_CONNECT); |
| result = CURLM_CALL_MULTI_PERFORM; |
| easy->result = CURLE_OK; |
| } |
| else { |
| /* Follow failed */ |
| easy->result = drc; |
| free(newurl); |
| } |
| } |
| else { |
| /* done didn't return OK or SEND_ERROR */ |
| easy->result = drc; |
| free(newurl); |
| } |
| } |
| else { |
| /* Have error handler disconnect conn if we can't retry */ |
| disconnect_conn = TRUE; |
| } |
| } |
| else { |
| /* failure detected */ |
| Curl_posttransfer(data); |
| Curl_done(&easy->easy_conn, easy->result, FALSE); |
| disconnect_conn = TRUE; |
| } |
| } |
| break; |
| |
| case CURLM_STATE_DOING: |
| /* we continue DOING until the DO phase is complete */ |
| easy->result = Curl_protocol_doing(easy->easy_conn, |
| &dophase_done); |
| if(CURLE_OK == easy->result) { |
| if(dophase_done) { |
| /* after DO, go DO_DONE or DO_MORE */ |
| multistate(easy, easy->easy_conn->bits.do_more? |
| CURLM_STATE_DO_MORE: |
| CURLM_STATE_DO_DONE); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } /* dophase_done */ |
| } |
| else { |
| /* failure detected */ |
| Curl_posttransfer(data); |
| Curl_done(&easy->easy_conn, easy->result, FALSE); |
| disconnect_conn = TRUE; |
| } |
| break; |
| |
| case CURLM_STATE_DO_MORE: |
| /* |
| * When we are connected, DO MORE and then go DO_DONE |
| */ |
| easy->result = Curl_do_more(easy->easy_conn, &dophase_done); |
| |
| /* No need to remove this handle from the send pipeline here since that |
| is done in Curl_done() */ |
| if(CURLE_OK == easy->result) { |
| if(dophase_done) { |
| multistate(easy, CURLM_STATE_DO_DONE); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| else |
| /* stay in DO_MORE */ |
| result = CURLM_OK; |
| } |
| else { |
| /* failure detected */ |
| Curl_posttransfer(data); |
| Curl_done(&easy->easy_conn, easy->result, FALSE); |
| disconnect_conn = TRUE; |
| } |
| break; |
| |
| case CURLM_STATE_DO_DONE: |
| /* Move ourselves from the send to recv pipeline */ |
| moveHandleFromSendToRecvPipeline(data, 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 */ |
| if(!easy->easy_conn->readchannel_inuse && |
| isHandleAtHead(data, |
| easy->easy_conn->recv_pipe)) { |
| /* Grab the channel */ |
| easy->easy_conn->readchannel_inuse = TRUE; |
| multistate(easy, CURLM_STATE_PERFORM); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| #ifdef DEBUGBUILD |
| else { |
| infof(data, "Conn %ld recv pipe %zu inuse %d athead %d\n", |
| easy->easy_conn->connectindex, |
| easy->easy_conn->recv_pipe->size, |
| easy->easy_conn->readchannel_inuse?1:0, |
| isHandleAtHead(data, |
| easy->easy_conn->recv_pipe)?1:0); |
| } |
| #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); |
| if(( (data->set.max_send_speed == 0) || |
| (data->progress.ulspeed < data->set.max_send_speed )) && |
| ( (data->set.max_recv_speed == 0) || |
| (data->progress.dlspeed < data->set.max_recv_speed))) |
| multistate(easy, CURLM_STATE_PERFORM); |
| break; |
| |
| case CURLM_STATE_PERFORM: |
| /* check if over send speed */ |
| if((data->set.max_send_speed > 0) && |
| (data->progress.ulspeed > data->set.max_send_speed)) { |
| int buffersize; |
| |
| multistate(easy, CURLM_STATE_TOOFAST); |
| |
| /* calculate upload rate-limitation timeout. */ |
| buffersize = (int)(data->set.buffer_size ? |
| data->set.buffer_size : BUFSIZE); |
| timeout_ms = Curl_sleep_time(data->set.max_send_speed, |
| data->progress.ulspeed, buffersize); |
| Curl_expire(data, timeout_ms); |
| break; |
| } |
| |
| /* check if over recv speed */ |
| if((data->set.max_recv_speed > 0) && |
| (data->progress.dlspeed > data->set.max_recv_speed)) { |
| int buffersize; |
| |
| multistate(easy, CURLM_STATE_TOOFAST); |
| |
| /* Calculate download rate-limitation timeout. */ |
| buffersize = (int)(data->set.buffer_size ? |
| data->set.buffer_size : BUFSIZE); |
| timeout_ms = Curl_sleep_time(data->set.max_recv_speed, |
| data->progress.dlspeed, buffersize); |
| Curl_expire(data, timeout_ms); |
| break; |
| } |
| |
| /* read/write data if it is ready to do so */ |
| easy->result = Curl_readwrite(easy->easy_conn, &done); |
| |
| k = &data->req; |
| |
| if(!(k->keepon & KEEP_RECV)) { |
| /* We're done receiving */ |
| easy->easy_conn->readchannel_inuse = FALSE; |
| } |
| |
| if(!(k->keepon & KEEP_SEND)) { |
| /* We're done sending */ |
| easy->easy_conn->writechannel_inuse = FALSE; |
| } |
| |
| if(easy->result) { |
| /* The transfer phase returned error, we mark the connection to get |
| * closed to prevent being re-used. This is because we can't possibly |
| * know if the connection is in a good shape or not now. Unless it is |
| * a protocol which uses two "channels" like FTP, as then the error |
| * happened in the data connection. |
| */ |
| if(!(easy->easy_conn->handler->flags & PROTOPT_DUAL)) |
| easy->easy_conn->bits.close = TRUE; |
| |
| Curl_posttransfer(data); |
| Curl_done(&easy->easy_conn, easy->result, FALSE); |
| } |
| else if(done) { |
| char *newurl = NULL; |
| bool retry = FALSE; |
| followtype follow=FOLLOW_NONE; |
| |
| easy->result = Curl_retry_request(easy->easy_conn, &newurl); |
| if(!easy->result) |
| retry = (newurl)?TRUE:FALSE; |
| |
| /* call this even if the readwrite function returned error */ |
| Curl_posttransfer(data); |
| |
| /* we're no longer receiving */ |
| moveHandleFromRecvToDonePipeline(data, |
| easy->easy_conn); |
| |
| /* 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 */ |
| if(data->req.newurl || retry) { |
| if(!retry) { |
| /* if the URL is a follow-location and not just a retried request |
| then figure out the URL here */ |
| newurl = data->req.newurl; |
| data->req.newurl = NULL; |
| follow = FOLLOW_REDIR; |
| } |
| else |
| follow = FOLLOW_RETRY; |
| easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE); |
| if(easy->result == CURLE_OK) |
| easy->result = Curl_follow(data, newurl, follow); |
| if(CURLE_OK == easy->result) { |
| multistate(easy, CURLM_STATE_CONNECT); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| else if(newurl) |
| /* Since we "took it", we are in charge of freeing this on |
| failure */ |
| free(newurl); |
| } |
| else { |
| /* after the transfer is done, go DONE */ |
| |
| /* but first check to see if we got a location info even though we're |
| not following redirects */ |
| if(data->req.location) { |
| newurl = data->req.location; |
| data->req.location = NULL; |
| easy->result = Curl_follow(data, newurl, FOLLOW_FAKE); |
| if(easy->result) { |
| disconnect_conn = TRUE; |
| free(newurl); |
| } |
| } |
| |
| multistate(easy, CURLM_STATE_DONE); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| } |
| |
| break; |
| |
| 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(data, |
| easy->easy_conn->recv_pipe); |
| Curl_removeHandleFromPipeline(data, |
| 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; |
| } |
| |
| if(data->set.wildcardmatch) { |
| if(data->wildcard.state != CURLWC_DONE) { |
| /* if a wildcard is set and we are not ending -> lets start again |
| with CURLM_STATE_INIT */ |
| result = CURLM_CALL_MULTI_PERFORM; |
| multistate(easy, CURLM_STATE_INIT); |
| break; |
| } |
| } |
| |
| /* after we have DONE what we're supposed to do, go COMPLETED, and |
| it doesn't matter what the Curl_done() returned! */ |
| multistate(easy, CURLM_STATE_COMPLETED); |
| |
| break; |
| |
| case CURLM_STATE_COMPLETED: |
| /* this is a completed transfer, it is likely to still be connected */ |
| |
| /* This node should be delinked from the list now and we should post |
| an information message that we are complete. */ |
| |
| /* Important: reset the conn pointer so that we don't point to memory |
| that could be freed anytime */ |
| easy->easy_conn = NULL; |
| |
| Curl_expire(data, 0); /* stop all timers */ |
| break; |
| |
| case CURLM_STATE_MSGSENT: |
| return CURLM_OK; /* do nothing */ |
| |
| default: |
| return CURLM_INTERNAL_ERROR; |
| } |
| |
| if(easy->state < CURLM_STATE_COMPLETED) { |
| if(CURLE_OK != easy->result) { |
| /* |
| * If an error was returned, and we aren't in completed state now, |
| * then we go to completed and consider this transfer aborted. |
| */ |
| |
| /* NOTE: no attempt to disconnect connections must be made |
| in the case blocks above - cleanup happens only here */ |
| |
| data->state.pipe_broke = FALSE; |
| |
| if(easy->easy_conn) { |
| /* if this has a connection, unsubscribe from the pipelines */ |
| easy->easy_conn->writechannel_inuse = FALSE; |
| easy->easy_conn->readchannel_inuse = FALSE; |
| Curl_removeHandleFromPipeline(data, |
| easy->easy_conn->send_pipe); |
| Curl_removeHandleFromPipeline(data, |
| easy->easy_conn->recv_pipe); |
| Curl_removeHandleFromPipeline(data, |
| easy->easy_conn->done_pipe); |
| /* Check if we can move pending requests to send pipe */ |
| checkPendPipeline(easy->easy_conn); |
| |
| if(disconnect_conn) { |
| /* disconnect properly */ |
| Curl_disconnect(easy->easy_conn, /* dead_connection */ FALSE); |
| |
| /* This is where we make sure that the easy_conn pointer is reset. |
| We don't have to do this in every case block above where a |
| failure is detected */ |
| easy->easy_conn = NULL; |
| } |
| } |
| else if(easy->state == CURLM_STATE_CONNECT) { |
| /* Curl_connect() failed */ |
| (void)Curl_posttransfer(data); |
| } |
| |
| multistate(easy, CURLM_STATE_COMPLETED); |
| } |
| /* if there's still a connection to use, call the progress function */ |
| else if(easy->easy_conn && Curl_pgrsUpdate(easy->easy_conn)) { |
| /* aborted due to progress callback return code must close the |
| connection */ |
| easy->easy_conn->bits.close = TRUE; |
| |
| /* if not yet in DONE state, go there, otherwise COMPLETED */ |
| multistate(easy, (easy->state < CURLM_STATE_DONE)? |
| CURLM_STATE_DONE: CURLM_STATE_COMPLETED); |
| result = CURLM_CALL_MULTI_PERFORM; |
| } |
| } |
| } WHILE_FALSE; /* just to break out from! */ |
| |
| if(CURLM_STATE_COMPLETED == easy->state) { |
| if(data->dns.hostcachetype == HCACHE_MULTI) { |
| /* clear out the usage of the shared DNS cache */ |
| data->dns.hostcache = NULL; |
| data->dns.hostcachetype = HCACHE_NONE; |
| } |
| |
| /* now fill in the Curl_message with this info */ |
| msg = &easy->msg; |
| |
| msg->extmsg.msg = CURLMSG_DONE; |
| msg->extmsg.easy_handle = data; |
| msg->extmsg.data.result = easy->result; |
| |
| result = multi_addmsg(multi, msg); |
| |
| multistate(easy, CURLM_STATE_MSGSENT); |
| } |
| |
| return result; |
| } |
| |
| |
| 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; |
| struct timeval now = Curl_tvnow(); |
| |
| if(!GOOD_MULTI_HANDLE(multi)) |
| return CURLM_BAD_HANDLE; |
| |
| easy=multi->easy.next; |
| while(easy != &multi->easy) { |
| CURLMcode result; |
| struct WildcardData *wc = &easy->easy_handle->wildcard; |
| |
| if(easy->easy_handle->set.wildcardmatch) { |
| if(!wc->filelist) { |
| CURLcode ret = Curl_wildcard_init(wc); /* init wildcard structures */ |
| if(ret) |
| return CURLM_OUT_OF_MEMORY; |
| } |
| } |
| |
| do |
| result = multi_runsingle(multi, now, easy); |
| while(CURLM_CALL_MULTI_PERFORM == result); |
| |
| if(easy->easy_handle->set.wildcardmatch) { |
| /* destruct wildcard structures if it is needed */ |
| if(wc->state == CURLWC_DONE || result) |
| Curl_wildcard_dtor(wc); |
| } |
| |
| if(result) |
| returncode = result; |
| |
| easy = easy->next; /* operate on next handle */ |
| } |
| |
| /* |
| * Simply remove all expired timers from the splay since handles are dealt |
| * with unconditionally by this function and curl_multi_timeout() requires |
| * that already passed/handled expire times are removed from the splay. |
| * |
| * It is important that the 'now' value is set at the entry of this function |
| * and not for the current time as it may have ticked a little while since |
| * then and then we risk this loop to remove timers that actually have not |
| * been handled! |
| */ |
| do { |
| multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); |
| if(t) |
| /* the removed may have another timeout in queue */ |
| (void)add_next_timeout(now, multi, t->payload); |
| |
| } while(t); |
| |
| *running_handles = multi->num_alive; |
| |
| if(CURLM_OK >= returncode) |
| update_timer(multi); |
| |
| return returncode; |
| } |
| |
| 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 */ |
| |
| /* go over all connections that have close actions */ |
| for(i=0; i< multi->connc->num; i++) { |
| if(multi->connc->connects[i] && |
| multi->connc->connects[i]->handler->flags & PROTOPT_CLOSEACTION) { |
| Curl_disconnect(multi->connc->connects[i], FALSE); |
| 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; /* allow cleanup */ |
| 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_hash_destroy(multi->hostcache); |
| multi->hostcache = NULL; |
| |
| Curl_hash_destroy(multi->sockhash); |
| multi->sockhash = NULL; |
| |
| Curl_rm_connc(multi->connc); |
| multi->connc = NULL; |
| |
| /* remove the pending list of messages */ |
| Curl_llist_destroy(multi->msglist, NULL); |
| multi->msglist = NULL; |
| |
| /* remove all easy handles */ |
| easy = multi->easy.next; |
| while(easy != &multi->easy) { |
| nexteasy=easy->next; |
| 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 */ |
| |
| free(easy); |
| easy = nexteasy; |
| } |
| |
| free(multi); |
| |
| return CURLM_OK; |
| } |
| else |
| return CURLM_BAD_HANDLE; |
| } |
| |
| /* |
| * curl_multi_info_read() |
| * |
| * This function is the primary way for a multi/multi_socket application to |
| * figure out if a transfer has ended. We MUST make this function as fast as |
| * possible as it will be polled frequently and we MUST NOT scan any lists in |
| * here to figure out things. We must scale fine to thousands of handles and |
| * beyond. The current design is fully O(1). |
| */ |
| |
| CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue) |
| { |
| struct Curl_multi *multi=(struct Curl_multi *)multi_handle; |
| struct Curl_message *msg; |
| |
| *msgs_in_queue = 0; /* default to none */ |
| |
| if(GOOD_MULTI_HANDLE(multi) && Curl_llist_count(multi->msglist)) { |
| /* there is one or more messages in the list */ |
| struct curl_llist_element *e; |
| |
| /* extract the head of the list to return */ |
| e = multi->msglist->head; |
| |
| msg = e->ptr; |
| |
| /* remove the extracted entry */ |
| Curl_llist_remove(multi->msglist, e, NULL); |
| |
| *msgs_in_queue = curlx_uztosi(Curl_llist_count(multi->msglist)); |
| |
| return &msg->extmsg; |
| } |
| else |
| return NULL; |
| } |
| |
| /* |
| * 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. |
| */ |
| static void singlesocket(struct Curl_multi *multi, |
| struct Curl_one_easy *easy) |
| { |
| curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; |
| int i; |
| struct Curl_sh_entry *entry; |
| curl_socket_t s; |
| int num; |
| unsigned int curraction; |
| struct Curl_one_easy *easy_by_hash; |
| bool remove_sock_from_hash; |
| |
| for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) |
| socks[i] = CURL_SOCKET_BAD; |
| |
| /* Fill in the 'current' struct with the state as it is now: what sockets to |
| supervise and for what actions */ |
| curraction = multi_getsock(easy, socks, MAX_SOCKSPEREASYHANDLE); |
| |
| /* 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 */ |
| |
| /* walk over the sockets we got right now */ |
| for(i=0; (i< MAX_SOCKSPEREASYHANDLE) && |
| (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); |
| i++) { |
| int action = CURL_POLL_NONE; |
| |
| s = socks[i]; |
| |
| /* get it from the hash */ |
| entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); |
| |
| if(curraction & GETSOCK_READSOCK(i)) |
| action |= CURL_POLL_IN; |
| if(curraction & GETSOCK_WRITESOCK(i)) |
| action |= CURL_POLL_OUT; |
| |
| 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; |
| } |
| |
| /* 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); |
| |
| entry->action = action; /* store the current action state */ |
| } |
| |
| 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++) { |
| if(s == socks[j]) { |
| /* this is still supervised */ |
| s = CURL_SOCKET_BAD; |
| break; |
| } |
| } |
| if(s != CURL_SOCKET_BAD) { |
| |
| /* 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) { |
| /* 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 */ |
| remove_sock_from_hash = FALSE; |
| |
| if(remove_sock_from_hash) { |
| if(multi->socket_cb) |
| multi->socket_cb(easy->easy_handle, |
| s, |
| CURL_POLL_REMOVE, |
| multi->socket_userp, |
| entry ? entry->socketp : NULL); |
| sh_delentry(multi->sockhash, s); |
| } |
| |
| } |
| } |
| |
| memcpy(easy->sockets, socks, num*sizeof(curl_socket_t)); |
| easy->numsocks = num; |
| } |
| |
| /* |
| * 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; |
| } |
| if(!list->size) { |
| /* clear the expire times within the handles that we remove from the |
| splay tree */ |
| tv->tv_sec = 0; |
| tv->tv_usec = 0; |
| } |
| else { |
| e = list->head; |
| /* 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; |
| } |
| |
| |
| static CURLMcode multi_socket(struct Curl_multi *multi, |
| bool checkall, |
| curl_socket_t s, |
| int ev_bitmask, |
| int *running_handles) |
| { |
| CURLMcode result = CURLM_OK; |
| struct SessionHandle *data = NULL; |
| struct Curl_tree *t; |
| struct timeval now = Curl_tvnow(); |
| |
| if(checkall) { |
| struct Curl_one_easy *easyp; |
| /* *perform() deals with running_handles on its own */ |
| result = curl_multi_perform(multi, running_handles); |
| |
| /* walk through each easy handle and do the socket state change magic |
| and callbacks */ |
| easyp=multi->easy.next; |
| while(easyp != &multi->easy) { |
| singlesocket(multi, easyp); |
| easyp = easyp->next; |
| } |
| |
| /* or should we fall-through and do the timer-based stuff? */ |
| return result; |
| } |
| else if(s != CURL_SOCKET_TIMEOUT) { |
| |
| struct Curl_sh_entry *entry = |
| Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); |
| |
| if(!entry) |
| /* 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; |
| |
| if(data->magic != CURLEASY_MAGIC_NUMBER) |
| /* bad bad bad bad bad bad bad */ |
| return CURLM_INTERNAL_ERROR; |
| |
| /* 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; |
| } |
| |
| 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; |
| |
| do |
| result = multi_runsingle(multi, now, data->set.one_easy); |
| while(CURLM_CALL_MULTI_PERFORM == result); |
| |
| 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; |
| |
| if(CURLM_OK >= result) |
| /* get the socket(s) and check if the state has been changed since |
| last */ |
| singlesocket(multi, data->set.one_easy); |
| |
| /* 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. */ |
| |
| data = NULL; /* set data to NULL again to avoid calling |
| multi_runsingle() in case there's no need to */ |
| } |
| } |
| |
| 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; |
| } |
| |
| /* |
| * 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) { |
| do |
| result = multi_runsingle(multi, now, data->set.one_easy); |
| while(CURLM_CALL_MULTI_PERFORM == result); |
| |
| if(CURLM_OK >= result) |
| /* 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); |
| } |
| |
| } while(t); |
| |
| *running_handles = multi->num_alive; |
| return result; |
| } |
| |
| #undef curl_multi_setopt |
| 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; |
| case CURLMOPT_MAXCONNECTS: |
| multi->maxconnects = va_arg(param, long); |
| break; |
| default: |
| res = CURLM_UNKNOWN_OPTION; |
| break; |
| } |
| va_end(param); |
| return res; |
| } |
| |
| /* we define curl_multi_socket() in the public multi.h header */ |
| #undef curl_multi_socket |
| |
| CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, |
| int *running_handles) |
| { |
| CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s, |
| 0, running_handles); |
| if(CURLM_OK >= result) |
| update_timer((struct Curl_multi *)multi_handle); |
| return result; |
| } |
| |
| CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s, |
| int ev_bitmask, int *running_handles) |
| { |
| CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s, |
| ev_bitmask, running_handles); |
| if(CURLM_OK >= result) |
| update_timer((struct Curl_multi *)multi_handle); |
| return result; |
| } |
| |
| CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles) |
| |
| { |
| CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, |
| TRUE, CURL_SOCKET_BAD, 0, running_handles); |
| if(CURLM_OK >= result) |
| update_timer((struct Curl_multi *)multi_handle); |
| return result; |
| } |
| |
| static CURLMcode multi_timeout(struct Curl_multi *multi, |
| long *timeout_ms) |
| { |
| static struct timeval tv_zero = {0,0}; |
| |
| 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); |
| |
| if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) { |
| /* some time left before expiration */ |
| *timeout_ms = curlx_tvdiff(multi->timetree->key, now); |
| 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; |
| } |
| else |
| /* 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; |
| |
| 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); |
| } |
| |
| 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; |
| struct curl_llist *pipeline; |
| CURLcode rc; |
| |
| 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 */ |
| #ifdef DEBUGBUILD |
| infof(conn->data, "%p is at send pipe head!\n", |
| conn->send_pipe->head->ptr); |
| #endif |
| Curl_expire(conn->send_pipe->head->ptr, 1); |
| } |
| |
| return rc; |
| } |
| |
| static int checkPendPipeline(struct connectdata *conn) |
| { |
| int result = 0; |
| struct curl_llist_element *sendhead = conn->send_pipe->head; |
| |
| size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; |
| if(conn->server_supports_pipelining || pipeLen == 0) { |
| struct curl_llist_element *curr = conn->pend_pipe->head; |
| const size_t maxPipeLen = |
| conn->server_supports_pipelining ? MAX_PIPELINE_LENGTH : 1; |
| |
| while(pipeLen < maxPipeLen && curr) { |
| Curl_llist_move(conn->pend_pipe, curr, |
| conn->send_pipe, conn->send_pipe->tail); |
| Curl_pgrsTime(curr->ptr, TIMER_PRETRANSFER); |
| ++result; /* count how many handles we moved */ |
| curr = conn->pend_pipe->head; |
| ++pipeLen; |
| } |
| } |
| |
| if(result) { |
| conn->now = Curl_tvnow(); |
| /* 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 */ |
| #ifdef DEBUGBUILD |
| infof(conn->data, "%p is at send pipe head!\n", |
| conn->send_pipe->head->ptr); |
| #endif |
| Curl_expire(conn->send_pipe->head->ptr, 1); |
| } |
| } |
| |
| return result; |
| } |
| |
| /* 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. |
| |
| */ |
| static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle, |
| struct connectdata *conn) |
| { |
| 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); |
| |
| 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 */ |
| #ifdef DEBUGBUILD |
| infof(conn->data, "%p is at send pipe head B!\n", |
| conn->send_pipe->head->ptr); |
| #endif |
| 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! */ |
| } |
| 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; |
| } |
| } |
| 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; |
| |
| return FALSE; |
| } |
| |
| /* |
| * 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); |
| return CURLM_OUT_OF_MEMORY; |
| } |
| |
| return CURLM_OK; |
| } |
| |
| /* |
| * 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. |
| */ |
| void Curl_expire(struct SessionHandle *data, long milli) |
| { |
| struct Curl_multi *multi = data->multi; |
| struct timeval *nowp = &data->state.expiretime; |
| int rc; |
| |
| /* 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) { |
| /* 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); |
| |
| #ifdef DEBUGBUILD |
| infof(data, "Expire cleared\n"); |
| #endif |
| nowp->tv_sec = 0; |
| nowp->tv_usec = 0; |
| } |
| } |
| else { |
| struct timeval set; |
| |
| set = Curl_tvnow(); |
| set.tv_sec += milli/1000; |
| set.tv_usec += (milli%1000)*1000; |
| |
| if(set.tv_usec >= 1000000) { |
| 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. */ |
| 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); |
| 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); |
| |
| /* 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); |
| } |
| |
| *nowp = set; |
| data->state.timenode.payload = data; |
| multi->timetree = Curl_splayinsert(*nowp, |
| multi->timetree, |
| &data->state.timenode); |
| } |
| #if 0 |
| Curl_splayprint(multi->timetree, 0, TRUE); |
| #endif |
| } |
| |
| 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; |
| data->state.shared_conn = NULL; |
| } |
| } |
| else { |
| /* disconect the easy handle from the connection since the connection |
| will now remain but this easy handle is going */ |
| data->state.shared_conn = NULL; |
| 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) { |
| struct closure *n; |
| bool inuse = FALSE; |
| int i; |
| |
| 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; |
| } |
| |
| #ifdef DEBUGBUILD |
| void Curl_multi_dump(const struct Curl_multi *multi_handle) |
| { |
| 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); |
| for(easy=multi->easy.next; easy != &multi->easy; easy = easy->next) { |
| if(easy->state < CURLM_STATE_COMPLETED) { |
| /* only display handles that are not completed */ |
| fprintf(stderr, "handle %p, state %s, %d sockets\n", |
| (void *)easy->easy_handle, |
| statename[easy->state], easy->numsocks); |
| 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 |