diff options
Diffstat (limited to 'libs/libcurl/src/conncache.c')
-rw-r--r-- | libs/libcurl/src/conncache.c | 333 |
1 files changed, 269 insertions, 64 deletions
diff --git a/libs/libcurl/src/conncache.c b/libs/libcurl/src/conncache.c index 48271f7510..f8ef2e88b5 100644 --- a/libs/libcurl/src/conncache.c +++ b/libs/libcurl/src/conncache.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se> - * Copyright (C) 2012 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se> + * Copyright (C) 2012 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at http://curl.haxx.se/docs/copyright.html. + * are also available at https://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 @@ -30,67 +30,163 @@ #include "progress.h" #include "multiif.h" #include "sendf.h" -#include "rawstr.h" -#include "bundles.h" #include "conncache.h" +#include "share.h" +#include "sigpipe.h" +#include "connect.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" -static void free_bundle_hash_entry(void *freethis) +#define CONN_LOCK(x) if((x)->share) \ + Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE) +#define CONN_UNLOCK(x) if((x)->share) \ + Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT) + + +static void conn_llist_dtor(void *user, void *element) { - struct connectbundle *b = (struct connectbundle *) freethis; + struct connectdata *data = element; + (void)user; - Curl_bundle_destroy(b); + data->bundle = NULL; } -struct conncache *Curl_conncache_init(int size) +static CURLcode bundle_create(struct Curl_easy *data, + struct connectbundle **cb_ptr) { - struct conncache *connc; + (void)data; + DEBUGASSERT(*cb_ptr == NULL); + *cb_ptr = malloc(sizeof(struct connectbundle)); + if(!*cb_ptr) + return CURLE_OUT_OF_MEMORY; - connc = calloc(1, sizeof(struct conncache)); - if(!connc) - return NULL; + (*cb_ptr)->num_connections = 0; + (*cb_ptr)->multiuse = BUNDLE_UNKNOWN; - connc->hash = Curl_hash_alloc(size, Curl_hash_str, - Curl_str_key_compare, free_bundle_hash_entry); + Curl_llist_init(&(*cb_ptr)->conn_list, (curl_llist_dtor) conn_llist_dtor); + return CURLE_OK; +} - if(!connc->hash) { - free(connc); - return NULL; - } +static void bundle_destroy(struct connectbundle *cb_ptr) +{ + if(!cb_ptr) + return; - return connc; + Curl_llist_destroy(&cb_ptr->conn_list, NULL); + + free(cb_ptr); } -void Curl_conncache_destroy(struct conncache *connc) +/* Add a connection to a bundle */ +static CURLcode bundle_add_conn(struct connectbundle *cb_ptr, + struct connectdata *conn) { - if(connc) { - Curl_hash_destroy(connc->hash); - connc->hash = NULL; - free(connc); + Curl_llist_insert_next(&cb_ptr->conn_list, cb_ptr->conn_list.tail, conn, + &conn->bundle_node); + conn->bundle = cb_ptr; + cb_ptr->num_connections++; + return CURLE_OK; +} + +/* Remove a connection from a bundle */ +static int bundle_remove_conn(struct connectbundle *cb_ptr, + struct connectdata *conn) +{ + struct curl_llist_element *curr; + + curr = cb_ptr->conn_list.head; + while(curr) { + if(curr->ptr == conn) { + Curl_llist_remove(&cb_ptr->conn_list, curr, NULL); + cb_ptr->num_connections--; + conn->bundle = NULL; + return 1; /* we removed a handle */ + } + curr = curr->next; } + return 0; } -struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc, - char *hostname) +static void free_bundle_hash_entry(void *freethis) { - struct connectbundle *bundle = NULL; + struct connectbundle *b = (struct connectbundle *) freethis; + bundle_destroy(b); +} + +int Curl_conncache_init(struct conncache *connc, int size) +{ + int rc; + + /* allocate a new easy handle to use when closing cached connections */ + connc->closure_handle = curl_easy_init(); + if(!connc->closure_handle) + return 1; /* bad */ + + rc = Curl_hash_init(&connc->hash, size, Curl_hash_str, + Curl_str_key_compare, free_bundle_hash_entry); + if(rc) { + Curl_close(connc->closure_handle); + connc->closure_handle = NULL; + } + else + connc->closure_handle->state.conn_cache = connc; + + return rc; +} + +void Curl_conncache_destroy(struct conncache *connc) +{ if(connc) - bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1); + Curl_hash_destroy(&connc->hash); +} + +/* creates a key to find a bundle for this connection */ +static void hashkey(struct connectdata *conn, char *buf, + size_t len) /* something like 128 is fine */ +{ + const char *hostname; + + if(conn->bits.socksproxy) + hostname = conn->socks_proxy.host.name; + else if(conn->bits.httpproxy) + hostname = conn->http_proxy.host.name; + else if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + + DEBUGASSERT(len > 32); + + /* put the number first so that the hostname gets cut off if too long */ + snprintf(buf, len, "%ld%s", conn->port, hostname); +} + +/* Look up the bundle with all the connections to the same host this + connectdata struct is setup to use. */ +struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn, + struct conncache *connc) +{ + struct connectbundle *bundle = NULL; + if(connc) { + char key[128]; + hashkey(conn, key, sizeof(key)); + CONN_LOCK(conn->data); + bundle = Curl_hash_pick(&connc->hash, key, strlen(key)); + CONN_UNLOCK(conn->data); + } return bundle; } static bool conncache_add_bundle(struct conncache *connc, - char *hostname, + char *key, struct connectbundle *bundle) { - void *p; - - p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle); + void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle); return p?TRUE:FALSE; } @@ -104,14 +200,14 @@ static void conncache_remove_bundle(struct conncache *connc, if(!connc) return; - Curl_hash_start_iterate(connc->hash, &iter); + Curl_hash_start_iterate(&connc->hash, &iter); he = Curl_hash_next_element(&iter); while(he) { if(he->ptr == bundle) { /* The bundle is destroyed by the hash destructor function, free_bundle_hash_entry() */ - Curl_hash_delete(connc->hash, he->key, he->key_len); + Curl_hash_delete(&connc->hash, he->key, he->key_len); return; } @@ -125,32 +221,46 @@ CURLcode Curl_conncache_add_conn(struct conncache *connc, CURLcode result; struct connectbundle *bundle; struct connectbundle *new_bundle = NULL; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; - bundle = Curl_conncache_find_bundle(data->state.conn_cache, - conn->host.name); + bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache); if(!bundle) { - result = Curl_bundle_create(data, &new_bundle); - if(result != CURLE_OK) + int rc; + char key[128]; + + result = bundle_create(data, &new_bundle); + if(result) return result; - if(!conncache_add_bundle(data->state.conn_cache, - conn->host.name, new_bundle)) { - Curl_bundle_destroy(new_bundle); + hashkey(conn, key, sizeof(key)); + CONN_LOCK(data); + rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle); + CONN_UNLOCK(data); + + if(!rc) { + bundle_destroy(new_bundle); return CURLE_OUT_OF_MEMORY; } bundle = new_bundle; } - result = Curl_bundle_add_conn(bundle, conn); - if(result != CURLE_OK) { + CONN_LOCK(data); + result = bundle_add_conn(bundle, conn); + if(result) { if(new_bundle) conncache_remove_bundle(data->state.conn_cache, new_bundle); + CONN_UNLOCK(data); return result; } + CONN_UNLOCK(data); + conn->connection_id = connc->next_connection_id++; connc->num_connections++; + DEBUGF(infof(conn->data, "Added connection %ld. " + "The cache now contains %" CURL_FORMAT_CURL_OFF_TU " members\n", + conn->connection_id, (curl_off_t) connc->num_connections)); + return CURLE_OK; } @@ -162,14 +272,18 @@ void Curl_conncache_remove_conn(struct conncache *connc, /* The bundle pointer can be NULL, since this function can be called due to a failed connection attempt, before being added to a bundle */ if(bundle) { - Curl_bundle_remove_conn(bundle, conn); - if(bundle->num_connections == 0) { + CONN_LOCK(conn->data); + bundle_remove_conn(bundle, conn); + if(bundle->num_connections == 0) conncache_remove_bundle(connc, bundle); - } - connc->num_connections--; + CONN_UNLOCK(conn->data); + if(connc) { + connc->num_connections--; - DEBUGF(infof(conn->data, "The cache now contains %d members\n", - connc->num_connections)); + DEBUGF(infof(conn->data, "The cache now contains %" + CURL_FORMAT_CURL_OFF_TU " members\n", + (curl_off_t) connc->num_connections)); + } } } @@ -179,7 +293,8 @@ void Curl_conncache_remove_conn(struct conncache *connc, Return 0 from func() to continue the loop, return 1 to abort it. */ -void Curl_conncache_foreach(struct conncache *connc, +void Curl_conncache_foreach(struct Curl_easy *data, + struct conncache *connc, void *param, int (*func)(struct connectdata *conn, void *param)) { @@ -190,47 +305,53 @@ void Curl_conncache_foreach(struct conncache *connc, if(!connc) return; - Curl_hash_start_iterate(connc->hash, &iter); + CONN_LOCK(data); + Curl_hash_start_iterate(&connc->hash, &iter); he = Curl_hash_next_element(&iter); while(he) { struct connectbundle *bundle; - struct connectdata *conn; bundle = he->ptr; + he = Curl_hash_next_element(&iter); - curr = bundle->conn_list->head; + curr = bundle->conn_list.head; while(curr) { /* Yes, we need to update curr before calling func(), because func() might decide to remove the connection */ - conn = curr->ptr; + struct connectdata *conn = curr->ptr; curr = curr->next; - if(1 == func(conn, param)) + if(1 == func(conn, param)) { + CONN_UNLOCK(data); return; + } } - - he = Curl_hash_next_element(&iter); } + CONN_UNLOCK(data); } /* Return the first connection found in the cache. Used when closing all - connections */ + connections. + + NOTE: no locking is done here as this is presumably only done when cleaning + up a cache! +*/ struct connectdata * Curl_conncache_find_first_connection(struct conncache *connc) { struct curl_hash_iterator iter; - struct curl_llist_element *curr; struct curl_hash_element *he; struct connectbundle *bundle; - Curl_hash_start_iterate(connc->hash, &iter); + Curl_hash_start_iterate(&connc->hash, &iter); he = Curl_hash_next_element(&iter); while(he) { + struct curl_llist_element *curr; bundle = he->ptr; - curr = bundle->conn_list->head; + curr = bundle->conn_list.head; if(curr) { return curr->ptr; } @@ -241,6 +362,90 @@ Curl_conncache_find_first_connection(struct conncache *connc) return NULL; } +/* + * 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. + */ +struct connectdata * +Curl_conncache_oldest_idle(struct Curl_easy *data) +{ + struct conncache *bc = data->state.conn_cache; + struct curl_hash_iterator iter; + struct curl_llist_element *curr; + struct curl_hash_element *he; + timediff_t highscore =- 1; + timediff_t score; + struct curltime now; + struct connectdata *conn_candidate = NULL; + struct connectbundle *bundle; + + now = Curl_now(); + + CONN_LOCK(data); + Curl_hash_start_iterate(&bc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectdata *conn; + + bundle = he->ptr; + + 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_timediff(now, conn->now); + + if(score > highscore) { + highscore = score; + conn_candidate = conn; + } + } + curr = curr->next; + } + + he = Curl_hash_next_element(&iter); + } + CONN_UNLOCK(data); + + return conn_candidate; +} + +void Curl_conncache_close_all_connections(struct conncache *connc) +{ + struct connectdata *conn; + + conn = Curl_conncache_find_first_connection(connc); + while(conn) { + SIGPIPE_VARIABLE(pipe_st); + conn->data = connc->closure_handle; + + sigpipe_ignore(conn->data, &pipe_st); + conn->data->easy_conn = NULL; /* clear the easy handle's connection + pointer */ + /* This will remove the connection from the cache */ + connclose(conn, "kill all"); + (void)Curl_disconnect(conn, FALSE); + sigpipe_restore(&pipe_st); + + conn = Curl_conncache_find_first_connection(connc); + } + + if(connc->closure_handle) { + SIGPIPE_VARIABLE(pipe_st); + sigpipe_ignore(connc->closure_handle, &pipe_st); + + Curl_hostcache_clean(connc->closure_handle, + connc->closure_handle->dns.hostcache); + Curl_close(connc->closure_handle); + sigpipe_restore(&pipe_st); + } +} #if 0 /* Useful for debugging the connection cache */ |