Home | History | Annotate | Download | only in lib
      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) 2012, 2016, Linus Nielsen Feltzing, <linus (at) haxx.se>
      9  * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel (at) haxx.se>, et al.
     10  *
     11  * This software is licensed as described in the file COPYING, which
     12  * you should have received as part of this distribution. The terms
     13  * are also available at https://curl.haxx.se/docs/copyright.html.
     14  *
     15  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     16  * copies of the Software, and permit persons to whom the Software is
     17  * furnished to do so, under the terms of the COPYING file.
     18  *
     19  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     20  * KIND, either express or implied.
     21  *
     22  ***************************************************************************/
     23 
     24 #include "curl_setup.h"
     25 
     26 #include <curl/curl.h>
     27 
     28 #include "urldata.h"
     29 #include "url.h"
     30 #include "progress.h"
     31 #include "multiif.h"
     32 #include "sendf.h"
     33 #include "rawstr.h"
     34 #include "conncache.h"
     35 /* The last 3 #include files should be in this order */
     36 #include "curl_printf.h"
     37 #include "curl_memory.h"
     38 #include "memdebug.h"
     39 
     40 static void conn_llist_dtor(void *user, void *element)
     41 {
     42   struct connectdata *data = element;
     43   (void)user;
     44 
     45   data->bundle = NULL;
     46 }
     47 
     48 static CURLcode bundle_create(struct Curl_easy *data,
     49                               struct connectbundle **cb_ptr)
     50 {
     51   (void)data;
     52   DEBUGASSERT(*cb_ptr == NULL);
     53   *cb_ptr = malloc(sizeof(struct connectbundle));
     54   if(!*cb_ptr)
     55     return CURLE_OUT_OF_MEMORY;
     56 
     57   (*cb_ptr)->num_connections = 0;
     58   (*cb_ptr)->multiuse = BUNDLE_UNKNOWN;
     59 
     60   (*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor);
     61   if(!(*cb_ptr)->conn_list) {
     62     Curl_safefree(*cb_ptr);
     63     return CURLE_OUT_OF_MEMORY;
     64   }
     65   return CURLE_OK;
     66 }
     67 
     68 static void bundle_destroy(struct connectbundle *cb_ptr)
     69 {
     70   if(!cb_ptr)
     71     return;
     72 
     73   if(cb_ptr->conn_list) {
     74     Curl_llist_destroy(cb_ptr->conn_list, NULL);
     75     cb_ptr->conn_list = NULL;
     76   }
     77   free(cb_ptr);
     78 }
     79 
     80 /* Add a connection to a bundle */
     81 static CURLcode bundle_add_conn(struct connectbundle *cb_ptr,
     82                               struct connectdata *conn)
     83 {
     84   if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn))
     85     return CURLE_OUT_OF_MEMORY;
     86 
     87   conn->bundle = cb_ptr;
     88 
     89   cb_ptr->num_connections++;
     90   return CURLE_OK;
     91 }
     92 
     93 /* Remove a connection from a bundle */
     94 static int bundle_remove_conn(struct connectbundle *cb_ptr,
     95                               struct connectdata *conn)
     96 {
     97   struct curl_llist_element *curr;
     98 
     99   curr = cb_ptr->conn_list->head;
    100   while(curr) {
    101     if(curr->ptr == conn) {
    102       Curl_llist_remove(cb_ptr->conn_list, curr, NULL);
    103       cb_ptr->num_connections--;
    104       conn->bundle = NULL;
    105       return 1; /* we removed a handle */
    106     }
    107     curr = curr->next;
    108   }
    109   return 0;
    110 }
    111 
    112 static void free_bundle_hash_entry(void *freethis)
    113 {
    114   struct connectbundle *b = (struct connectbundle *) freethis;
    115 
    116   bundle_destroy(b);
    117 }
    118 
    119 int Curl_conncache_init(struct conncache *connc, int size)
    120 {
    121   return Curl_hash_init(&connc->hash, size, Curl_hash_str,
    122                         Curl_str_key_compare, free_bundle_hash_entry);
    123 }
    124 
    125 void Curl_conncache_destroy(struct conncache *connc)
    126 {
    127   if(connc)
    128     Curl_hash_destroy(&connc->hash);
    129 }
    130 
    131 /* returns an allocated key to find a bundle for this connection */
    132 static char *hashkey(struct connectdata *conn)
    133 {
    134   const char *hostname;
    135 
    136   if(conn->bits.proxy)
    137     hostname = conn->proxy.name;
    138   else if(conn->bits.conn_to_host)
    139     hostname = conn->conn_to_host.name;
    140   else
    141     hostname = conn->host.name;
    142 
    143   return aprintf("%s:%d", hostname, conn->port);
    144 }
    145 
    146 /* Look up the bundle with all the connections to the same host this
    147    connectdata struct is setup to use. */
    148 struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn,
    149                                                  struct conncache *connc)
    150 {
    151   struct connectbundle *bundle = NULL;
    152   if(connc) {
    153     char *key = hashkey(conn);
    154     if(key) {
    155       bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
    156       free(key);
    157     }
    158   }
    159 
    160   return bundle;
    161 }
    162 
    163 static bool conncache_add_bundle(struct conncache *connc,
    164                                  char *key,
    165                                  struct connectbundle *bundle)
    166 {
    167   void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle);
    168 
    169   return p?TRUE:FALSE;
    170 }
    171 
    172 static void conncache_remove_bundle(struct conncache *connc,
    173                                     struct connectbundle *bundle)
    174 {
    175   struct curl_hash_iterator iter;
    176   struct curl_hash_element *he;
    177 
    178   if(!connc)
    179     return;
    180 
    181   Curl_hash_start_iterate(&connc->hash, &iter);
    182 
    183   he = Curl_hash_next_element(&iter);
    184   while(he) {
    185     if(he->ptr == bundle) {
    186       /* The bundle is destroyed by the hash destructor function,
    187          free_bundle_hash_entry() */
    188       Curl_hash_delete(&connc->hash, he->key, he->key_len);
    189       return;
    190     }
    191 
    192     he = Curl_hash_next_element(&iter);
    193   }
    194 }
    195 
    196 CURLcode Curl_conncache_add_conn(struct conncache *connc,
    197                                  struct connectdata *conn)
    198 {
    199   CURLcode result;
    200   struct connectbundle *bundle;
    201   struct connectbundle *new_bundle = NULL;
    202   struct Curl_easy *data = conn->data;
    203 
    204   bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache);
    205   if(!bundle) {
    206     char *key;
    207     int rc;
    208 
    209     result = bundle_create(data, &new_bundle);
    210     if(result)
    211       return result;
    212 
    213     key = hashkey(conn);
    214     if(!key) {
    215       bundle_destroy(new_bundle);
    216       return CURLE_OUT_OF_MEMORY;
    217     }
    218 
    219     rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle);
    220     free(key);
    221     if(!rc) {
    222       bundle_destroy(new_bundle);
    223       return CURLE_OUT_OF_MEMORY;
    224     }
    225     bundle = new_bundle;
    226   }
    227 
    228   result = bundle_add_conn(bundle, conn);
    229   if(result) {
    230     if(new_bundle)
    231       conncache_remove_bundle(data->state.conn_cache, new_bundle);
    232     return result;
    233   }
    234 
    235   conn->connection_id = connc->next_connection_id++;
    236   connc->num_connections++;
    237 
    238   DEBUGF(infof(conn->data, "Added connection %ld. "
    239                "The cache now contains %" CURL_FORMAT_CURL_OFF_TU " members\n",
    240                conn->connection_id, (curl_off_t) connc->num_connections));
    241 
    242   return CURLE_OK;
    243 }
    244 
    245 void Curl_conncache_remove_conn(struct conncache *connc,
    246                                 struct connectdata *conn)
    247 {
    248   struct connectbundle *bundle = conn->bundle;
    249 
    250   /* The bundle pointer can be NULL, since this function can be called
    251      due to a failed connection attempt, before being added to a bundle */
    252   if(bundle) {
    253     bundle_remove_conn(bundle, conn);
    254     if(bundle->num_connections == 0) {
    255       conncache_remove_bundle(connc, bundle);
    256     }
    257 
    258     if(connc) {
    259       connc->num_connections--;
    260 
    261       DEBUGF(infof(conn->data, "The cache now contains %"
    262                    CURL_FORMAT_CURL_OFF_TU " members\n",
    263                    (curl_off_t) connc->num_connections));
    264     }
    265   }
    266 }
    267 
    268 /* This function iterates the entire connection cache and calls the
    269    function func() with the connection pointer as the first argument
    270    and the supplied 'param' argument as the other,
    271 
    272    Return 0 from func() to continue the loop, return 1 to abort it.
    273  */
    274 void Curl_conncache_foreach(struct conncache *connc,
    275                             void *param,
    276                             int (*func)(struct connectdata *conn, void *param))
    277 {
    278   struct curl_hash_iterator iter;
    279   struct curl_llist_element *curr;
    280   struct curl_hash_element *he;
    281 
    282   if(!connc)
    283     return;
    284 
    285   Curl_hash_start_iterate(&connc->hash, &iter);
    286 
    287   he = Curl_hash_next_element(&iter);
    288   while(he) {
    289     struct connectbundle *bundle;
    290 
    291     bundle = he->ptr;
    292     he = Curl_hash_next_element(&iter);
    293 
    294     curr = bundle->conn_list->head;
    295     while(curr) {
    296       /* Yes, we need to update curr before calling func(), because func()
    297          might decide to remove the connection */
    298       struct connectdata *conn = curr->ptr;
    299       curr = curr->next;
    300 
    301       if(1 == func(conn, param))
    302         return;
    303     }
    304   }
    305 }
    306 
    307 /* Return the first connection found in the cache. Used when closing all
    308    connections */
    309 struct connectdata *
    310 Curl_conncache_find_first_connection(struct conncache *connc)
    311 {
    312   struct curl_hash_iterator iter;
    313   struct curl_hash_element *he;
    314   struct connectbundle *bundle;
    315 
    316   Curl_hash_start_iterate(&connc->hash, &iter);
    317 
    318   he = Curl_hash_next_element(&iter);
    319   while(he) {
    320     struct curl_llist_element *curr;
    321     bundle = he->ptr;
    322 
    323     curr = bundle->conn_list->head;
    324     if(curr) {
    325       return curr->ptr;
    326     }
    327 
    328     he = Curl_hash_next_element(&iter);
    329   }
    330 
    331   return NULL;
    332 }
    333 
    334 
    335 #if 0
    336 /* Useful for debugging the connection cache */
    337 void Curl_conncache_print(struct conncache *connc)
    338 {
    339   struct curl_hash_iterator iter;
    340   struct curl_llist_element *curr;
    341   struct curl_hash_element *he;
    342 
    343   if(!connc)
    344     return;
    345 
    346   fprintf(stderr, "=Bundle cache=\n");
    347 
    348   Curl_hash_start_iterate(connc->hash, &iter);
    349 
    350   he = Curl_hash_next_element(&iter);
    351   while(he) {
    352     struct connectbundle *bundle;
    353     struct connectdata *conn;
    354 
    355     bundle = he->ptr;
    356 
    357     fprintf(stderr, "%s -", he->key);
    358     curr = bundle->conn_list->head;
    359     while(curr) {
    360       conn = curr->ptr;
    361 
    362       fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
    363       curr = curr->next;
    364     }
    365     fprintf(stderr, "\n");
    366 
    367     he = Curl_hash_next_element(&iter);
    368   }
    369 }
    370 #endif
    371