diff options
Diffstat (limited to 'lib/url.c')
-rw-r--r-- | lib/url.c | 268 |
1 files changed, 208 insertions, 60 deletions
@@ -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 |