diff options
author | Linus Nielsen Feltzing <linus@haxx.se> | 2013-02-15 11:50:45 +0100 |
---|---|---|
committer | Linus Nielsen Feltzing <linus@haxx.se> | 2013-03-13 23:55:24 +0100 |
commit | 0f147887b0d592d5fa72215282e84103eb165ad7 (patch) | |
tree | 4ad5bc8185f3f8df0eed54ee45afd26d692b44a0 /lib | |
parent | 911b2d3f677eb1538d8469d43df5780e8b7c7abc (diff) | |
download | curl-0f147887b0d592d5fa72215282e84103eb165ad7.tar.gz |
Multiple pipelines and limiting the number of connections.
Introducing a number of options to the multi interface that
allows for multiple pipelines to the same host, in order to
optimize the balance between the penalty for opening new
connections and the potential pipelining latency.
Two new options for limiting the number of connections:
CURLMOPT_MAX_HOST_CONNECTIONS - Limits the number of running connections
to the same host. When adding a handle that exceeds this limit,
that handle will be put in a pending state until another handle is
finished, so we can reuse the connection.
CURLMOPT_MAX_TOTAL_CONNECTIONS - Limits the number of connections in total.
When adding a handle that exceeds this limit,
that handle will be put in a pending state until another handle is
finished. The free connection will then be reused, if possible, or
closed if the pending handle can't reuse it.
Several new options for pipelining:
CURLMOPT_MAX_PIPELINE_LENGTH - Limits the pipeling length. If a
pipeline is "full" when a connection is to be reused, a new connection
will be opened if the CURLMOPT_MAX_xxx_CONNECTIONS limits allow it.
If not, the handle will be put in a pending state until a connection is
ready (either free or a pipe got shorter).
CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE - A pipelined connection will not
be reused if it is currently processing a transfer with a content
length that is larger than this.
CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE - A pipelined connection will not
be reused if it is currently processing a chunk larger than this.
CURLMOPT_PIPELINING_SITE_BL - A blacklist of hosts that don't allow
pipelining.
CURLMOPT_PIPELINING_SERVER_BL - A blacklist of server types that don't allow
pipelining.
See the curl_multi_setopt() man page for details.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.inc | 4 | ||||
-rw-r--r-- | lib/README.pipelining | 7 | ||||
-rw-r--r-- | lib/hash.h | 1 | ||||
-rw-r--r-- | lib/http.c | 20 | ||||
-rw-r--r-- | lib/multi.c | 261 | ||||
-rw-r--r-- | lib/multihandle.h | 63 | ||||
-rw-r--r-- | lib/multiif.h | 30 | ||||
-rw-r--r-- | lib/pipeline.c | 366 | ||||
-rw-r--r-- | lib/pipeline.h | 50 | ||||
-rw-r--r-- | lib/sendf.c | 3 | ||||
-rw-r--r-- | lib/strerror.c | 3 | ||||
-rw-r--r-- | lib/transfer.c | 7 | ||||
-rw-r--r-- | lib/url.c | 268 | ||||
-rw-r--r-- | lib/urldata.h | 17 |
14 files changed, 835 insertions, 265 deletions
diff --git a/lib/Makefile.inc b/lib/Makefile.inc index db0597365..f76e1ec83 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -25,7 +25,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ http_proxy.c non-ascii.c asyn-ares.c asyn-thread.c curl_gssapi.c \ curl_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_ntlm_msgs.c \ curl_sasl.c curl_schannel.c curl_multibyte.c curl_darwinssl.c \ - hostcheck.c bundles.c conncache.c + hostcheck.c bundles.c conncache.c pipeline.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ @@ -44,4 +44,4 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \ asyn.h curl_ntlm.h curl_gssapi.h curl_ntlm_wb.h curl_ntlm_core.h \ curl_ntlm_msgs.h curl_sasl.h curl_schannel.h curl_multibyte.h \ curl_darwinssl.h hostcheck.h bundles.h conncache.h curl_setup_once.h \ - multihandle.h setup-vms.h + multihandle.h setup-vms.h pipeline.h diff --git a/lib/README.pipelining b/lib/README.pipelining index c7b462248..e5bf6ec33 100644 --- a/lib/README.pipelining +++ b/lib/README.pipelining @@ -42,10 +42,3 @@ Details still resolve the second one properly to make sure that they actually _can_ be considered for pipelining. Also, asking for explicit pipelining on handle X may be tricky when handle X get a closed connection. - -- We need options to control max pipeline length, and probably how to behave - if we reach that limit. As was discussed on the list, it can probably be - made very complicated, so perhaps we can think of a way to pass all - variables involved to a callback and let the application decide how to act - in specific situations. Either way, these fancy options are only interesting - to work on when everything is working and we have working apps to test with. diff --git a/lib/hash.h b/lib/hash.h index 99a627405..aa935d4eb 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -104,4 +104,3 @@ void Curl_hash_print(struct curl_hash *h, #endif /* HEADER_CURL_HASH_H */ - diff --git a/lib/http.c b/lib/http.c index daaafe317..0ba11133f 100644 --- a/lib/http.c +++ b/lib/http.c @@ -73,6 +73,8 @@ #include "http_proxy.h" #include "warnless.h" #include "non-ascii.h" +#include "bundles.h" +#include "pipeline.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -3148,13 +3150,19 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, } else if(conn->httpversion >= 11 && !conn->bits.close) { + struct connectbundle *cb_ptr; /* If HTTP version is >= 1.1 and connection is persistent server supports pipelining. */ DEBUGF(infof(data, "HTTP 1.1 or later with persistent connection, " "pipelining supported\n")); - conn->server_supports_pipelining = TRUE; + /* Activate pipelining if needed */ + cb_ptr = conn->bundle; + if(cb_ptr) { + if(!Curl_pipeline_site_blacklisted(data, conn)) + cb_ptr->server_supports_pipelining = TRUE; + } } switch(k->httpcode) { @@ -3231,6 +3239,16 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, data->info.contenttype = contenttype; } } + else if(checkprefix("Server:", k->p)) { + char *server_name = copy_header_value(k->p); + + /* Turn off pipelining if the server version is blacklisted */ + if(conn->bundle && conn->bundle->server_supports_pipelining) { + if(Curl_pipeline_server_blacklisted(data, server_name)) + conn->bundle->server_supports_pipelining = FALSE; + } + Curl_safefree(server_name); + } else if((conn->httpversion == 10) && conn->bits.httpproxy && Curl_compareheader(k->p, diff --git a/lib/multi.c b/lib/multi.c index a369d0361..3e2583a21 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -40,6 +40,7 @@ #include "conncache.h" #include "bundles.h" #include "multihandle.h" +#include "pipeline.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -69,13 +70,6 @@ static void singlesocket(struct Curl_multi *multi, struct Curl_one_easy *easy); 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, @@ -85,6 +79,7 @@ static CURLMcode add_next_timeout(struct timeval now, #ifdef DEBUGBUILD static const char * const statename[]={ "INIT", + "CONNECT_PEND", "CONNECT", "WAITRESOLVE", "WAITCONNECT", @@ -125,9 +120,9 @@ static void mstate(struct Curl_one_easy *easy, CURLMstate state easy->state = state; #ifdef DEBUGBUILD - if(easy->easy_conn) { - if(easy->state > CURLM_STATE_CONNECT && - easy->state < CURLM_STATE_COMPLETED) + if(easy->state >= CURLM_STATE_CONNECT_PEND && + easy->state < CURLM_STATE_COMPLETED) { + if(easy->easy_conn) connection_id = easy->easy_conn->connection_id; infof(easy->easy_handle, @@ -314,6 +309,7 @@ CURLM *curl_multi_init(void) multi->easy.next = &multi->easy; multi->easy.prev = &multi->easy; + multi->max_pipeline_length = 5; return (CURLM *) multi; error: @@ -580,7 +576,7 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, /* as this was using a shared connection cache we clear the pointer to that since we're not part of that multi handle anymore */ - easy->easy_handle->state.conn_cache = NULL; + easy->easy_handle->state.conn_cache = NULL; /* change state without using multistate(), only to make singlesocket() do what we want */ @@ -638,9 +634,9 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle, return CURLM_BAD_EASY_HANDLE; /* twasn't found */ } -bool Curl_multi_canPipeline(const struct Curl_multi* multi) +bool Curl_multi_pipeline_enabled(const struct Curl_multi* multi) { - return multi->pipelining_enabled; + return multi && multi->pipelining_enabled; } void Curl_multi_handlePipeBreak(struct SessionHandle *data) @@ -1007,16 +1003,27 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; + case CURLM_STATE_CONNECT_PEND: + /* We will stay here until there is a connection available. Then + we try again in the CURLM_STATE_CONNECT state. */ + break; + case CURLM_STATE_CONNECT: - /* Connect. We get a connection identifier filled in. */ + /* Connect. We want to get a connection identifier filled in. */ Curl_pgrsTime(data, TIMER_STARTSINGLE); easy->result = Curl_connect(data, &easy->easy_conn, &async, &protocol_connect); + if(CURLE_NO_CONNECTION_AVAILABLE == easy->result) { + /* There was no connection available. We will go to the pending + state and wait for an available connection. */ + multistate(easy, CURLM_STATE_CONNECT_PEND); + easy->result = CURLM_OK; + break; + } if(CURLE_OK == easy->result) { /* Add this handle to the send or pend pipeline */ - easy->result = addHandleToSendOrPendPipeline(data, - easy->easy_conn); + easy->result = Curl_add_handle_to_pipeline(data, easy->easy_conn); if(CURLE_OK != easy->result) disconnect_conn = TRUE; else { @@ -1357,9 +1364,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_DO_DONE: /* Move ourselves from the send to recv pipeline */ - moveHandleFromSendToRecvPipeline(data, easy->easy_conn); + Curl_move_handle_from_send_to_recv_pipe(data, easy->easy_conn); /* Check if we can move pending requests to send pipe */ - checkPendPipeline(easy->easy_conn); + Curl_multi_process_pending_handles(multi); multistate(easy, CURLM_STATE_WAITPERFORM); result = CURLM_CALL_MULTI_PERFORM; break; @@ -1491,15 +1498,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Curl_posttransfer(data); /* we're no longer receiving */ - moveHandleFromRecvToDonePipeline(data, - easy->easy_conn); + Curl_removeHandleFromPipeline(data, easy->easy_conn->recv_pipe); /* 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); + Curl_multi_process_pending_handles(multi); /* When we follow redirects or is set to retry the connection, we must to go back to the CONNECT state */ @@ -1554,14 +1560,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, 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. */ + /* Remove ourselves from the receive pipeline, if we are there. */ 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); + Curl_multi_process_pending_handles(multi); if(easy->easy_conn->bits.stream_was_rewound) { /* This request read past its response boundary so we quickly let @@ -1638,10 +1641,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, 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); + Curl_multi_process_pending_handles(multi); if(disconnect_conn) { /* disconnect properly */ @@ -1789,8 +1790,8 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle) Curl_hostcache_clean(multi->closure_handle); Curl_close(multi->closure_handle); + multi->closure_handle = NULL; } - multi->closure_handle = NULL; Curl_hash_destroy(multi->sockhash); multi->sockhash = NULL; @@ -1825,6 +1826,10 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle) Curl_hash_destroy(multi->hostcache); multi->hostcache = NULL; + /* Free the blacklists by setting them to NULL */ + Curl_pipeline_set_site_blacklist(NULL, &multi->pipelining_site_bl); + Curl_pipeline_set_server_blacklist(NULL, &multi->pipelining_server_bl); + free(multi); return CURLM_OK; @@ -2242,6 +2247,29 @@ CURLMcode curl_multi_setopt(CURLM *multi_handle, case CURLMOPT_MAXCONNECTS: multi->maxconnects = va_arg(param, long); break; + case CURLMOPT_MAX_HOST_CONNECTIONS: + multi->max_host_connections = va_arg(param, long); + break; + case CURLMOPT_MAX_PIPELINE_LENGTH: + multi->max_pipeline_length = va_arg(param, long); + break; + case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: + multi->content_length_penalty_size = va_arg(param, long); + break; + case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: + multi->chunk_length_penalty_size = va_arg(param, long); + break; + case CURLMOPT_PIPELINING_SITE_BL: + res = Curl_pipeline_set_site_blacklist(va_arg(param, char **), + &multi->pipelining_site_bl); + break; + case CURLMOPT_PIPELINING_SERVER_BL: + res = Curl_pipeline_set_server_blacklist(va_arg(param, char **), + &multi->pipelining_server_bl); + break; + case CURLMOPT_MAX_TOTAL_CONNECTIONS: + multi->max_total_connections = va_arg(param, long); + break; default: res = CURLM_UNKNOWN_OPTION; break; @@ -2366,131 +2394,12 @@ static int update_timer(struct Curl_multi *multi) return multi->timer_cb((CURLM*)multi, timeout_ms, multi->timer_userp); } -static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, +void Curl_multi_set_easy_connection(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; - } + handle->set.one_easy->easy_conn = conn; } -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) { @@ -2670,6 +2579,56 @@ CURLMcode curl_multi_assign(CURLM *multi_handle, return CURLM_OK; } +size_t Curl_multi_max_host_connections(struct Curl_multi *multi) +{ + return multi ? multi->max_host_connections : 0; +} + +size_t Curl_multi_max_total_connections(struct Curl_multi *multi) +{ + return multi ? multi->max_total_connections : 0; +} + +size_t Curl_multi_max_pipeline_length(struct Curl_multi *multi) +{ + return multi ? multi->max_pipeline_length : 0; +} + +curl_off_t Curl_multi_content_length_penalty_size(struct Curl_multi *multi) +{ + return multi ? multi->content_length_penalty_size : 0; +} + +curl_off_t Curl_multi_chunk_length_penalty_size(struct Curl_multi *multi) +{ + return multi ? multi->chunk_length_penalty_size : 0; +} + +struct curl_llist *Curl_multi_pipelining_site_bl(struct Curl_multi *multi) +{ + return multi->pipelining_site_bl; +} + +struct curl_llist *Curl_multi_pipelining_server_bl(struct Curl_multi *multi) +{ + return multi->pipelining_server_bl; +} + +void Curl_multi_process_pending_handles(struct Curl_multi *multi) +{ + struct Curl_one_easy *easy; + + easy=multi->easy.next; + while(easy != &multi->easy) { + if(easy->state == CURLM_STATE_CONNECT_PEND) { + multistate(easy, CURLM_STATE_CONNECT); + /* Make sure that the handle will be processed soonish. */ + Curl_expire(easy->easy_handle, 1); + } + easy = easy->next; /* operate on next handle */ + } +} + #ifdef DEBUGBUILD void Curl_multi_dump(const struct Curl_multi *multi_handle) { diff --git a/lib/multihandle.h b/lib/multihandle.h index 941835941..3fcd9c0e6 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -31,25 +31,26 @@ struct Curl_message { 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 */ + CURLM_STATE_INIT, /* 0 - start in this state */ + CURLM_STATE_CONNECT_PEND, /* 1 - no connections, waiting for one */ + CURLM_STATE_CONNECT, /* 2 - resolve/connect has been sent off */ + CURLM_STATE_WAITRESOLVE, /* 3 - awaiting the resolve to finalize */ + CURLM_STATE_WAITCONNECT, /* 4 - awaiting the connect to finalize */ + CURLM_STATE_WAITPROXYCONNECT, /* 5 - awaiting proxy CONNECT to finalize */ + CURLM_STATE_PROTOCONNECT, /* 6 - completing the protocol-specific connect + phase */ + CURLM_STATE_WAITDO, /* 7 - wait for our turn to send the request */ + CURLM_STATE_DO, /* 8 - start send off the request (part 1) */ + CURLM_STATE_DOING, /* 9 - sending off the request (part 1) */ + CURLM_STATE_DO_MORE, /* 10 - send off the request (part 2) */ + CURLM_STATE_DO_DONE, /* 11 - done sending off request */ + CURLM_STATE_WAITPERFORM, /* 12 - wait for our turn to read the response */ + CURLM_STATE_PERFORM, /* 13 - transfer data */ + CURLM_STATE_TOOFAST, /* 14 - wait because limit-rate exceeded */ + CURLM_STATE_DONE, /* 15 - post data transfer operation */ + CURLM_STATE_COMPLETED, /* 16 - operation complete */ + CURLM_STATE_MSGSENT, /* 17 - the operation complete message is sent */ + CURLM_STATE_LAST /* 18 - not a true state, never use this */ } CURLMstate; /* we support N sockets per easy handle. Set the corresponding bit to what @@ -123,6 +124,30 @@ struct Curl_multi { long maxconnects; /* if >0, a fixed limit of the maximum number of entries we're allowed to grow the connection cache to */ + long max_host_connections; /* if >0, a fixed limit of the maximum number + of connections per host */ + + long max_total_connections; /* if >0, a fixed limit of the maximum number + of connections in total */ + + long max_pipeline_length; /* if >0, maximum number of requests in a + pipeline */ + + long content_length_penalty_size; /* a connection with a + content-length bigger than + this is not considered + for pipelining */ + + long chunk_length_penalty_size; /* a connection with a chunk length + bigger than this is not + considered for pipelining */ + + struct curl_llist *pipelining_site_bl; /* List of sites that are blacklisted + from pipelining */ + + struct curl_llist *pipelining_server_bl; /* List of server types that are + blacklisted from pipelining */ + /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; diff --git a/lib/multiif.h b/lib/multiif.h index c84b6184c..0dcdec76e 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -27,7 +27,7 @@ */ void Curl_expire(struct SessionHandle *data, long milli); -bool Curl_multi_canPipeline(const struct Curl_multi* multi); +bool Curl_multi_pipeline_enabled(const struct Curl_multi* multi); void Curl_multi_handlePipeBreak(struct SessionHandle *data); /* the write bits start at bit 16 for the *getsock() bitmap */ @@ -50,5 +50,31 @@ void Curl_multi_handlePipeBreak(struct SessionHandle *data); void Curl_multi_dump(const struct Curl_multi *multi_handle); #endif -#endif /* HEADER_CURL_MULTIIF_H */ +/* Update the current connection of a One_Easy handle */ +void Curl_multi_set_easy_connection(struct SessionHandle *handle, + struct connectdata *conn); + +void Curl_multi_process_pending_handles(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_MAX_HOST_CONNECTIONS option */ +size_t Curl_multi_max_host_connections(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_MAX_PIPELINE_LENGTH option */ +size_t Curl_multi_max_pipeline_length(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE option */ +curl_off_t Curl_multi_content_length_penalty_size(struct Curl_multi *multi); +/* Return the value of the CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE option */ +curl_off_t Curl_multi_chunk_length_penalty_size(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_PIPELINING_SITE_BL option */ +struct curl_llist *Curl_multi_pipelining_site_bl(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_PIPELINING_SERVER_BL option */ +struct curl_llist *Curl_multi_pipelining_server_bl(struct Curl_multi *multi); + +/* Return the value of the CURLMOPT_MAX_TOTAL_CONNECTIONS option */ +size_t Curl_multi_max_total_connections(struct Curl_multi *multi); + +#endif /* HEADER_CURL_MULTIIF_H */ diff --git a/lib/pipeline.c b/lib/pipeline.c new file mode 100644 index 000000000..7abc35fd0 --- /dev/null +++ b/lib/pipeline.c @@ -0,0 +1,366 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2013, Linus Nielsen Feltzing, <linus@haxx.se> + * + * 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 "curl_setup.h" + +#include <curl/curl.h> + +#include "urldata.h" +#include "url.h" +#include "progress.h" +#include "multiif.h" +#include "pipeline.h" +#include "sendf.h" +#include "rawstr.h" +#include "bundles.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +struct site_blacklist_entry { + char *hostname; + unsigned short port; +}; + +static void site_blacklist_llist_dtor(void *user, void *element) +{ + struct site_blacklist_entry *entry = element; + (void)user; + + Curl_safefree(entry->hostname); + Curl_safefree(entry); +} + +static void server_blacklist_llist_dtor(void *user, void *element) +{ + char *server_name = element; + (void)user; + + Curl_safefree(server_name); +} + +bool Curl_pipeline_penalized(struct SessionHandle *data, + struct connectdata *conn) +{ + if(data) { + bool penalized = FALSE; + curl_off_t penalty_size = + Curl_multi_content_length_penalty_size(data->multi); + curl_off_t chunk_penalty_size = + Curl_multi_chunk_length_penalty_size(data->multi); + curl_off_t recv_size = -2; /* Make it easy to spot in the log */ + + /* Find the head of the recv pipe, if any */ + if(conn->recv_pipe && conn->recv_pipe->head) { + struct SessionHandle *recv_handle = conn->recv_pipe->head->ptr; + + recv_size = recv_handle->req.size; + + if(penalty_size > 0 && recv_size > penalty_size) + penalized = TRUE; + } + + if(chunk_penalty_size > 0 && + (curl_off_t)conn->chunk.datasize > chunk_penalty_size) + penalized = TRUE; + + infof(data, "Conn: %d (%p) Receive pipe weight: (%d/%d), penalized: %d\n", + conn->connection_id, conn, recv_size, + conn->chunk.datasize, penalized); + return penalized; + } + return FALSE; +} + +/* Find the best connection in a bundle to use for the next request */ +struct connectdata * +Curl_bundle_find_best(struct SessionHandle *data, + struct connectbundle *cb_ptr) +{ + struct curl_llist_element *curr; + struct connectdata *conn; + struct connectdata *best_conn = NULL; + size_t pipe_len; + size_t best_pipe_len = 99; + + (void)data; + + curr = cb_ptr->conn_list->head; + while(curr) { + conn = curr->ptr; + pipe_len = conn->send_pipe->size + conn->recv_pipe->size; + + if(!Curl_pipeline_penalized(conn->data, conn) && + pipe_len < best_pipe_len) { + best_conn = conn; + best_pipe_len = pipe_len; + } + curr = curr->next; + } + + /* If we haven't found a connection, i.e all pipelines are penalized + or full, just pick one. The request will then be queued in + Curl_add_handle_to_pipeline(). */ + if(!best_conn) { + best_conn = cb_ptr->conn_list->head->ptr; + } + return best_conn; +} + +CURLcode Curl_add_handle_to_pipeline(struct SessionHandle *handle, + struct connectdata *conn) +{ + struct curl_llist_element *sendhead = conn->send_pipe->head; + struct curl_llist *pipeline; + CURLcode rc; + + pipeline = conn->send_pipe; + + infof(conn->data, "Adding handle: conn: %p\n", conn); + infof(conn->data, "Adding handle: send: %d\n", conn->send_pipe->size); + infof(conn->data, "Adding handle: recv: %d\n", conn->recv_pipe->size); + 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); + } + + print_pipeline(conn); + + return rc; +} + +/* 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. + +*/ +void Curl_move_handle_from_send_to_recv_pipe(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; + } +} + +bool Curl_pipeline_site_blacklisted(struct SessionHandle *handle, + struct connectdata *conn) +{ + if(handle->multi) { + struct curl_llist *blacklist = + Curl_multi_pipelining_site_bl(handle->multi); + + if(blacklist) { + struct curl_llist_element *curr; + + curr = blacklist->head; + while(curr) { + struct site_blacklist_entry *site; + + site = curr->ptr; + if(Curl_raw_equal(site->hostname, conn->host.name) && + site->port == conn->remote_port) { + infof(handle, "Site %s:%d is pipeline blacklisted\n", + conn->host.name, conn->remote_port); + return TRUE; + } + curr = curr->next; + } + } + } + return FALSE; +} + +CURLMcode Curl_pipeline_set_site_blacklist(char **sites, + struct curl_llist **list_ptr) +{ + struct curl_llist *old_list = *list_ptr; + struct curl_llist *new_list = NULL; + + if(sites) { + new_list = Curl_llist_alloc((curl_llist_dtor) site_blacklist_llist_dtor); + if(!new_list) + return CURLM_OUT_OF_MEMORY; + + /* Parse the URLs and populate the list */ + while(*sites) { + char *hostname; + char *port; + struct site_blacklist_entry *entry; + + entry = malloc(sizeof(struct site_blacklist_entry)); + + hostname = strdup(*sites); + if(!hostname) + return CURLM_OUT_OF_MEMORY; + + port = strchr(hostname, ':'); + if(port) { + *port = '\0'; + port++; + entry->port = (unsigned short)strtol(port, NULL, 10); + } + else { + /* Default port number for HTTP */ + entry->port = 80; + } + + entry->hostname = hostname; + + if(!Curl_llist_insert_next(new_list, new_list->tail, entry)) + return CURLM_OUT_OF_MEMORY; + + sites++; + } + } + + /* Free the old list */ + if(old_list) { + Curl_llist_destroy(old_list, NULL); + } + + /* This might be NULL if sites == NULL, i.e the blacklist is cleared */ + *list_ptr = new_list; + + return CURLM_OK; +} + +bool Curl_pipeline_server_blacklisted(struct SessionHandle *handle, + char *server_name) +{ + if(handle->multi) { + struct curl_llist *blacklist = + Curl_multi_pipelining_server_bl(handle->multi); + + if(blacklist) { + struct curl_llist_element *curr; + + curr = blacklist->head; + while(curr) { + char *bl_server_name; + + bl_server_name = curr->ptr; + if(Curl_raw_nequal(bl_server_name, server_name, + strlen(bl_server_name))) { + infof(handle, "Server %s is blacklisted\n", server_name); + return TRUE; + } + curr = curr->next; + } + } + + infof(handle, "Server %s is not blacklisted\n", server_name); + } + return FALSE; +} + +CURLMcode Curl_pipeline_set_server_blacklist(char **servers, + struct curl_llist **list_ptr) +{ + struct curl_llist *old_list = *list_ptr; + struct curl_llist *new_list = NULL; + + if(servers) { + new_list = Curl_llist_alloc((curl_llist_dtor) server_blacklist_llist_dtor); + if(!new_list) + return CURLM_OUT_OF_MEMORY; + + /* Parse the URLs and populate the list */ + while(*servers) { + char *server_name; + + server_name = strdup(*servers); + if(!server_name) + return CURLM_OUT_OF_MEMORY; + + if(!Curl_llist_insert_next(new_list, new_list->tail, server_name)) + return CURLM_OUT_OF_MEMORY; + + servers++; + } + } + + /* Free the old list */ + if(old_list) { + Curl_llist_destroy(old_list, NULL); + } + + /* This might be NULL if sites == NULL, i.e the blacklist is cleared */ + *list_ptr = new_list; + + return CURLM_OK; +} + + +void print_pipeline(struct connectdata *conn) +{ + struct curl_llist_element *curr; + struct connectbundle *cb_ptr; + struct SessionHandle *data = conn->data; + + cb_ptr = conn->bundle; + + if(cb_ptr) { + curr = cb_ptr->conn_list->head; + while(curr) { + conn = curr->ptr; + infof(data, "- Conn %d (%p) send_pipe: %d, recv_pipe: %d\n", + conn->connection_id, + conn, + conn->send_pipe->size, + conn->recv_pipe->size); + curr = curr->next; + } + } +} diff --git a/lib/pipeline.h b/lib/pipeline.h new file mode 100644 index 000000000..f3a734c9a --- /dev/null +++ b/lib/pipeline.h @@ -0,0 +1,50 @@ +#ifndef HEADER_CURL_PIPELINE_H +#define HEADER_CURL_PIPELINE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2013, Linus Nielsen Feltzing, <linus@haxx.se> + * + * 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. + * + ***************************************************************************/ + +struct connectdata * +Curl_bundle_find_best(struct SessionHandle *data, + struct connectbundle *cb_ptr); + +CURLcode Curl_add_handle_to_pipeline(struct SessionHandle *handle, + struct connectdata *conn); +void Curl_move_handle_from_send_to_recv_pipe(struct SessionHandle *handle, + struct connectdata *conn); +bool Curl_pipeline_penalized(struct SessionHandle *data, + struct connectdata *conn); + +bool Curl_pipeline_site_blacklisted(struct SessionHandle *handle, + struct connectdata *conn); + +CURLMcode Curl_pipeline_set_site_blacklist(char **sites, + struct curl_llist **list_ptr); + +bool Curl_pipeline_server_blacklisted(struct SessionHandle *handle, + char *server_name); + +CURLMcode Curl_pipeline_set_server_blacklist(char **servers, + struct curl_llist **list_ptr); + +void print_pipeline(struct connectdata *conn); + +#endif /* HEADER_CURL_PIPELINE_H */ diff --git a/lib/sendf.c b/lib/sendf.c index c64d686b9..d5bf17282 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -529,8 +529,7 @@ CURLcode Curl_read(struct connectdata *conn, /* connection data */ ssize_t nread = 0; size_t bytesfromsocket = 0; char *buffertofill = NULL; - bool pipelining = (conn->data->multi && - Curl_multi_canPipeline(conn->data->multi)) ? TRUE : FALSE; + bool pipelining = Curl_multi_pipeline_enabled(conn->data->multi); /* Set 'num' to 0 or 1, depending on which socket that has been sent here. If it is the second socket, we set num to 1. Otherwise to 0. This lets diff --git a/lib/strerror.c b/lib/strerror.c index 58c6dc310..a385f5572 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -292,6 +292,9 @@ curl_easy_strerror(CURLcode error) case CURLE_CHUNK_FAILED: return "Chunk callback failed"; + case CURLE_NO_CONNECTION_AVAILABLE: + return "The max connection limit is reached"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE16: case CURLE_OBSOLETE20: diff --git a/lib/transfer.c b/lib/transfer.c index 330b37a2b..db0318d5a 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -473,7 +473,7 @@ static CURLcode readwrite_data(struct SessionHandle *data, /* We've stopped dealing with input, get out of the do-while loop */ if(nread > 0) { - if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) { + if(Curl_multi_pipeline_enabled(conn->data->multi)) { infof(data, "Rewinding stream by : %zd" " bytes on url %s (zero-length body)\n", @@ -602,8 +602,7 @@ static CURLcode readwrite_data(struct SessionHandle *data, if(dataleft != 0) { infof(conn->data, "Leftovers after chunking: %zu bytes\n", dataleft); - if(conn->data->multi && - Curl_multi_canPipeline(conn->data->multi)) { + if(Curl_multi_pipeline_enabled(conn->data->multi)) { /* only attempt the rewind if we truly are pipelining */ infof(conn->data, "Rewinding %zu bytes\n",dataleft); read_rewind(conn, dataleft); @@ -626,7 +625,7 @@ static CURLcode readwrite_data(struct SessionHandle *data, excess = (size_t)(k->bytecount + nread - k->maxdownload); if(excess > 0 && !k->ignorebody) { - if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) { + if(Curl_multi_pipeline_enabled(conn->data->multi)) { /* The 'excess' amount below can't be more than BUFSIZE which always will fit in a size_t */ infof(data, @@ -123,6 +123,7 @@ int curl_win32_idn_to_ascii(const char *in, char **out); #include "bundles.h" #include "conncache.h" #include "multihandle.h" +#include "pipeline.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -134,6 +135,9 @@ int curl_win32_idn_to_ascii(const char *in, char **out); /* Local static prototypes */ static struct connectdata * find_oldest_idle_connection(struct SessionHandle *data); +static struct connectdata * +find_oldest_idle_connection_in_bundle(struct SessionHandle *data, + struct connectbundle *bundle); static void conn_free(struct connectdata *conn); static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke); static CURLcode do_init(struct connectdata *conn); @@ -2470,13 +2474,9 @@ static void conn_free(struct connectdata *conn) Curl_llist_destroy(conn->send_pipe, NULL); Curl_llist_destroy(conn->recv_pipe, NULL); - Curl_llist_destroy(conn->pend_pipe, NULL); - Curl_llist_destroy(conn->done_pipe, NULL); conn->send_pipe = NULL; conn->recv_pipe = NULL; - conn->pend_pipe = NULL; - conn->done_pipe = NULL; Curl_safefree(conn->localdev); Curl_free_ssl_config(&conn->ssl_config); @@ -2566,11 +2566,9 @@ CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection) Curl_ssl_close(conn, FIRSTSOCKET); /* Indicate to all handles on the pipe that we're dead */ - if(Curl_isPipeliningEnabled(data)) { + if(Curl_multi_pipeline_enabled(data->multi)) { signalPipeClose(conn->send_pipe, TRUE); signalPipeClose(conn->recv_pipe, TRUE); - signalPipeClose(conn->pend_pipe, TRUE); - signalPipeClose(conn->done_pipe, FALSE); } conn_free(conn); @@ -2602,7 +2600,7 @@ static bool IsPipeliningPossible(const struct SessionHandle *handle, const struct connectdata *conn) { if((conn->handler->protocol & CURLPROTO_HTTP) && - handle->multi && Curl_multi_canPipeline(handle->multi) && + Curl_multi_pipeline_enabled(handle->multi) && (handle->set.httpreq == HTTPREQ_GET || handle->set.httpreq == HTTPREQ_HEAD) && handle->set.httpversion != CURL_HTTP_VERSION_1_0) @@ -2613,10 +2611,7 @@ static bool IsPipeliningPossible(const struct SessionHandle *handle, bool Curl_isPipeliningEnabled(const struct SessionHandle *handle) { - if(handle->multi && Curl_multi_canPipeline(handle->multi)) - return TRUE; - - return FALSE; + return Curl_multi_pipeline_enabled(handle->multi); } CURLcode Curl_addHandleToPipeline(struct SessionHandle *data, @@ -2624,6 +2619,7 @@ CURLcode Curl_addHandleToPipeline(struct SessionHandle *data, { if(!Curl_llist_insert_next(pipeline, pipeline->tail, data)) return CURLE_OUT_OF_MEMORY; + infof(data, "Curl_addHandleToPipeline: length: %d\n", pipeline->size); return CURLE_OK; } @@ -2683,8 +2679,6 @@ void Curl_getoff_all_pipelines(struct SessionHandle *data, conn->readchannel_inuse = FALSE; if(Curl_removeHandleFromPipeline(data, conn->send_pipe) && send_head) conn->writechannel_inuse = FALSE; - Curl_removeHandleFromPipeline(data, conn->pend_pipe); - Curl_removeHandleFromPipeline(data, conn->done_pipe); } static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke) @@ -2715,8 +2709,8 @@ static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke) } /* - * This function kills and removes an existing connection in the connection - * cache. The connection that has been unused for the longest time. + * This function finds the connection in the connection + * cache that has been unused for the longest time. * * Returns the pointer to the oldest idle connection, or NULL if none was * found. @@ -2767,6 +2761,47 @@ find_oldest_idle_connection(struct SessionHandle *data) } /* + * This function finds the connection in the connection + * bundle that has been unused for the longest time. + * + * Returns the pointer to the oldest idle connection, or NULL if none was + * found. + */ +static struct connectdata * +find_oldest_idle_connection_in_bundle(struct SessionHandle *data, + struct connectbundle *bundle) +{ + struct curl_llist_element *curr; + long highscore=-1; + long score; + struct timeval now; + struct connectdata *conn_candidate = NULL; + struct connectdata *conn; + + (void)data; + + now = Curl_tvnow(); + + curr = bundle->conn_list->head; + while(curr) { + conn = curr->ptr; + + if(!conn->inuse) { + /* Set higher score for the age passed since the connection was used */ + score = Curl_tvdiff(now, conn->now); + + if(score > highscore) { + highscore = score; + conn_candidate = conn; + } + } + curr = curr->next; + } + + return conn_candidate; +} + +/* * Given one filled in connection struct (named needle), this function should * detect if there already is one that has all the significant details * exactly the same and thus should be used instead. @@ -2774,11 +2809,15 @@ find_oldest_idle_connection(struct SessionHandle *data) * If there is a match, this function returns TRUE - and has marked the * connection as 'in-use'. It must later be called with ConnectionDone() to * return back to 'idle' (unused) state. + * + * The force_reuse flag is set if the connection must be used, even if + * the pipelining strategy wants to open a new connection instead of reusing. */ static bool ConnectionExists(struct SessionHandle *data, struct connectdata *needle, - struct connectdata **usethis) + struct connectdata **usethis, + bool *force_reuse) { struct connectdata *check; struct connectdata *chosen = 0; @@ -2787,15 +2826,30 @@ ConnectionExists(struct SessionHandle *data, (data->state.authhost.want==CURLAUTH_NTLM_WB) ? TRUE : FALSE; struct connectbundle *bundle; + *force_reuse = FALSE; + + /* We can't pipe if the site is blacklisted */ + if(canPipeline && Curl_pipeline_site_blacklisted(data, needle)) { + canPipeline = FALSE; + } + /* Look up the bundle with all the connections to this particular host */ bundle = Curl_conncache_find_bundle(data->state.conn_cache, needle->host.name); if(bundle) { + size_t max_pipe_len = Curl_multi_max_pipeline_length(data->multi); + size_t best_pipe_len = max_pipe_len; struct curl_llist_element *curr; infof(data, "Found bundle for host %s: %p\n", needle->host.name, bundle); + /* We can't pipe if we don't know anything about the server */ + if(canPipeline && !bundle->server_supports_pipelining) { + infof(data, "Server doesn't support pipelining\n"); + canPipeline = FALSE; + } + curr = bundle->conn_list->head; while(curr) { bool match = FALSE; @@ -2845,12 +2899,6 @@ ConnectionExists(struct SessionHandle *data, if(!IsPipeliningPossible(rh, check)) continue; } -#ifdef DEBUGBUILD - if(pipeLen > MAX_PIPELINE_LENGTH) { - infof(data, "BAD! Connection #%ld has too big pipeline!\n", - check->connection_id); - } -#endif } else { if(pipeLen > 0) { @@ -2989,26 +3037,60 @@ ConnectionExists(struct SessionHandle *data, } if(match) { - chosen = check; + /* If we are looking for an NTLM connection, check if this is already + authenticating with the right credentials. If not, keep looking so + that we can reuse NTLM connections if possible. (Especially we + must not reuse the same connection if partway through + a handshake!) */ + if(wantNTLM) { + if(credentialsMatch && check->ntlm.state != NTLMSTATE_NONE) { + chosen = check; + + /* We must use this connection, no other */ + *force_reuse = TRUE; + break; + } + else + continue; + } - /* If we are not looking for an NTLM connection, we can choose this one - immediately. */ - if(!wantNTLM) - break; + if(canPipeline) { + /* We can pipeline if we want to. Let's continue looking for + the optimal connection to use, i.e the shortest pipe that is not + blacklisted. */ - /* Otherwise, check if this is already authenticating with the right - credentials. If not, keep looking so that we can reuse NTLM - connections if possible. (Especially we must reuse the same - connection if partway through a handshake!) */ - if(credentialsMatch && chosen->ntlm.state != NTLMSTATE_NONE) + if(pipeLen == 0) { + /* We have the optimal connection. Let's stop looking. */ + chosen = check; + break; + } + + /* We can't use the connection if the pipe is full */ + if(pipeLen >= max_pipe_len) + continue; + + /* We can't use the connection if the pipe is penalized */ + if(Curl_pipeline_penalized(data, check)) + continue; + + if(pipeLen < best_pipe_len) { + /* This connection has a shorter pipe so far. We'll pick this + and continue searching */ + chosen = check; + best_pipe_len = pipeLen; + continue; + } + } + else { + /* We have found a connection. Let's stop searching. */ + chosen = check; break; + } } } } if(chosen) { - chosen->inuse = TRUE; /* mark this as being in use so that no other - handle in a multi stack may nick it */ *usethis = chosen; return TRUE; /* yes, we found one to use! */ } @@ -3475,7 +3557,7 @@ static struct connectdata *allocate_conn(struct SessionHandle *data) conn->response_header = NULL; #endif - if(data->multi && Curl_multi_canPipeline(data->multi) && + if(Curl_multi_pipeline_enabled(data->multi) && !conn->master_buffer) { /* Allocate master_buffer to be used for pipelining */ conn->master_buffer = calloc(BUFSIZE, sizeof (char)); @@ -3486,10 +3568,7 @@ static struct connectdata *allocate_conn(struct SessionHandle *data) /* Initialize the pipeline lists */ conn->send_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); conn->recv_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - conn->pend_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - conn->done_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - if(!conn->send_pipe || !conn->recv_pipe || !conn->pend_pipe || - !conn->done_pipe) + if(!conn->send_pipe || !conn->recv_pipe) goto error; #if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) @@ -3515,13 +3594,9 @@ static struct connectdata *allocate_conn(struct SessionHandle *data) Curl_llist_destroy(conn->send_pipe, NULL); Curl_llist_destroy(conn->recv_pipe, NULL); - Curl_llist_destroy(conn->pend_pipe, NULL); - Curl_llist_destroy(conn->done_pipe, NULL); conn->send_pipe = NULL; conn->recv_pipe = NULL; - conn->pend_pipe = NULL; - conn->done_pipe = NULL; Curl_safefree(conn->master_buffer); Curl_safefree(conn->localdev); @@ -4623,13 +4698,9 @@ static void reuse_conn(struct connectdata *old_conn, Curl_llist_destroy(old_conn->send_pipe, NULL); Curl_llist_destroy(old_conn->recv_pipe, NULL); - Curl_llist_destroy(old_conn->pend_pipe, NULL); - Curl_llist_destroy(old_conn->done_pipe, NULL); old_conn->send_pipe = NULL; old_conn->recv_pipe = NULL; - old_conn->pend_pipe = NULL; - old_conn->done_pipe = NULL; Curl_safefree(old_conn->master_buffer); } @@ -4663,6 +4734,10 @@ static CURLcode create_conn(struct SessionHandle *data, bool reuse; char *proxy = NULL; bool prot_missing = FALSE; + bool no_connections_available = FALSE; + bool force_reuse; + size_t max_host_connections = Curl_multi_max_host_connections(data->multi); + size_t max_total_connections = Curl_multi_max_total_connections(data->multi); *async = FALSE; @@ -4963,7 +5038,25 @@ static CURLcode create_conn(struct SessionHandle *data, if(data->set.reuse_fresh && !data->state.this_is_a_follow) reuse = FALSE; else - reuse = ConnectionExists(data, conn, &conn_temp); + reuse = ConnectionExists(data, conn, &conn_temp, &force_reuse); + + /* If we found a reusable connection, we may still want to + open a new connection if we are pipelining. */ + if(reuse && !force_reuse && IsPipeliningPossible(data, conn_temp)) { + size_t pipelen = conn_temp->send_pipe->size + conn_temp->recv_pipe->size; + if(pipelen > 0) { + infof(data, "Found connection %d, with requests in the pipe (%d)\n", + conn_temp->connection_id, pipelen); + + if(conn_temp->bundle->num_connections < max_host_connections && + data->state.conn_cache->num_connections < max_total_connections) { + /* We want a new connection anyway */ + reuse = FALSE; + + infof(data, "We can reuse, but we want a new connection anyway\n"); + } + } + } if(reuse) { /* @@ -4972,6 +5065,8 @@ static CURLcode create_conn(struct SessionHandle *data, * just allocated before we can move along and use the previously * existing one. */ + conn_temp->inuse = TRUE; /* mark this as being in use so that no other + handle in a multi stack may nick it */ reuse_conn(conn, conn_temp); free(conn); /* we don't need this anymore */ conn = conn_temp; @@ -4985,14 +5080,66 @@ static CURLcode create_conn(struct SessionHandle *data, conn->proxy.name?conn->proxy.dispname:conn->host.dispname); } else { - /* - * This is a brand new connection, so let's store it in the connection - * cache of ours! - */ - conn->inuse = TRUE; - ConnectionStore(data, conn); + /* We have decided that we want a new connection. However, we may not + be able to do that if we have reached the limit of how many + connections we are allowed to open. */ + struct connectbundle *bundle; + + bundle = Curl_conncache_find_bundle(data->state.conn_cache, + conn->host.name); + if(max_host_connections > 0 && bundle && + (bundle->num_connections >= max_host_connections)) { + struct connectdata *conn_candidate; + + /* The bundle is full. Let's see if we can kill a connection. */ + conn_candidate = find_oldest_idle_connection_in_bundle(data, bundle); + + if(conn_candidate) { + /* Set the connection's owner correctly, then kill it */ + conn_candidate->data = data; + (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); + } + else + no_connections_available = TRUE; + } + + if(max_total_connections > 0 && + (data->state.conn_cache->num_connections >= max_total_connections)) { + struct connectdata *conn_candidate; + + /* The cache is full. Let's see if we can kill a connection. */ + conn_candidate = find_oldest_idle_connection(data); + + if(conn_candidate) { + /* Set the connection's owner correctly, then kill it */ + conn_candidate->data = data; + (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); + } + else + no_connections_available = TRUE; + } + + + if(no_connections_available) { + infof(data, "No connections available.\n"); + + conn_free(conn); + *in_connect = NULL; + + return CURLE_NO_CONNECTION_AVAILABLE; + } + else { + /* + * This is a brand new connection, so let's store it in the connection + * cache of ours! + */ + ConnectionStore(data, conn); + } } + /* Mark the connection as used */ + conn->inuse = TRUE; + /* Setup and init stuff before DO starts, in preparing for the transfer. */ do_init(conn); @@ -5167,6 +5314,11 @@ CURLcode Curl_connect(struct SessionHandle *data, } } + if(code == CURLE_NO_CONNECTION_AVAILABLE) { + *in_connect = NULL; + return code; + } + if(code && *in_connect) { /* We're not allowed to return failure with memory left allocated in the connectdata struct, free those here */ @@ -5258,12 +5410,8 @@ CURLcode Curl_done(struct connectdata **connp, state it is for re-using, so we're forced to close it. In a perfect world we can add code that keep track of if we really must close it here or not, but currently we have no such detail knowledge. - - connection_id == -1 here means that the connection has not been added - to the connection cache (OOM) and thus we must disconnect it here. */ - if(data->set.reuse_forbid || conn->bits.close || premature || - (-1 == conn->connection_id)) { + if(data->set.reuse_forbid || conn->bits.close || premature) { CURLcode res2 = Curl_disconnect(conn, premature); /* close connection */ /* If we had an error already, make sure we return that one. But diff --git a/lib/urldata.h b/lib/urldata.h index 1cf7c38b0..b63d8eed6 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -935,17 +935,10 @@ struct connectdata { handle */ bool writechannel_inuse; /* whether the write channel is in use by an easy handle */ - bool server_supports_pipelining; /* TRUE if server supports pipelining, - set after first response */ struct curl_llist *send_pipe; /* List of handles waiting to send on this pipeline */ struct curl_llist *recv_pipe; /* List of handles waiting to read their responses on this pipeline */ - struct curl_llist *pend_pipe; /* List of pending handles on - this pipeline */ - struct curl_llist *done_pipe; /* Handles that are finished, but - still reference this connectdata */ -#define MAX_PIPELINE_LENGTH 5 char* master_buffer; /* The master buffer allocated on-demand; used for pipelining. */ size_t read_pos; /* Current read position in the master buffer */ @@ -1022,8 +1015,7 @@ struct connectdata { TUNNEL_CONNECT, /* CONNECT has been sent off */ TUNNEL_COMPLETE /* CONNECT response received completely */ } tunnel_state[2]; /* two separate ones to allow FTP */ - - struct connectbundle *bundle; /* The bundle we are member of */ + struct connectbundle *bundle; /* The bundle we are member of */ }; /* The end of connectdata. */ @@ -1172,13 +1164,6 @@ struct UrlState { /* buffers to store authentication data in, as parsed from input options */ struct timeval keeps_speed; /* for the progress meter really */ - struct connectdata *pending_conn; /* This points to the connection we want - to open when we are waiting in the - CONNECT_PEND state in the multi - interface. This to avoid recreating it - when we enter the CONNECT state again. - */ - struct connectdata *lastconnect; /* The last connection, NULL if undefined */ char *headerbuff; /* allocated buffer to store headers in */ |