From 686d90745be4417127050ad4b36d0a5403def200 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Mon, 10 Apr 2006 15:00:53 +0000 Subject: [PATCH] First curl_multi_socket() commit. Should primarily be considered as an internal code rearrange to fit the future better. --- lib/Makefile.inc | 4 +- lib/connect.c | 10 +- lib/ftp.c | 26 +- lib/ftp.h | 7 +- lib/hash.c | 26 +- lib/hostares.c | 14 +- lib/hostip.h | 13 +- lib/hostsyn.c | 14 +- lib/http.c | 22 +- lib/http.h | 9 +- lib/multi.c | 1397 ++++++++++++++++++++++++++++++++-------------- lib/multiif.h | 14 + lib/speedcheck.c | 6 +- lib/splay.c | 406 ++++++++++++++ lib/splay.h | 50 ++ lib/strerror.c | 6 + lib/transfer.c | 45 +- lib/transfer.h | 8 +- lib/url.c | 37 +- lib/url.h | 12 + lib/urldata.h | 28 +- 21 files changed, 1609 insertions(+), 545 deletions(-) create mode 100644 lib/splay.c create mode 100644 lib/splay.h diff --git a/lib/Makefile.inc b/lib/Makefile.inc index eb3565c04..7bb875847 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -8,7 +8,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ content_encoding.c share.c http_digest.c md5.c http_negotiate.c \ http_ntlm.c inet_pton.c strtoofft.c strerror.c hostares.c hostasyn.c \ hostip4.c hostip6.c hostsyn.c hostthre.c inet_ntop.c parsedate.c \ - select.c gtls.c sslgen.c tftp.c + select.c gtls.c sslgen.c tftp.c splay.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ @@ -18,6 +18,6 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ share.h md5.h http_digest.h http_negotiate.h http_ntlm.h ca-bundle.h \ inet_pton.h strtoofft.h strerror.h inet_ntop.h curlx.h memory.h \ setup.h transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h \ - gtls.h tftp.h sockaddr.h + gtls.h tftp.h sockaddr.h splay.h diff --git a/lib/connect.c b/lib/connect.c index b1186f267..8964ec551 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -98,6 +98,7 @@ #include "memory.h" #include "select.h" #include "url.h" /* for Curl_safefree() */ +#include "multiif.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ /* The last #include file should be: */ @@ -534,6 +535,7 @@ CURLcode Curl_is_connected(struct connectdata *conn, CURLcode code = CURLE_OK; curl_socket_t sockfd = conn->sock[sockindex]; long allow = DEFAULT_CONNECT_TIMEOUT; + long allow_total = 0; long has_passed; curlassert(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); @@ -546,12 +548,12 @@ CURLcode Curl_is_connected(struct connectdata *conn, /* subtract the most strict timeout of the ones */ if(data->set.timeout && data->set.connecttimeout) { if (data->set.timeout < data->set.connecttimeout) - allow = data->set.timeout*1000; + allow_total = allow = data->set.timeout*1000; else allow = data->set.connecttimeout*1000; } else if(data->set.timeout) { - allow = data->set.timeout*1000; + allow_total = allow = data->set.timeout*1000; } else if(data->set.connecttimeout) { allow = data->set.connecttimeout*1000; @@ -564,10 +566,13 @@ CURLcode Curl_is_connected(struct connectdata *conn, } if(conn->bits.tcpconnect) { /* we are connected already! */ + Curl_expire(data, allow_total); *connected = TRUE; return CURLE_OK; } + Curl_expire(data, allow); + /* check for connect without timeout as we want to return immediately */ rc = waitconnect(sockfd, 0); @@ -818,6 +823,7 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */ return CURLE_OPERATION_TIMEOUTED; } } + Curl_expire(data, timeout_ms); /* Max time for each address */ num_addr = Curl_num_addresses(remotehost->addr); diff --git a/lib/ftp.c b/lib/ftp.c index 7296b684a..6e9443721 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -96,6 +96,7 @@ #include "select.h" #include "parsedate.h" /* for the week day and month names */ #include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "multiif.h" #if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) #include "inet_ntoa_r.h" @@ -718,27 +719,24 @@ static CURLcode ftp_state_pwd(struct connectdata *conn) } /* For the FTP "protocol connect" and "doing" phases only */ -CURLcode Curl_ftp_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +int Curl_ftp_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) { struct FTP *ftp = conn->proto.ftp; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + + if(!numsocks) + return GETSOCK_BLANK; + + socks[0] = conn->sock[FIRSTSOCKET]; if(ftp->sendleft) { /* write mode */ - FD_SET(sockfd, write_fd_set); - } - else { - /* read mode */ - FD_SET(sockfd, read_fd_set); + return GETSOCK_WRITESOCK(0); } - if((int)sockfd > *max_fdp) - *max_fdp = (int)sockfd; - - return CURLE_OK; + /* read mode */ + return GETSOCK_READSOCK(0); } /* This is called after the FTP_QUOTE state is passed. diff --git a/lib/ftp.h b/lib/ftp.h index 3c26cc38b..c85d89e4a 100644 --- a/lib/ftp.h +++ b/lib/ftp.h @@ -34,10 +34,9 @@ CURLcode Curl_GetFTPResponse(ssize_t *nread, struct connectdata *conn, int *ftpcode); CURLcode Curl_ftp_nextconnect(struct connectdata *conn); CURLcode Curl_ftp_multi_statemach(struct connectdata *conn, bool *done); -CURLcode Curl_ftp_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp); +int Curl_ftp_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); CURLcode Curl_ftp_doing(struct connectdata *conn, bool *dophase_done); #endif /* CURL_DISABLE_FTP */ diff --git a/lib/hash.c b/lib/hash.c index 96ccaa7fb..26ea9c475 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -124,8 +124,11 @@ mk_hash_element(char *key, size_t key_len, const void *p) (struct curl_hash_element *) malloc(sizeof(struct curl_hash_element)); if(he) { - char *dup = strdup(key); + char *dup = malloc(key_len); if(dup) { + /* copy the key */ + memcpy(dup, key, key_len); + he->key = dup; he->key_len = key_len; he->ptr = (void *) p; @@ -179,6 +182,23 @@ Curl_hash_add(struct curl_hash *h, char *key, size_t key_len, void *p) return NULL; /* failure */ } +/* remove the identified hash entry, returns non-zero on failure */ +int Curl_hash_delete(struct curl_hash *h, char *key, size_t key_len) +{ + struct curl_llist_element *le; + struct curl_hash_element *he; + struct curl_llist *l = FETCH_LIST(h, key, key_len); + + for (le = l->head; le; le = le->next) { + he = le->ptr; + if (hash_key_compare(he->key, he->key_len, key, key_len)) { + Curl_llist_remove(l, le, (void *) h); + return 0; + } + } + return 1; +} + void * Curl_hash_pick(struct curl_hash *h, char *key, size_t key_len) { @@ -186,9 +206,7 @@ Curl_hash_pick(struct curl_hash *h, char *key, size_t key_len) struct curl_hash_element *he; struct curl_llist *l = FETCH_LIST(h, key, key_len); - for (le = l->head; - le; - le = le->next) { + for (le = l->head; le; le = le->next) { he = le->ptr; if (hash_key_compare(he->key, he->key_len, key, key_len)) { return he->ptr; diff --git a/lib/hostares.c b/lib/hostares.c index af02d8dc8..baaa4b0fb 100644 --- a/lib/hostares.c +++ b/lib/hostares.c @@ -105,17 +105,15 @@ * Returns: CURLE_OK always! */ -CURLcode Curl_resolv_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +int Curl_resolv_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) { - int max = ares_fds(conn->data->state.areschannel, - read_fd_set, write_fd_set); - *max_fdp = max; + int max = ares_getsock(conn->data->state.areschannel, + (int *)socks, numsocks); - return CURLE_OK; + return max; } /* diff --git a/lib/hostip.h b/lib/hostip.h index 62c6721e1..f72319ba4 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -160,6 +160,14 @@ CURLcode Curl_is_resolved(struct connectdata *conn, CURLcode Curl_wait_for_resolv(struct connectdata *conn, struct Curl_dns_entry **dnsentry); + +/* Curl_resolv_getsock() is a generic function that exists in multiple versions + depending on what name resolve technology we've built to use. The function + is called from the multi_getsock() function */ +int Curl_resolv_getsock(struct connectdata *conn, + curl_socket_t *sock, + int numsocks); +#if 0 /* Curl_resolv_fdset() is a generic function that exists in multiple versions depending on what name resolve technology we've built to use. The function is called from the curl_multi_fdset() function */ @@ -167,8 +175,11 @@ CURLcode Curl_resolv_fdset(struct connectdata *conn, fd_set *read_fd_set, fd_set *write_fd_set, int *max_fdp); +#endif + /* unlock a previously resolved dns entry */ -void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns); +void Curl_resolv_unlock(struct SessionHandle *data, + struct Curl_dns_entry *dns); /* for debugging purposes only: */ void Curl_scan_cache_used(void *user, void *ptr); diff --git a/lib/hostsyn.c b/lib/hostsyn.c index a385632e9..fac78f11f 100644 --- a/lib/hostsyn.c +++ b/lib/hostsyn.c @@ -126,17 +126,15 @@ CURLcode Curl_is_resolved(struct connectdata *conn, * It is present here to keep #ifdefs out from multi.c */ -CURLcode Curl_resolv_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +int Curl_resolv_getsock(struct connectdata *conn, + curl_socket_t *sock, + int numsocks) { (void)conn; - (void)read_fd_set; - (void)write_fd_set; - (void)max_fdp; + (void)sock; + (void)numsocks; - return CURLE_OK; + return 0; /* no bits since we don't use any socks */ } #endif /* truly sync */ diff --git a/lib/http.c b/lib/http.c index a7db903fd..5a43f2478 100644 --- a/lib/http.c +++ b/lib/http.c @@ -97,6 +97,7 @@ #include "select.h" #include "parsedate.h" /* for the week day and month names */ #include "strtoofft.h" +#include "multiif.h" #define _MPRINTF_REPLACE /* use our functions only */ #include @@ -1416,26 +1417,25 @@ CURLcode Curl_https_connecting(struct connectdata *conn, bool *done) } #ifdef USE_SSLEAY -CURLcode Curl_https_proto_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +int Curl_https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) { if (conn->protocol & PROT_HTTPS) { struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + + if(!numsocks) + return GETSOCK_BLANK; if (connssl->connecting_state == ssl_connect_2_writing) { /* write mode */ - FD_SET(sockfd, write_fd_set); - if((int)sockfd > *max_fdp) - *max_fdp = (int)sockfd; + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); } else if (connssl->connecting_state == ssl_connect_2_reading) { /* read mode */ - FD_SET(sockfd, read_fd_set); - if((int)sockfd > *max_fdp) - *max_fdp = (int)sockfd; + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0); } } return CURLE_OK; diff --git a/lib/http.h b/lib/http.h index bc459f5d3..e84e28b07 100644 --- a/lib/http.h +++ b/lib/http.h @@ -8,7 +8,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2006, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -38,10 +38,9 @@ CURLcode Curl_http(struct connectdata *conn, bool *done); CURLcode Curl_http_done(struct connectdata *, CURLcode); CURLcode Curl_http_connect(struct connectdata *conn, bool *done); CURLcode Curl_https_connecting(struct connectdata *conn, bool *done); -CURLcode Curl_https_proto_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp); +int Curl_https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); /* The following functions are defined in http_chunks.c */ void Curl_httpchunk_init(struct connectdata *conn); diff --git a/lib/multi.c b/lib/multi.c index f914761bb..2eed1b2b3 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -46,6 +46,7 @@ #include "easyif.h" #include "multiif.h" #include "sendf.h" +#include "timeval.h" /* The last #include file should be: */ #include "memdebug.h" @@ -73,6 +74,18 @@ typedef enum { CURLM_STATE_LAST /* not a true state, never use this */ } CURLMstate; +/* we support 16 sockets per easy handle. Set the corresponding bit to what + action we should wait for */ +#define MAX_SOCKSPEREASYHANDLE 16 +#define GETSOCK_READABLE (0x00ff) +#define GETSOCK_WRITABLE (0xff00) + +struct socketstate { + curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; + long action; /* socket action bitmap */ + long timeout[MAX_SOCKSPEREASYHANDLE]; +}; + struct Curl_one_easy { /* first, two fields for the linked list of these */ struct Curl_one_easy *next; @@ -90,6 +103,8 @@ struct Curl_one_easy { will be deleted when this handle is removed from the multi-handle */ int msg_num; /* number of messages left in 'msg' to return */ + + struct socketstate sockstate; /* for the socket API magic */ }; #define CURL_MULTI_HANDLE 0x000bab1e @@ -111,8 +126,21 @@ struct Curl_multi { int num_msgs; /* total amount of messages in the easy handles */ + /* 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; }; /* always use this function to change state, to make debugging easier */ @@ -134,6 +162,7 @@ static void multistate(struct Curl_one_easy *easy, CURLMstate state) }; CURLMstate oldstate = easy->state; #endif + easy->state = state; #ifdef CURLDEBUG @@ -143,25 +172,166 @@ static void multistate(struct Curl_one_easy *easy, CURLMstate state) #endif } +/* + * We add one of these structs to the sockhash, and then if we add more easy + * handles for the same socket we just link them with the next/prev pointers + * from the node added to the hash. We only remove the node from the hash when + * the final easy handle/socket associated with the node is removed. + */ + +struct Curl_sh_entry { + struct Curl_sh_entry *next; + struct Curl_sh_entry *prev; + struct SessionHandle *easy; + time_t timestamp; + long inuse; +}; + +/* make sure this socket is present in the hash for this handle */ +static int 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) { + /* verify that this particular handle is in here */ + check = there; + while(check) { + if(check->easy == data) + /* it is, return fine */ + return 0; + check = check->next; + } + } + + /* not present, add it */ + check = calloc(sizeof(struct Curl_sh_entry), 1); + if(!check) + return 1; /* major failure */ + check->easy = data; + + if(there) { + /* the node for this socket is already here, now link in the struct for + the new handle */ + + check->next = there->next; /* get the previous next to point to */ + there->next = check; /* make the new next point to the new entry */ + + check->next->prev = check; /* make sure the next one points back to the + new one */ + /* check->prev = NULL; is already cleared and we have no previous + node */ + } + else { + /* make/add new hash entry */ + if(NULL == Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) + return 1; /* major failure */ + } + return 0; /* 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 SessionHandle *data) +{ + struct Curl_sh_entry *there = + Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + + while(there) { + /* this socket is in the hash, now scan the list at this point and see if + the given easy handle is in there and if so remote that singe entry */ + if(there->easy == data) { + /* match! */ + if(there->next || there->prev) { + /* it is not the only handle for this socket, so only unlink this + particular easy handle and leave the actional hash entry */ + + /* unlink */ + there->next->prev = there->prev; + there->prev->next = there->next; + free(there); + } + else { + /* This is the only easy handle for this socket, we must remove the + hash entry. (This'll end up in a call to sh_freeentry().) */ + Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); + } + break; + } + there = there->next; + } +} + +/* + * free a sockhash entry + */ +static void sh_freeentry(void *freethis) +{ + struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; + struct Curl_sh_entry *more = p->next; + + /* if there's a chain of more handles, remove that chain first */ + while(more) { + struct Curl_sh_entry *next = more->next; + free(more); + more = next; + } + + free(p); +} + +/* + * 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(97, sh_freeentry); +} + CURLM *curl_multi_init(void) { - struct Curl_multi *multi; + struct Curl_multi *multi = (void *)calloc(sizeof(struct Curl_multi), 1); - multi = (void *)malloc(sizeof(struct Curl_multi)); - - if(multi) { - memset(multi, 0, sizeof(struct Curl_multi)); - multi->type = CURL_MULTI_HANDLE; - } - else + if(!multi) return NULL; + multi->type = CURL_MULTI_HANDLE; + multi->hostcache = Curl_mk_dnscache(); if(!multi->hostcache) { /* failure, free mem and bail out */ free(multi); - multi = NULL; + return NULL; } + + multi->sockhash = sh_init(); + if(!multi->sockhash) { + /* failure, free mem and bail out */ + Curl_hash_destroy(multi->hostcache); + free(multi); + return NULL; + } + return (CURLM *) multi; } @@ -170,6 +340,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle, { struct Curl_multi *multi=(struct Curl_multi *)multi_handle; struct Curl_one_easy *easy; + int i; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) @@ -180,12 +351,12 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle, return CURLM_BAD_EASY_HANDLE; /* Now, time to add an easy handle to the multi stack */ - easy = (struct Curl_one_easy *)malloc(sizeof(struct Curl_one_easy)); + easy = (struct Curl_one_easy *)calloc(sizeof(struct Curl_one_easy), 1); if(!easy) return CURLM_OUT_OF_MEMORY; - /* clean it all first (just to be sure) */ - memset(easy, 0, sizeof(struct Curl_one_easy)); + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) + easy->sockstate.socks[i] = CURL_SOCKET_BAD; /* set the easy handle */ easy->easy_handle = easy_handle; @@ -209,6 +380,9 @@ CURLMcode curl_multi_add_handle(CURLM *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; + /* increase the node-counter */ multi->num_easy++; @@ -257,6 +431,8 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, if(easy->next) easy->next->prev = easy->prev; + easy->easy_handle->set.one_easy = NULL; /* detached */ + /* NOTE NOTE NOTE We do not touch the easy handle here! */ if (easy->msg) @@ -271,6 +447,65 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, return CURLM_BAD_EASY_HANDLE; /* twasn't found */ } +static int waitconnect_getsock(struct connectdata *conn, + curl_socket_t *sock, + int numsocks) +{ + if(!numsocks) + return GETSOCK_BLANK; + + sock[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); +} + +static int domore_getsock(struct connectdata *conn, + curl_socket_t *sock, + int numsocks) +{ + if(!numsocks) + return GETSOCK_BLANK; + + /* When in DO_MORE state, we could be either waiting for us + to connect to a remote site, or we could wait for that site + to connect to us. It makes a difference in the way: if we + connect to the site we wait for the socket to become writable, if + the site connects to us we wait for it to become readable */ + sock[0] = conn->sock[SECONDARYSOCKET]; + + return GETSOCK_WRITESOCK(0); +} + +/* 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) +{ + switch(easy->state) { + default: + return 0; + + case CURLM_STATE_WAITRESOLVE: + return Curl_resolv_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_PROTOCONNECT: + return Curl_protocol_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_DOING: + return Curl_doing_getsock(easy->easy_conn, socks, numsocks); + + 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_PERFORM: + 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) @@ -281,96 +516,421 @@ CURLMcode curl_multi_fdset(CURLM *multi_handle, 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; - *max_fd = -1; /* so far none! */ - easy=multi->easy.next; while(easy) { - switch(easy->state) { - default: - break; - case CURLM_STATE_WAITRESOLVE: - /* waiting for a resolve to complete */ - Curl_resolv_fdset(easy->easy_conn, read_fd_set, write_fd_set, - &this_max_fd); - if(this_max_fd > *max_fd) - *max_fd = this_max_fd; - break; + bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); - case CURLM_STATE_PROTOCONNECT: - Curl_protocol_fdset(easy->easy_conn, read_fd_set, write_fd_set, - &this_max_fd); - if(this_max_fd > *max_fd) - *max_fd = this_max_fd; - break; + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { + curl_socket_t s = CURL_SOCKET_BAD; - case CURLM_STATE_DOING: - Curl_doing_fdset(easy->easy_conn, read_fd_set, write_fd_set, - &this_max_fd); - if(this_max_fd > *max_fd) - *max_fd = this_max_fd; - break; - - case CURLM_STATE_WAITCONNECT: - case CURLM_STATE_DO_MORE: - { - /* when we're waiting for a connect, we wait for the socket to - become writable */ - struct connectdata *conn = easy->easy_conn; - curl_socket_t sockfd; - - if(CURLM_STATE_WAITCONNECT == easy->state) { - sockfd = conn->sock[FIRSTSOCKET]; - FD_SET(sockfd, write_fd_set); - } - else { - /* When in DO_MORE state, we could be either waiting for us - to connect to a remote site, or we could wait for that site - to connect to us. It makes a difference in the way: if we - connect to the site we wait for the socket to become writable, if - the site connects to us we wait for it to become readable */ - sockfd = conn->sock[SECONDARYSOCKET]; - FD_SET(sockfd, write_fd_set); - } - - if((int)sockfd > *max_fd) - *max_fd = (int)sockfd; + if(bitmap & GETSOCK_READSOCK(i)) { + FD_SET(sockbunch[i], read_fd_set); + s = sockbunch[i]; + } + if(bitmap & GETSOCK_WRITESOCK(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(s > this_max_fd) + this_max_fd = s; } - break; - case CURLM_STATE_PERFORM: - /* This should have a set of file descriptors for us to set. */ - /* after the transfer is done, go DONE */ - - Curl_single_fdset(easy->easy_conn, - read_fd_set, write_fd_set, - exc_fd_set, &this_max_fd); - - /* remember the maximum file descriptor */ - if(this_max_fd > *max_fd) - *max_fd = this_max_fd; - - break; } + easy = easy->next; /* check next handle */ } + *max_fd = this_max_fd; + return CURLM_OK; } -CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) +static CURLMcode multi_runsingle(struct Curl_multi *multi, + struct Curl_one_easy *easy, + int *running_handles) { - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - bool done; - CURLMcode result=CURLM_OK; struct Curl_message *msg = NULL; bool connected; bool async; bool protocol_connect; bool dophase_done; + bool done; + CURLMcode result = CURLM_OK; + + do { + if (CURLM_STATE_WAITCONNECT <= easy->state && + easy->state <= CURLM_STATE_DO && + easy->easy_handle->change.url_changed) { + char *gotourl; + Curl_posttransfer(easy->easy_handle); + + easy->result = Curl_done(&easy->easy_conn, CURLE_OK); + if(CURLE_OK == easy->result) { + gotourl = strdup(easy->easy_handle->change.url); + if(gotourl) { + easy->easy_handle->change.url_changed = FALSE; + easy->result = Curl_follow(easy->easy_handle, gotourl, FALSE); + if(CURLE_OK == easy->result) + multistate(easy, CURLM_STATE_CONNECT); + else + free(gotourl); + } + else { + easy->result = CURLE_OUT_OF_MEMORY; + multistate(easy, CURLM_STATE_COMPLETED); + break; + } + } + } + + easy->easy_handle->change.url_changed = FALSE; + + switch(easy->state) { + case CURLM_STATE_INIT: + /* init this transfer. */ + easy->result=Curl_pretransfer(easy->easy_handle); + + if(CURLE_OK == easy->result) { + /* after init, go CONNECT */ + multistate(easy, CURLM_STATE_CONNECT); + result = CURLM_CALL_MULTI_PERFORM; + + easy->easy_handle->state.used_interface = Curl_if_multi; + } + break; + + case CURLM_STATE_CONNECT: + /* Connect. We get a connection identifier filled in. */ + Curl_pgrsTime(easy->easy_handle, TIMER_STARTSINGLE); + easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn, + &async, &protocol_connect); + + if(CURLE_OK == easy->result) { + 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 + DO! */ + result = CURLM_CALL_MULTI_PERFORM; + + if(protocol_connect) + multistate(easy, CURLM_STATE_DO); + else + 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_is_resolved(easy->easy_conn, &dns); + + 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 { + /* FIX: what if protocol_connect is TRUE here?! */ + multistate(easy, CURLM_STATE_WAITCONNECT); + } + } + + if(CURLE_OK != easy->result) { + /* failure detected */ + Curl_disconnect(easy->easy_conn); /* disconnect properly */ + easy->easy_conn = NULL; /* no more connection */ + break; + } + } + break; + + case CURLM_STATE_WAITCONNECT: + /* awaiting a completion of an asynch connect */ + easy->result = Curl_is_connected(easy->easy_conn, FIRSTSOCKET, + &connected); + if(connected) + easy->result = Curl_protocol_connect(easy->easy_conn, + &protocol_connect); + + if(CURLE_OK != easy->result) { + /* failure detected */ + Curl_disconnect(easy->easy_conn); /* close the connection */ + easy->easy_conn = NULL; /* no more connection */ + 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. */ + multistate(easy, CURLM_STATE_PROTOCONNECT); + } + else { + /* after the connect has completed, go DO */ + multistate(easy, 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(protocol_connect) { + /* after the connect has completed, go DO */ + multistate(easy, CURLM_STATE_DO); + result = CURLM_CALL_MULTI_PERFORM; + } + else if(easy->result) { + /* failure detected */ + Curl_posttransfer(easy->easy_handle); + Curl_done(&easy->easy_conn, easy->result); + Curl_disconnect(easy->easy_conn); /* close the connection */ + easy->easy_conn = NULL; /* no more connection */ + } + break; + + case CURLM_STATE_DO: + if(easy->easy_handle->set.connect_only) { + /* keep connection open for application to use the socket */ + easy->easy_conn->bits.close = FALSE; + multistate(easy, CURLM_STATE_DONE); + easy->result = CURLE_OK; + result = CURLM_OK; + } + else { + /* Perform the protocol's DO action */ + easy->result = Curl_do(&easy->easy_conn, &dophase_done); + + if(CURLE_OK == easy->result) { + + if(!dophase_done) { + /* DO was not completed in one function call, we must continue + DOING... */ + multistate(easy, CURLM_STATE_DOING); + result = CURLM_OK; + } + + /* after DO, go PERFORM... 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 PERFORM */ + easy->result = Curl_readwrite_init(easy->easy_conn); + if(CURLE_OK == easy->result) { + multistate(easy, CURLM_STATE_PERFORM); + result = CURLM_CALL_MULTI_PERFORM; + } + } + } + else { + /* failure detected */ + Curl_posttransfer(easy->easy_handle); + Curl_done(&easy->easy_conn, easy->result); + Curl_disconnect(easy->easy_conn); /* close the connection */ + easy->easy_conn = NULL; /* no more connection */ + } + } + 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 PERFORM... or DO_MORE */ + if(easy->easy_conn->bits.do_more) { + /* we're supposed to do more, but we need to sit down, relax + and wait a little while first */ + multistate(easy, CURLM_STATE_DO_MORE); + result = CURLM_OK; + } + else { + /* we're done with the DO, now PERFORM */ + easy->result = Curl_readwrite_init(easy->easy_conn); + if(CURLE_OK == easy->result) { + multistate(easy, CURLM_STATE_PERFORM); + result = CURLM_CALL_MULTI_PERFORM; + } + } + } /* dophase_done */ + } + else { + /* failure detected */ + Curl_posttransfer(easy->easy_handle); + Curl_done(&easy->easy_conn, easy->result); + Curl_disconnect(easy->easy_conn); /* close the connection */ + easy->easy_conn = NULL; /* no more connection */ + } + break; + + case CURLM_STATE_DO_MORE: + /* Ready to do more? */ + easy->result = Curl_is_connected(easy->easy_conn, SECONDARYSOCKET, + &connected); + if(connected) { + /* + * When we are connected, DO MORE and then go PERFORM + */ + easy->result = Curl_do_more(easy->easy_conn); + + if(CURLE_OK == easy->result) + easy->result = Curl_readwrite_init(easy->easy_conn); + + if(CURLE_OK == easy->result) { + multistate(easy, CURLM_STATE_PERFORM); + result = CURLM_CALL_MULTI_PERFORM; + } + } + break; + + case CURLM_STATE_PERFORM: + /* read/write data if it is ready to do so */ + easy->result = Curl_readwrite(easy->easy_conn, &done); + + if(easy->result) { + /* The transfer phase returned error, we mark the connection to get + * closed to prevent being re-used. This is becasue we can't + * possibly know if the connection is in a good shape or not now. */ + easy->easy_conn->bits.close = TRUE; + + if(CURL_SOCKET_BAD != easy->easy_conn->sock[SECONDARYSOCKET]) { + /* if we failed anywhere, we must clean up the secondary socket if + it was used */ + sclose(easy->easy_conn->sock[SECONDARYSOCKET]); + easy->easy_conn->sock[SECONDARYSOCKET]=-1; + } + Curl_posttransfer(easy->easy_handle); + Curl_done(&easy->easy_conn, easy->result); + } + + else if(TRUE == done) { + char *newurl; + bool retry = Curl_retry_request(easy->easy_conn, &newurl); + + /* call this even if the readwrite function returned error */ + Curl_posttransfer(easy->easy_handle); + + /* When we follow redirects, must to go back to the CONNECT state */ + if(easy->easy_conn->newurl || retry) { + if(!retry) { + /* if the URL is a follow-location and not just a retried request + then figure out the URL here */ + newurl = easy->easy_conn->newurl; + easy->easy_conn->newurl = NULL; + } + easy->result = Curl_done(&easy->easy_conn, CURLE_OK); + if(easy->result == CURLE_OK) + easy->result = Curl_follow(easy->easy_handle, newurl, retry); + if(CURLE_OK == easy->result) { + multistate(easy, CURLM_STATE_CONNECT); + result = CURLM_CALL_MULTI_PERFORM; + } + else + /* Since we "took it", we are in charge of freeing this on + failure */ + free(newurl); + } + else { + /* after the transfer is done, go DONE */ + multistate(easy, CURLM_STATE_DONE); + result = CURLM_CALL_MULTI_PERFORM; + } + } + break; + case CURLM_STATE_DONE: + /* post-transfer command */ + easy->result = Curl_done(&easy->easy_conn, CURLE_OK); + + /* 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. */ + break; + default: + return CURLM_INTERNAL_ERROR; + } + + if(CURLM_STATE_COMPLETED != easy->state) { + if(CURLE_OK != easy->result) { + /* + * If an error was returned, and we aren't in completed state now, + * then we go to completed and consider this transfer aborted. */ + multistate(easy, CURLM_STATE_COMPLETED); + } + else + /* this one still lives! */ + (*running_handles)++; + } + + } while (easy->easy_handle->change.url_changed); + + if ((CURLM_STATE_COMPLETED == easy->state) && !easy->msg) { + /* clear out the usage of the shared DNS cache */ + easy->easy_handle->hostcache = NULL; + + /* now add a node to the Curl_message linked list with this info */ + msg = (struct Curl_message *)malloc(sizeof(struct Curl_message)); + + if(!msg) + return CURLM_OUT_OF_MEMORY; + + msg->extmsg.msg = CURLMSG_DONE; + msg->extmsg.easy_handle = easy->easy_handle; + msg->extmsg.data.result = easy->result; + msg->next=NULL; + + easy->msg = msg; + easy->msg_num = 1; /* there is one unread message here */ + + multi->num_msgs++; /* increase message counter */ + } + + 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; *running_handles = 0; /* bump this once for every living handle */ @@ -379,362 +939,26 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) easy=multi->easy.next; while(easy) { - do { - if (CURLM_STATE_WAITCONNECT <= easy->state && - easy->state <= CURLM_STATE_DO && - easy->easy_handle->change.url_changed) { - char *gotourl; - Curl_posttransfer(easy->easy_handle); + CURLMcode result = multi_runsingle(multi, easy, running_handles); + if(result) + returncode = result; - easy->result = Curl_done(&easy->easy_conn, CURLE_OK); - if(CURLE_OK == easy->result) { - gotourl = strdup(easy->easy_handle->change.url); - if(gotourl) { - easy->easy_handle->change.url_changed = FALSE; - easy->result = Curl_follow(easy->easy_handle, gotourl, FALSE); - if(CURLE_OK == easy->result) - multistate(easy, CURLM_STATE_CONNECT); - else - free(gotourl); - } - else { - easy->result = CURLE_OUT_OF_MEMORY; - multistate(easy, CURLM_STATE_COMPLETED); - break; - } - } - } - - easy->easy_handle->change.url_changed = FALSE; - - switch(easy->state) { - case CURLM_STATE_INIT: - /* init this transfer. */ - easy->result=Curl_pretransfer(easy->easy_handle); - - if(CURLE_OK == easy->result) { - /* after init, go CONNECT */ - multistate(easy, CURLM_STATE_CONNECT); - result = CURLM_CALL_MULTI_PERFORM; - - easy->easy_handle->state.used_interface = Curl_if_multi; - } - break; - - case CURLM_STATE_CONNECT: - /* Connect. We get a connection identifier filled in. */ - Curl_pgrsTime(easy->easy_handle, TIMER_STARTSINGLE); - easy->result = Curl_connect(easy->easy_handle, &easy->easy_conn, - &async, &protocol_connect); - - if(CURLE_OK == easy->result) { - 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 - DO! */ - result = CURLM_CALL_MULTI_PERFORM; - - if(protocol_connect) - multistate(easy, CURLM_STATE_DO); - else - 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_is_resolved(easy->easy_conn, &dns); - - 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 { - /* FIX: what if protocol_connect is TRUE here?! */ - multistate(easy, CURLM_STATE_WAITCONNECT); - } - } - - if(CURLE_OK != easy->result) { - /* failure detected */ - Curl_disconnect(easy->easy_conn); /* disconnect properly */ - easy->easy_conn = NULL; /* no more connection */ - break; - } - } - break; - - case CURLM_STATE_WAITCONNECT: - /* awaiting a completion of an asynch connect */ - easy->result = Curl_is_connected(easy->easy_conn, FIRSTSOCKET, - &connected); - if(connected) - easy->result = Curl_protocol_connect(easy->easy_conn, - &protocol_connect); - - if(CURLE_OK != easy->result) { - /* failure detected */ - Curl_disconnect(easy->easy_conn); /* close the connection */ - easy->easy_conn = NULL; /* no more connection */ - 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. */ - multistate(easy, CURLM_STATE_PROTOCONNECT); - } - else { - /* after the connect has completed, go DO */ - multistate(easy, 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(protocol_connect) { - /* after the connect has completed, go DO */ - multistate(easy, CURLM_STATE_DO); - result = CURLM_CALL_MULTI_PERFORM; - } - else if(easy->result) { - /* failure detected */ - Curl_posttransfer(easy->easy_handle); - Curl_done(&easy->easy_conn, easy->result); - Curl_disconnect(easy->easy_conn); /* close the connection */ - easy->easy_conn = NULL; /* no more connection */ - } - break; - - case CURLM_STATE_DO: - if(easy->easy_handle->set.connect_only) { - /* keep connection open for application to use the socket */ - easy->easy_conn->bits.close = FALSE; - multistate(easy, CURLM_STATE_DONE); - easy->result = CURLE_OK; - result = CURLM_OK; - } - else { - /* Perform the protocol's DO action */ - easy->result = Curl_do(&easy->easy_conn, &dophase_done); - - if(CURLE_OK == easy->result) { - - if(!dophase_done) { - /* DO was not completed in one function call, we must continue - DOING... */ - multistate(easy, CURLM_STATE_DOING); - result = CURLM_OK; - } - - /* after DO, go PERFORM... 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 PERFORM */ - easy->result = Curl_readwrite_init(easy->easy_conn); - if(CURLE_OK == easy->result) { - multistate(easy, CURLM_STATE_PERFORM); - result = CURLM_CALL_MULTI_PERFORM; - } - } - } - else { - /* failure detected */ - Curl_posttransfer(easy->easy_handle); - Curl_done(&easy->easy_conn, easy->result); - Curl_disconnect(easy->easy_conn); /* close the connection */ - easy->easy_conn = NULL; /* no more connection */ - } - } - 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 PERFORM... or DO_MORE */ - if(easy->easy_conn->bits.do_more) { - /* we're supposed to do more, but we need to sit down, relax - and wait a little while first */ - multistate(easy, CURLM_STATE_DO_MORE); - result = CURLM_OK; - } - else { - /* we're done with the DO, now PERFORM */ - easy->result = Curl_readwrite_init(easy->easy_conn); - if(CURLE_OK == easy->result) { - multistate(easy, CURLM_STATE_PERFORM); - result = CURLM_CALL_MULTI_PERFORM; - } - } - } /* dophase_done */ - } - else { - /* failure detected */ - Curl_posttransfer(easy->easy_handle); - Curl_done(&easy->easy_conn, easy->result); - Curl_disconnect(easy->easy_conn); /* close the connection */ - easy->easy_conn = NULL; /* no more connection */ - } - break; - - case CURLM_STATE_DO_MORE: - /* Ready to do more? */ - easy->result = Curl_is_connected(easy->easy_conn, SECONDARYSOCKET, - &connected); - if(connected) { - /* - * When we are connected, DO MORE and then go PERFORM - */ - easy->result = Curl_do_more(easy->easy_conn); - - if(CURLE_OK == easy->result) - easy->result = Curl_readwrite_init(easy->easy_conn); - - if(CURLE_OK == easy->result) { - multistate(easy, CURLM_STATE_PERFORM); - result = CURLM_CALL_MULTI_PERFORM; - } - } - break; - - case CURLM_STATE_PERFORM: - /* read/write data if it is ready to do so */ - easy->result = Curl_readwrite(easy->easy_conn, &done); - - if(easy->result) { - /* The transfer phase returned error, we mark the connection to get - * closed to prevent being re-used. This is becasue we can't - * possibly know if the connection is in a good shape or not now. */ - easy->easy_conn->bits.close = TRUE; - - if(CURL_SOCKET_BAD != easy->easy_conn->sock[SECONDARYSOCKET]) { - /* if we failed anywhere, we must clean up the secondary socket if - it was used */ - sclose(easy->easy_conn->sock[SECONDARYSOCKET]); - easy->easy_conn->sock[SECONDARYSOCKET]=-1; - } - Curl_posttransfer(easy->easy_handle); - Curl_done(&easy->easy_conn, easy->result); - } - - else if(TRUE == done) { - char *newurl; - bool retry = Curl_retry_request(easy->easy_conn, &newurl); - - /* call this even if the readwrite function returned error */ - Curl_posttransfer(easy->easy_handle); - - /* When we follow redirects, must to go back to the CONNECT state */ - if(easy->easy_conn->newurl || retry) { - if(!retry) { - /* if the URL is a follow-location and not just a retried request - then figure out the URL here */ - newurl = easy->easy_conn->newurl; - easy->easy_conn->newurl = NULL; - } - easy->result = Curl_done(&easy->easy_conn, CURLE_OK); - if(easy->result == CURLE_OK) - easy->result = Curl_follow(easy->easy_handle, newurl, retry); - if(CURLE_OK == easy->result) { - multistate(easy, CURLM_STATE_CONNECT); - result = CURLM_CALL_MULTI_PERFORM; - } - else - /* Since we "took it", we are in charge of freeing this on - failure */ - free(newurl); - } - else { - /* after the transfer is done, go DONE */ - multistate(easy, CURLM_STATE_DONE); - result = CURLM_CALL_MULTI_PERFORM; - } - } - break; - case CURLM_STATE_DONE: - /* post-transfer command */ - easy->result = Curl_done(&easy->easy_conn, CURLE_OK); - - /* 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. */ - break; - default: - return CURLM_INTERNAL_ERROR; - } - - if(CURLM_STATE_COMPLETED != easy->state) { - if(CURLE_OK != easy->result) { - /* - * If an error was returned, and we aren't in completed state now, - * then we go to completed and consider this transfer aborted. */ - multistate(easy, CURLM_STATE_COMPLETED); - } - else - /* this one still lives! */ - (*running_handles)++; - } - - } while (easy->easy_handle->change.url_changed); - - if ((CURLM_STATE_COMPLETED == easy->state) && !easy->msg) { - /* clear out the usage of the shared DNS cache */ - easy->easy_handle->hostcache = NULL; - - /* now add a node to the Curl_message linked list with this info */ - msg = (struct Curl_message *)malloc(sizeof(struct Curl_message)); - - if(!msg) - return CURLM_OUT_OF_MEMORY; - - msg->extmsg.msg = CURLMSG_DONE; - msg->extmsg.easy_handle = easy->easy_handle; - msg->extmsg.data.result = easy->result; - msg->next=NULL; - - easy->msg = msg; - easy->msg_num = 1; /* there is one unread message here */ - - multi->num_msgs++; /* increase message counter */ - } easy = easy->next; /* operate on next handle */ } - return result; + /* + * Simply remove all expired timers from the splay since handles are dealt + * with unconditionally by this function and curl_multi_timeout() requires + * that already passed/handled expire times are removed from the splay. + */ + do { + struct timeval now = Curl_tvnow(); + int key = now.tv_sec; /* drop the usec part */ + + multi->timetree = Curl_splaygetbest(key, multi->timetree, &t); + } while(t); + + return returncode; } /* This is called when an easy handle is cleanup'ed that is part of a multi @@ -753,6 +977,7 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle) if(GOOD_MULTI_HANDLE(multi)) { multi->type = 0; /* not good anymore */ Curl_hash_destroy(multi->hostcache); + Curl_hash_destroy(multi->sockhash); /* remove all easy handles */ easy = multi->easy.next; @@ -807,3 +1032,311 @@ CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue) else return NULL; } + +/* + * Check what sockets we deal with and their "action state" and if we have a + * difference from last time we call the callback accordingly. + */ +static void singlesocket(struct Curl_multi *multi, + struct Curl_one_easy *easy) +{ + struct socketstate current; + int i; + + memset(¤t, 0, sizeof(current)); + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) + current.socks[i] = CURL_SOCKET_BAD; + + /* first fill in the 'current' struct with the state as it is now */ + current.action = multi_getsock(easy, current.socks, MAX_SOCKSPEREASYHANDLE); + + /* when filled in, we compare with the previous round's state */ + if(memcmp(¤t, &easy->sockstate, sizeof(struct socketstate))) { + /* difference, call the callback once for every socket change ! */ + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { + int action; + curl_socket_t s = current.socks[i]; + + /* Ok, this approach is probably too naive and simple-minded but + it might work for a start */ + + if((easy->sockstate.socks[i] == CURL_SOCKET_BAD) && + (s == CURL_SOCKET_BAD)) { + /* no socket now and there was no socket before */ + break; + } + + if(s == CURL_SOCKET_BAD) { + /* socket is removed */ + action = CURL_POLL_REMOVE; + s = easy->sockstate.socks[i]; /* this is the removed socket */ + } + else { + if(easy->sockstate.socks[i] == s) { + /* still the same socket, but are we waiting for the same actions? */ + unsigned int curr; + unsigned int prev; + + /* the current read/write bits for this particular socket */ + curr = current.action & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i)); + + /* the previous read/write bits for this particular socket */ + prev = easy->sockstate.action & + (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i)); + + if(curr == prev) + continue; + } + + action = (current.action & GETSOCK_READSOCK(i)?CURL_POLL_IN:0) | + (current.action & GETSOCK_WRITESOCK(i)?CURL_POLL_OUT:0); + } + + /* call the callback with this new info */ + if(multi->socket_cb) { + multi->socket_cb(easy->easy_handle, + s, + action, + multi->socket_userp); + } + + /* Update the sockhash accordingly */ + if(action == CURL_POLL_REMOVE) + /* remove from hash for this easy handle */ + sh_delentry(multi->sockhash, s, easy->easy_handle); + else + /* make sure this socket is present in the hash for this handle */ + sh_addentry(multi->sockhash, s, easy->easy_handle); + } + /* copy the current state to the storage area */ + memcpy(&easy->sockstate, ¤t, sizeof(struct socketstate)); + } + else { + /* identical, nothing new happened so we don't do any callbacks */ + } + +} + +static CURLMcode multi_socket(struct Curl_multi *multi, + bool checkall, + curl_socket_t s) +{ + CURLMcode result = CURLM_OK; + int running_handles; + struct SessionHandle *data = NULL; + struct Curl_tree *t; + + if(checkall) { + struct Curl_one_easy *easyp; + 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) { + singlesocket(multi, easyp); + easyp = easyp->next; + } + + 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, major problemo! */ + return CURLM_BAD_SOCKET; /* better return code? */ + + /* Now, there is potentially a chain of easy handles in this hash + entry struct and we need to deal with all of them */ + + do { + data = entry->easy; + + result = multi_runsingle(multi, data->set.one_easy, &running_handles); + + if(result == CURLM_OK) + /* get the socket(s) and check if the state has been changed since + last */ + singlesocket(multi, data->set.one_easy); + + entry = entry->next; + + } while(entry); + + return result; + } + + /* + * 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 { + int key; + struct timeval now; + + /* the first loop lap 'data' can be NULL */ + if(data) { + result = multi_runsingle(multi, data->set.one_easy, &running_handles); + + if(result == CURLM_OK) + /* get the socket(s) and check if the state has been changed since + last */ + singlesocket(multi, data->set.one_easy); + } + + /* Check if there's one (more) expired timer to deal with! This function + extracts a matching node if there is one */ + + now = Curl_tvnow(); + key = now.tv_sec; /* drop the usec part */ + + multi->timetree = Curl_splaygetbest(key, multi->timetree, &t); + if(t) + data = t->payload; + + } while(t); + + return result; +} + +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; + default: + res = CURLM_UNKNOWN_OPTION; + } + va_end(param); + return res; +} + + +CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s) +{ +#if 0 + printf("multi_socket(%d)\n", (int)s); +#endif + + return multi_socket((struct Curl_multi *)multi_handle, FALSE, s); +} + +CURLMcode curl_multi_socket_all(CURLM *multi_handle) + +{ + return multi_socket((struct Curl_multi *)multi_handle, + TRUE, CURL_SOCKET_BAD); +} + +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; + + 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(0, multi->timetree); + + /* At least currently, the splay key is a time_t for the expire time */ + *timeout_ms = (multi->timetree->key - now.tv_sec) * 1000 - + now.tv_usec/1000; + if(*timeout_ms < 0) + /* 0 means immediately */ + *timeout_ms = 0; + } + else + *timeout_ms = -1; + + return CURLM_OK; +} + +/* 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() */ +void Curl_expire(struct SessionHandle *data, long milli) +{ + struct Curl_multi *multi = data->multi; + struct timeval *nowp = &data->state.expiretime; + + /* 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) { + /* Since this is an cleared time, we must remove the previous entry from + the splay tree */ + multi->timetree = Curl_splayremovebyaddr(multi->timetree, + &data->state.timenode); + infof(data, "Expire cleared\n"); + } + nowp->tv_sec = nowp->tv_usec = 0; + } + 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) { + /* compare if the new time is earlier, and only set it if so */ + 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 */ + multi->timetree = Curl_splayremovebyaddr(multi->timetree, + &data->state.timenode); + } + + *nowp = set; + infof(data, "Expire at %ld / %ld (%ldms)\n", + (long)nowp->tv_sec, (long)nowp->tv_usec, milli); + + data->state.timenode.payload = data; + multi->timetree = Curl_splayinsert((int)nowp->tv_sec, + multi->timetree, + &data->state.timenode); + } +#if 0 + Curl_splayprint(multi->timetree, 0, TRUE); +#endif +} + diff --git a/lib/multiif.h b/lib/multiif.h index cca55a502..e24f36744 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -26,5 +26,19 @@ /* * Prototypes for library-wide functions provided by multi.c */ +void Curl_expire(struct SessionHandle *data, long milli); + void Curl_multi_rmeasy(void *multi, CURL *data); + +/* the write bits start at bit 16 for the *getsock() bitmap */ +#define GETSOCK_WRITEBITSTART 16 + +#define GETSOCK_BLANK 0 /* no bits set */ + +/* set the bit for the given sock number to make the bitmap for writable */ +#define GETSOCK_WRITESOCK(x) (1 << (GETSOCK_WRITEBITSTART + (x))) + +/* set the bit for the given sock number to make the bitmap for readable */ +#define GETSOCK_READSOCK(x) (1 << (x)) + #endif /* __MULTIIF_H */ diff --git a/lib/speedcheck.c b/lib/speedcheck.c index 273cacb5a..5ee009ee2 100644 --- a/lib/speedcheck.c +++ b/lib/speedcheck.c @@ -29,6 +29,7 @@ #include #include "urldata.h" #include "sendf.h" +#include "multiif.h" #include "speedcheck.h" void Curl_speedinit(struct SessionHandle *data) @@ -43,13 +44,13 @@ CURLcode Curl_speedcheck(struct SessionHandle *data, data->set.low_speed_time && (Curl_tvlong(data->state.keeps_speed) != 0) && (data->progress.current_speed < data->set.low_speed_limit)) { + long howlong = Curl_tvdiff(now, data->state.keeps_speed); /* We are now below the "low speed limit". If we are below it for "low speed time" seconds we consider that enough reason to abort the download. */ - if( (Curl_tvdiff(now, data->state.keeps_speed)/1000) > - data->set.low_speed_time) { + if( (howlong/1000) > data->set.low_speed_time) { /* we have been this slow for long enough, now die */ failf(data, "Operation too slow. " @@ -58,6 +59,7 @@ CURLcode Curl_speedcheck(struct SessionHandle *data, data->set.low_speed_time); return CURLE_OPERATION_TIMEOUTED; } + Curl_expire(data, howlong); } else { /* we keep up the required speed all right */ diff --git a/lib/splay.c b/lib/splay.c new file mode 100644 index 000000000..0ea078b6c --- /dev/null +++ b/lib/splay.c @@ -0,0 +1,406 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1997 - 2006, Daniel Stenberg, , 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. + * + * $Id$ + ***************************************************************************/ + +#include +#include + +#include "splay.h" + +#define compare(i,j) ((i)-(j)) + +/* Set this to a key value that will *NEVER* appear otherwise */ +#define KEY_NOTUSED -1 + +/* + * Splay using the key i (which may or may not be in the tree.) The starting + * root is t. + */ +struct Curl_tree *Curl_splay(int i, struct Curl_tree *t) +{ + struct Curl_tree N, *l, *r, *y; + int comp; + + if (t == NULL) + return t; + N.smaller = N.larger = NULL; + l = r = &N; + + for (;;) { + comp = compare(i, t->key); + if (comp < 0) { + if (t->smaller == NULL) + break; + if (compare(i, t->smaller->key) < 0) { + y = t->smaller; /* rotate smaller */ + t->smaller = y->larger; + y->larger = t; + t = y; + if (t->smaller == NULL) + break; + } + r->smaller = t; /* link smaller */ + r = t; + t = t->smaller; + } + else if (comp > 0) { + if (t->larger == NULL) + break; + if (compare(i, t->larger->key) > 0) { + y = t->larger; /* rotate larger */ + t->larger = y->smaller; + y->smaller = t; + t = y; + if (t->larger == NULL) + break; + } + l->larger = t; /* link larger */ + l = t; + t = t->larger; + } + else { + break; + } + } + l->larger = r->smaller = NULL; + + l->larger = t->smaller; /* assemble */ + r->smaller = t->larger; + t->smaller = N.larger; + t->larger = N.smaller; + + return t; +} + +/* Insert key i into the tree t. Return a pointer to the resulting tree or + NULL if something went wrong. */ +struct Curl_tree *Curl_splayinsert(int i, struct Curl_tree *t, + struct Curl_tree *area) +{ + if (area == NULL) + return t; + + if (t != NULL) { + t = Curl_splay(i,t); + if (compare(i, t->key)==0) { + /* it already exists one of this size */ + + area->same = t; + area->key = i; + area->smaller = t->smaller; + area->larger = t->larger; + + t->smaller = area; + t->key = KEY_NOTUSED; + + return area; /* new root node */ + } + } + + if (t == NULL) { + area->smaller = area->larger = NULL; + } + else if (compare(i, t->key) < 0) { + area->smaller = t->smaller; + area->larger = t; + t->smaller = NULL; + + } + else { + area->larger = t->larger; + area->smaller = t; + t->larger = NULL; + } + area->key = i; + + area->same = NULL; /* no identical node (yet) */ + return area; +} + +/* Deletes 'i' from the tree if it's there (with an exact match). Returns a + pointer to the resulting tree. */ +struct Curl_tree *Curl_splayremove(int i, struct Curl_tree *t, + struct Curl_tree **removed) +{ + struct Curl_tree *x; + + if (t==NULL) + return NULL; + + t = Curl_splay(i,t); + if (compare(i, t->key) == 0) { /* found it */ + + /* FIRST! Check if there is a list with identical sizes */ + if((x = t->same)) { + /* there is, pick one from the list */ + + /* 'x' is the new root node */ + + x->key = t->key; + x->larger = t->larger; + x->smaller = t->smaller; + + *removed = t; + return x; /* new root */ + } + + if (t->smaller == NULL) { + x = t->larger; + } + else { + x = Curl_splay(i, t->smaller); + x->larger = t->larger; + } + *removed = t; + + return x; + } + else { + *removed = NULL; /* no match */ + return t; /* It wasn't there */ + } +} + +/* Finds and deletes the best-fit node from the tree. Return a pointer to the + resulting tree. best-fit means the node with the given or lower number */ +struct Curl_tree *Curl_splaygetbest(int i, struct Curl_tree *t, + struct Curl_tree **removed) +{ + struct Curl_tree *x; + + if (!t) { + *removed = NULL; /* none removed since there was no root */ + return NULL; + } + + t = Curl_splay(i,t); + if(compare(i, t->key) < 0) { + /* too big node, try the smaller chain */ + if(t->smaller) + t=Curl_splay(t->smaller->key, t); + else { + /* fail */ + *removed = NULL; + return t; + } + } + + if (compare(i, t->key) >= 0) { /* found it */ + /* FIRST! Check if there is a list with identical sizes */ + x = t->same; + if(x) { + /* there is, pick one from the list */ + + /* 'x' is the new root node */ + + x->key = t->key; + x->larger = t->larger; + x->smaller = t->smaller; + + *removed = t; + return x; /* new root */ + } + + if (t->smaller == NULL) { + x = t->larger; + } + else { + x = Curl_splay(i, t->smaller); + x->larger = t->larger; + } + *removed = t; + + return x; + } + else { + *removed = NULL; /* no match */ + return t; /* It wasn't there */ + } +} + + +/* Deletes the node we point out from the tree if it's there. Return a pointer + to the resulting tree. */ +struct Curl_tree *Curl_splayremovebyaddr(struct Curl_tree *t, + struct Curl_tree *remove) +{ + struct Curl_tree *x; + + if (!t || !remove) + return NULL; + + if(KEY_NOTUSED == remove->key) { + /* just unlink ourselves nice and quickly: */ + remove->smaller->same = remove->same; + if(remove->same) + remove->same->smaller = remove->smaller; + /* voila, we're done! */ + return t; + } + + t = Curl_splay(remove->key, t); + + /* Check if there is a list with identical sizes */ + + x = t->same; + if(x) { + /* 'x' is the new root node */ + + x->key = t->key; + x->larger = t->larger; + x->smaller = t->smaller; + + return x; /* new root */ + } + + /* Remove the actualy root node: */ + if (t->smaller == NULL) + x = t->larger; + else { + x = Curl_splay(remove->key, t->smaller); + x->larger = t->larger; + } + + return x; +} + +#ifdef CURLDEBUG + +int Curl_splayprint(struct Curl_tree * t, int d, char output) +{ + int distance=0; + struct Curl_tree *node; + int i; + if (t == NULL) + return 0; + distance += Curl_splayprint(t->larger, d+1, output); + for (i=0; ikey, i); + } + + for(node = t->same; node; node = node->same) { + distance += i; /* this has the same "virtual" distance */ + + if(output) + printf(" [+]"); + } + if(output) + puts(""); + + distance += i; + + distance += Curl_splayprint(t->smaller, d+1, output); + + return distance; +} +#endif + +#ifdef TEST_SPLAY + +/*#define TEST2 */ +#define MAX 50 +#define OUTPUT 0 /* 1 enables, 0 disables */ + +/* A sample use of these functions. Start with the empty tree, insert some + stuff into it, and then delete it */ +int main(int argc, char **argv) +{ + struct Curl_tree *root, *t; + void *ptrs[MAX]; + + long sizes[]={ + 50, 60, 50, 100, 60, 200, 120, 300, 400, 200, 256, 122, 60, 120, 200, 300, + 220, 80, 90, 50, 100, 60, 200, 120, 300, 400, 200, 256, 122, 60, 120, 200, + 300, 220, 80, 90, 50, 100, 60, 200, 120, 300, 400, 200, 256, 122, 60, 120, + 200, 300, 220, 80, 90}; + int i; + root = NULL; /* the empty tree */ + + for (i = 0; i < MAX; i++) { + ptrs[i] = t = (struct Curl_tree *)malloc(sizeof(struct Curl_tree)); + if(!t) { + puts("out of memory!"); + return 0; + } +#ifdef TEST2 + root = Curl_splayinsert(sizes[i], root, t); +#else + root = Curl_splayinsert((541*i)&1023, root, t); +#endif + } + +#if 0 + puts("Result:"); + printtree(root, 0, 1); +#endif + +#if 1 + for (i=0; root; i+=30) { + Curl_splayprint(root, 0, 1); + do { + root = Curl_splaygetbest(i, root, &t); + if(t) + printf("bestfit %d became %d\n", i, t->key); + else + printf("bestfit %d failed!\n", i); + } while(t && root); + } +#endif +#if 0 + for (i = 0; i < MAX; i++) { + printf("remove pointer %d size %d\n", i, sizes[i]); + root = removebyaddr(root, (struct Curl_tree *)ptrs[i]); + Curl_splayprint(root, 0, 1); + } +#endif + +#if 0 +#ifdef WEIGHT + for (i = -1; i<=root->weight; i++) { + t = find_rank(i, root); + if (t == NULL) { + printf("could not find a node of rank %d.\n", i); + } else { + printf("%d is of rank %d\n", t->key, i); + } + } +#endif +#endif + +#if 0 +#ifdef TEST2 + for (i = 0; i < MAX; i++) { + printf("remove size %d\n", sizes[i]); + root = Curl_splayremove(sizes[i], root, &t); + free(t); + Curl_splayprint(root, 0, 1); + } +#endif +#endif + return 0; +} + +#endif /* TEST_SPLAY */ diff --git a/lib/splay.h b/lib/splay.h new file mode 100644 index 000000000..7a6000c18 --- /dev/null +++ b/lib/splay.h @@ -0,0 +1,50 @@ +#ifndef __SPLAY_H +#define __SPLAY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1997 - 2006, Daniel Stenberg, , 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. + * + * $Id$ + ***************************************************************************/ + +struct Curl_tree { + struct Curl_tree *smaller; /* smaller node */ + struct Curl_tree *larger; /* larger node */ + struct Curl_tree *same; /* points to a node with identical key */ + int key; /* the "sort" key */ + void *payload; /* data the splay code doesn't care about */ +}; + +struct Curl_tree *Curl_splay(int i, struct Curl_tree *t); +struct Curl_tree *Curl_splayinsert(int key, struct Curl_tree *t, + struct Curl_tree *new); +struct Curl_tree *Curl_splayremove(int key, struct Curl_tree *t, + struct Curl_tree **removed); +struct Curl_tree *Curl_splaygetbest(int key, struct Curl_tree *t, + struct Curl_tree **removed); +struct Curl_tree *Curl_splayremovebyaddr(struct Curl_tree *t, + struct Curl_tree *remove); + +#ifdef CURLDEBUG +int Curl_splayprint(struct Curl_tree * t, int d, char output); +#else +#define Curl_splayprint(x,y,z) +#endif + +#endif diff --git a/lib/strerror.c b/lib/strerror.c index df3bac084..b035e6772 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -329,6 +329,12 @@ curl_multi_strerror(CURLMcode error) case CURLM_INTERNAL_ERROR: return "internal error"; + case CURLM_BAD_SOCKET: + return "invalid socket argument"; + + case CURLM_UNKNOWN_OPTION: + return "unknown option"; + case CURLM_LAST: break; } diff --git a/lib/transfer.c b/lib/transfer.c index 6bd2dc904..6e4fe4c71 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -101,6 +101,7 @@ #include "share.h" #include "memory.h" #include "select.h" +#include "multiif.h" #include "easyif.h" /* for Curl_convert_to_network prototype */ #define _MPRINTF_REPLACE /* use our functions only */ @@ -1522,34 +1523,42 @@ CURLcode Curl_readwrite_init(struct connectdata *conn) } /* - * Curl_single_fdset() gets called by the multi interface code when the app - * has requested to get the fd_sets for the current connection. This function + * Curl_single_getsock() gets called by the multi interface code when the app + * has requested to get the sockets for the current connection. This function * will then be called once for every connection that the multi interface * keeps track of. This function will only be called for connections that are * in the proper state to have this information available. */ -void Curl_single_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - fd_set *exc_fd_set, - int *max_fd) +int Curl_single_getsock(struct connectdata *conn, + curl_socket_t *sock, /* points to numsocks number + of sockets */ + int numsocks) { - *max_fd = -1; /* init */ + int bitmap = GETSOCK_BLANK; + int index = 0; + + if(numsocks < 2) + /* simple check but we might need two slots */ + return GETSOCK_BLANK; + if(conn->keep.keepon & KEEP_READ) { - FD_SET(conn->sockfd, read_fd_set); - *max_fd = (int)conn->sockfd; + bitmap |= GETSOCK_READSOCK(index); + sock[index] = conn->sockfd; } if(conn->keep.keepon & KEEP_WRITE) { - FD_SET(conn->writesockfd, write_fd_set); - /* since sockets are curl_socket_t nowadays, we typecast it to int here - to compare it nicely */ - if((int)conn->writesockfd > *max_fd) - *max_fd = (int)conn->writesockfd; + if((conn->sockfd != conn->writesockfd) && + (conn->keep.keepon & KEEP_READ)) { + /* only if they are not the same socket and we had a readable one, + we increase index */ + index++; + sock[index] = conn->writesockfd; + } + + bitmap |= GETSOCK_WRITESOCK(index); } - /* we don't use exceptions, only touch that one to prevent compiler - warnings! */ - *exc_fd_set = *exc_fd_set; + + return bitmap; } diff --git a/lib/transfer.h b/lib/transfer.h index e7f71ba30..3f4878cd7 100644 --- a/lib/transfer.h +++ b/lib/transfer.h @@ -28,11 +28,9 @@ CURLcode Curl_second_connect(struct connectdata *conn); CURLcode Curl_posttransfer(struct SessionHandle *data); CURLcode Curl_follow(struct SessionHandle *data, char *newurl, bool retry); CURLcode Curl_readwrite(struct connectdata *conn, bool *done); -void Curl_single_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - fd_set *exc_fd_set, - int *max_fd); +int Curl_single_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); CURLcode Curl_readwrite_init(struct connectdata *conn); CURLcode Curl_readrewind(struct connectdata *conn); CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp); diff --git a/lib/url.c b/lib/url.c index a21ef5ba6..690b3f302 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1552,6 +1552,7 @@ CURLcode Curl_disconnect(struct connectdata *conn) NULL, Curl_scan_cache_used); #endif + Curl_expire(data, 0); /* shut off timers */ Curl_hostcache_prune(data); /* kill old DNS cache entries */ /* @@ -2318,26 +2319,22 @@ static void verboseconnect(struct connectdata *conn) conn->ip_addr_str, conn->port); } -CURLcode Curl_protocol_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +int Curl_protocol_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) { - CURLcode res = CURLE_OK; - if(conn->curl_proto_fdset) - res = conn->curl_proto_fdset(conn, read_fd_set, write_fd_set, max_fdp); - return res; + if(conn->curl_proto_getsock) + return conn->curl_proto_getsock(conn, socks, numsocks); + return GETSOCK_BLANK; } -CURLcode Curl_doing_fdset(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp) +int Curl_doing_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) { - CURLcode res = CURLE_OK; - if(conn && conn->curl_doing_fdset) - res = conn->curl_doing_fdset(conn, read_fd_set, write_fd_set, max_fdp); - return res; + if(conn && conn->curl_doing_getsock) + return conn->curl_doing_getsock(conn, socks, numsocks); + return GETSOCK_BLANK; } /* @@ -3034,7 +3031,7 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->curl_done = Curl_http_done; conn->curl_connect = Curl_http_connect; conn->curl_connecting = Curl_https_connecting; - conn->curl_proto_fdset = Curl_https_proto_fdset; + conn->curl_proto_getsock = Curl_https_getsock; #else /* USE_SS */ failf(data, LIBCURL_NAME @@ -3086,8 +3083,8 @@ static CURLcode CreateConnection(struct SessionHandle *data, conn->curl_connect = Curl_ftp_connect; conn->curl_connecting = Curl_ftp_multi_statemach; conn->curl_doing = Curl_ftp_doing; - conn->curl_proto_fdset = Curl_ftp_fdset; - conn->curl_doing_fdset = Curl_ftp_fdset; + conn->curl_proto_getsock = Curl_ftp_getsock; + conn->curl_doing_getsock = Curl_ftp_getsock; conn->curl_disconnect = Curl_ftp_disconnect; } @@ -4027,6 +4024,8 @@ CURLcode Curl_done(struct connectdata **connp, struct connectdata *conn = *connp; struct SessionHandle *data=conn->data; + Curl_expire(data, 0); /* stop timer */ + if(conn->bits.done) return CURLE_OK; /* Curl_done() has already been called */ diff --git a/lib/url.h b/lib/url.h index 40b406c68..9814a6a3e 100644 --- a/lib/url.h +++ b/lib/url.h @@ -45,6 +45,16 @@ CURLcode Curl_protocol_connect(struct connectdata *conn, bool *done); CURLcode Curl_protocol_connecting(struct connectdata *conn, bool *done); CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done); void Curl_safefree(void *ptr); + + +int Curl_protocol_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); +int Curl_doing_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); + +#if 0 CURLcode Curl_protocol_fdset(struct connectdata *conn, fd_set *read_fd_set, fd_set *write_fd_set, @@ -54,3 +64,5 @@ CURLcode Curl_doing_fdset(struct connectdata *conn, fd_set *write_fd_set, int *max_fdp); #endif + +#endif diff --git a/lib/urldata.h b/lib/urldata.h index a970b7377..457832dcf 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -96,6 +96,7 @@ #include "http_chunks.h" /* for the structs and enum stuff */ #include "hostip.h" #include "hash.h" +#include "splay.h" #ifdef HAVE_GSSAPI # ifdef HAVE_GSSGNU @@ -657,17 +658,15 @@ struct connectdata { /* Called from the multi interface during the PROTOCONNECT phase, and it should then return a proper fd set */ - CURLcode (*curl_proto_fdset)(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp); + int (*curl_proto_getsock)(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); /* Called from the multi interface during the DOING phase, and it should then return a proper fd set */ - CURLcode (*curl_doing_fdset)(struct connectdata *conn, - fd_set *read_fd_set, - fd_set *write_fd_set, - int *max_fdp); + int (*curl_doing_getsock)(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); /* This function *MAY* be set to a protocol-dependent function that is run * by the curl_disconnect(), as a step in the disconnection. @@ -932,10 +931,11 @@ struct UrlState { #if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H) ENGINE *engine; #endif /* USE_SSLEAY */ + struct timeval expiretime; /* set this with Curl_expire() only */ + struct Curl_tree timenode; /* for the splay stuff */ /* a place to store the most recenlty set FTP entrypath */ char *most_recent_ftp_entrypath; - }; @@ -968,6 +968,8 @@ struct DynamicStatic { * 'struct UrlState' instead. The only exceptions MUST note the changes in * the 'DynamicStatic' struct. */ +struct Curl_one_easy; /* declared and used only in multi.c */ +struct Curl_multi; /* declared and used only in multi.c */ struct UserDefined { FILE *err; /* the stderr user data goes here */ @@ -1071,6 +1073,12 @@ struct UserDefined { char *private_data; /* Private data */ + struct Curl_one_easy *one_easy; /* When adding an easy handle to a multi + handle, an internal 'Curl_one_easy' + struct is created and this is a pointer + to the particular struct associated with + this SessionHandle */ + struct curl_slist *http200aliases; /* linked list of aliases for http200 */ long ip_version; @@ -1139,7 +1147,7 @@ struct UserDefined { struct SessionHandle { struct curl_hash *hostcache; - void *multi; /* if non-NULL, points to the multi handle + struct Curl_multi *multi; /* if non-NULL, points to the multi handle struct of which this "belongs" */ struct Curl_share *share; /* Share, handles global variable mutexing */ struct UserDefined set; /* values set by the libcurl user */