Annotation of embedaddon/curl/lib/conncache.c, revision 1.1.1.1

1.1       misho       1: /***************************************************************************
                      2:  *                                  _   _ ____  _
                      3:  *  Project                     ___| | | |  _ \| |
                      4:  *                             / __| | | | |_) | |
                      5:  *                            | (__| |_| |  _ <| |___
                      6:  *                             \___|\___/|_| \_\_____|
                      7:  *
                      8:  * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se>
                      9:  * Copyright (C) 2012 - 2020, Daniel Stenberg, <daniel@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 "conncache.h"
                     34: #include "share.h"
                     35: #include "sigpipe.h"
                     36: #include "connect.h"
                     37: 
                     38: /* The last 3 #include files should be in this order */
                     39: #include "curl_printf.h"
                     40: #include "curl_memory.h"
                     41: #include "memdebug.h"
                     42: 
                     43: #define HASHKEY_SIZE 128
                     44: 
                     45: static void conn_llist_dtor(void *user, void *element)
                     46: {
                     47:   struct connectdata *conn = element;
                     48:   (void)user;
                     49:   conn->bundle = NULL;
                     50: }
                     51: 
                     52: static CURLcode bundle_create(struct Curl_easy *data,
                     53:                               struct connectbundle **cb_ptr)
                     54: {
                     55:   (void)data;
                     56:   DEBUGASSERT(*cb_ptr == NULL);
                     57:   *cb_ptr = malloc(sizeof(struct connectbundle));
                     58:   if(!*cb_ptr)
                     59:     return CURLE_OUT_OF_MEMORY;
                     60: 
                     61:   (*cb_ptr)->num_connections = 0;
                     62:   (*cb_ptr)->multiuse = BUNDLE_UNKNOWN;
                     63: 
                     64:   Curl_llist_init(&(*cb_ptr)->conn_list, (curl_llist_dtor) conn_llist_dtor);
                     65:   return CURLE_OK;
                     66: }
                     67: 
                     68: static void bundle_destroy(struct connectbundle *cb_ptr)
                     69: {
                     70:   if(!cb_ptr)
                     71:     return;
                     72: 
                     73:   Curl_llist_destroy(&cb_ptr->conn_list, NULL);
                     74: 
                     75:   free(cb_ptr);
                     76: }
                     77: 
                     78: /* Add a connection to a bundle */
                     79: static void bundle_add_conn(struct connectbundle *cb_ptr,
                     80:                             struct connectdata *conn)
                     81: {
                     82:   Curl_llist_insert_next(&cb_ptr->conn_list, cb_ptr->conn_list.tail, conn,
                     83:                          &conn->bundle_node);
                     84:   conn->bundle = cb_ptr;
                     85:   cb_ptr->num_connections++;
                     86: }
                     87: 
                     88: /* Remove a connection from a bundle */
                     89: static int bundle_remove_conn(struct connectbundle *cb_ptr,
                     90:                               struct connectdata *conn)
                     91: {
                     92:   struct curl_llist_element *curr;
                     93: 
                     94:   curr = cb_ptr->conn_list.head;
                     95:   while(curr) {
                     96:     if(curr->ptr == conn) {
                     97:       Curl_llist_remove(&cb_ptr->conn_list, curr, NULL);
                     98:       cb_ptr->num_connections--;
                     99:       conn->bundle = NULL;
                    100:       return 1; /* we removed a handle */
                    101:     }
                    102:     curr = curr->next;
                    103:   }
                    104:   DEBUGASSERT(0);
                    105:   return 0;
                    106: }
                    107: 
                    108: static void free_bundle_hash_entry(void *freethis)
                    109: {
                    110:   struct connectbundle *b = (struct connectbundle *) freethis;
                    111: 
                    112:   bundle_destroy(b);
                    113: }
                    114: 
                    115: int Curl_conncache_init(struct conncache *connc, int size)
                    116: {
                    117:   int rc;
                    118: 
                    119:   /* allocate a new easy handle to use when closing cached connections */
                    120:   connc->closure_handle = curl_easy_init();
                    121:   if(!connc->closure_handle)
                    122:     return 1; /* bad */
                    123: 
                    124:   rc = Curl_hash_init(&connc->hash, size, Curl_hash_str,
                    125:                       Curl_str_key_compare, free_bundle_hash_entry);
                    126:   if(rc)
                    127:     Curl_close(&connc->closure_handle);
                    128:   else
                    129:     connc->closure_handle->state.conn_cache = connc;
                    130: 
                    131:   return rc;
                    132: }
                    133: 
                    134: void Curl_conncache_destroy(struct conncache *connc)
                    135: {
                    136:   if(connc)
                    137:     Curl_hash_destroy(&connc->hash);
                    138: }
                    139: 
                    140: /* creates a key to find a bundle for this connection */
                    141: static void hashkey(struct connectdata *conn, char *buf,
                    142:                     size_t len,  /* something like 128 is fine */
                    143:                     const char **hostp)
                    144: {
                    145:   const char *hostname;
                    146:   long port = conn->remote_port;
                    147: 
                    148:   if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
                    149:     hostname = conn->http_proxy.host.name;
                    150:     port = conn->port;
                    151:   }
                    152:   else if(conn->bits.conn_to_host)
                    153:     hostname = conn->conn_to_host.name;
                    154:   else
                    155:     hostname = conn->host.name;
                    156: 
                    157:   if(hostp)
                    158:     /* report back which name we used */
                    159:     *hostp = hostname;
                    160: 
                    161:   /* put the number first so that the hostname gets cut off if too long */
                    162:   msnprintf(buf, len, "%ld%s", port, hostname);
                    163: }
                    164: 
                    165: void Curl_conncache_unlock(struct Curl_easy *data)
                    166: {
                    167:   CONN_UNLOCK(data);
                    168: }
                    169: 
                    170: /* Returns number of connections currently held in the connection cache.
                    171:    Locks/unlocks the cache itself!
                    172: */
                    173: size_t Curl_conncache_size(struct Curl_easy *data)
                    174: {
                    175:   size_t num;
                    176:   CONN_LOCK(data);
                    177:   num = data->state.conn_cache->num_conn;
                    178:   CONN_UNLOCK(data);
                    179:   return num;
                    180: }
                    181: 
                    182: /* Look up the bundle with all the connections to the same host this
                    183:    connectdata struct is setup to use.
                    184: 
                    185:    **NOTE**: When it returns, it holds the connection cache lock! */
                    186: struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn,
                    187:                                                  struct conncache *connc,
                    188:                                                  const char **hostp)
                    189: {
                    190:   struct connectbundle *bundle = NULL;
                    191:   CONN_LOCK(conn->data);
                    192:   if(connc) {
                    193:     char key[HASHKEY_SIZE];
                    194:     hashkey(conn, key, sizeof(key), hostp);
                    195:     bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
                    196:   }
                    197: 
                    198:   return bundle;
                    199: }
                    200: 
                    201: static bool conncache_add_bundle(struct conncache *connc,
                    202:                                  char *key,
                    203:                                  struct connectbundle *bundle)
                    204: {
                    205:   void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle);
                    206: 
                    207:   return p?TRUE:FALSE;
                    208: }
                    209: 
                    210: static void conncache_remove_bundle(struct conncache *connc,
                    211:                                     struct connectbundle *bundle)
                    212: {
                    213:   struct curl_hash_iterator iter;
                    214:   struct curl_hash_element *he;
                    215: 
                    216:   if(!connc)
                    217:     return;
                    218: 
                    219:   Curl_hash_start_iterate(&connc->hash, &iter);
                    220: 
                    221:   he = Curl_hash_next_element(&iter);
                    222:   while(he) {
                    223:     if(he->ptr == bundle) {
                    224:       /* The bundle is destroyed by the hash destructor function,
                    225:          free_bundle_hash_entry() */
                    226:       Curl_hash_delete(&connc->hash, he->key, he->key_len);
                    227:       return;
                    228:     }
                    229: 
                    230:     he = Curl_hash_next_element(&iter);
                    231:   }
                    232: }
                    233: 
                    234: CURLcode Curl_conncache_add_conn(struct conncache *connc,
                    235:                                  struct connectdata *conn)
                    236: {
                    237:   CURLcode result = CURLE_OK;
                    238:   struct connectbundle *bundle;
                    239:   struct connectbundle *new_bundle = NULL;
                    240:   struct Curl_easy *data = conn->data;
                    241: 
                    242:   /* *find_bundle() locks the connection cache */
                    243:   bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache, NULL);
                    244:   if(!bundle) {
                    245:     int rc;
                    246:     char key[HASHKEY_SIZE];
                    247: 
                    248:     result = bundle_create(data, &new_bundle);
                    249:     if(result) {
                    250:       goto unlock;
                    251:     }
                    252: 
                    253:     hashkey(conn, key, sizeof(key), NULL);
                    254:     rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle);
                    255: 
                    256:     if(!rc) {
                    257:       bundle_destroy(new_bundle);
                    258:       result = CURLE_OUT_OF_MEMORY;
                    259:       goto unlock;
                    260:     }
                    261:     bundle = new_bundle;
                    262:   }
                    263: 
                    264:   bundle_add_conn(bundle, conn);
                    265:   conn->connection_id = connc->next_connection_id++;
                    266:   connc->num_conn++;
                    267: 
                    268:   DEBUGF(infof(conn->data, "Added connection %ld. "
                    269:                "The cache now contains %zu members\n",
                    270:                conn->connection_id, connc->num_conn));
                    271: 
                    272:   unlock:
                    273:   CONN_UNLOCK(data);
                    274: 
                    275:   return result;
                    276: }
                    277: 
                    278: /*
                    279:  * Removes the connectdata object from the connection cache *and* clears the
                    280:  * ->data pointer association. Pass TRUE/FALSE in the 'lock' argument
                    281:  * depending on if the parent function already holds the lock or not.
                    282:  */
                    283: void Curl_conncache_remove_conn(struct Curl_easy *data,
                    284:                                 struct connectdata *conn, bool lock)
                    285: {
                    286:   struct connectbundle *bundle = conn->bundle;
                    287:   struct conncache *connc = data->state.conn_cache;
                    288: 
                    289:   /* The bundle pointer can be NULL, since this function can be called
                    290:      due to a failed connection attempt, before being added to a bundle */
                    291:   if(bundle) {
                    292:     if(lock) {
                    293:       CONN_LOCK(data);
                    294:     }
                    295:     bundle_remove_conn(bundle, conn);
                    296:     if(bundle->num_connections == 0)
                    297:       conncache_remove_bundle(connc, bundle);
                    298:     conn->bundle = NULL; /* removed from it */
                    299:     if(connc) {
                    300:       connc->num_conn--;
                    301:       DEBUGF(infof(data, "The cache now contains %zu members\n",
                    302:                    connc->num_conn));
                    303:     }
                    304:     conn->data = NULL; /* clear the association */
                    305:     if(lock) {
                    306:       CONN_UNLOCK(data);
                    307:     }
                    308:   }
                    309: }
                    310: 
                    311: /* This function iterates the entire connection cache and calls the function
                    312:    func() with the connection pointer as the first argument and the supplied
                    313:    'param' argument as the other.
                    314: 
                    315:    The conncache lock is still held when the callback is called. It needs it,
                    316:    so that it can safely continue traversing the lists once the callback
                    317:    returns.
                    318: 
                    319:    Returns 1 if the loop was aborted due to the callback's return code.
                    320: 
                    321:    Return 0 from func() to continue the loop, return 1 to abort it.
                    322:  */
                    323: bool Curl_conncache_foreach(struct Curl_easy *data,
                    324:                             struct conncache *connc,
                    325:                             void *param,
                    326:                             int (*func)(struct connectdata *conn, void *param))
                    327: {
                    328:   struct curl_hash_iterator iter;
                    329:   struct curl_llist_element *curr;
                    330:   struct curl_hash_element *he;
                    331: 
                    332:   if(!connc)
                    333:     return FALSE;
                    334: 
                    335:   CONN_LOCK(data);
                    336:   Curl_hash_start_iterate(&connc->hash, &iter);
                    337: 
                    338:   he = Curl_hash_next_element(&iter);
                    339:   while(he) {
                    340:     struct connectbundle *bundle;
                    341: 
                    342:     bundle = he->ptr;
                    343:     he = Curl_hash_next_element(&iter);
                    344: 
                    345:     curr = bundle->conn_list.head;
                    346:     while(curr) {
                    347:       /* Yes, we need to update curr before calling func(), because func()
                    348:          might decide to remove the connection */
                    349:       struct connectdata *conn = curr->ptr;
                    350:       curr = curr->next;
                    351: 
                    352:       if(1 == func(conn, param)) {
                    353:         CONN_UNLOCK(data);
                    354:         return TRUE;
                    355:       }
                    356:     }
                    357:   }
                    358:   CONN_UNLOCK(data);
                    359:   return FALSE;
                    360: }
                    361: 
                    362: /* Return the first connection found in the cache. Used when closing all
                    363:    connections.
                    364: 
                    365:    NOTE: no locking is done here as this is presumably only done when cleaning
                    366:    up a cache!
                    367: */
                    368: static struct connectdata *
                    369: conncache_find_first_connection(struct conncache *connc)
                    370: {
                    371:   struct curl_hash_iterator iter;
                    372:   struct curl_hash_element *he;
                    373:   struct connectbundle *bundle;
                    374: 
                    375:   Curl_hash_start_iterate(&connc->hash, &iter);
                    376: 
                    377:   he = Curl_hash_next_element(&iter);
                    378:   while(he) {
                    379:     struct curl_llist_element *curr;
                    380:     bundle = he->ptr;
                    381: 
                    382:     curr = bundle->conn_list.head;
                    383:     if(curr) {
                    384:       return curr->ptr;
                    385:     }
                    386: 
                    387:     he = Curl_hash_next_element(&iter);
                    388:   }
                    389: 
                    390:   return NULL;
                    391: }
                    392: 
                    393: /*
                    394:  * Give ownership of a connection back to the connection cache. Might
                    395:  * disconnect the oldest existing in there to make space.
                    396:  *
                    397:  * Return TRUE if stored, FALSE if closed.
                    398:  */
                    399: bool Curl_conncache_return_conn(struct Curl_easy *data,
                    400:                                 struct connectdata *conn)
                    401: {
                    402:   /* data->multi->maxconnects can be negative, deal with it. */
                    403:   size_t maxconnects =
                    404:     (data->multi->maxconnects < 0) ? data->multi->num_easy * 4:
                    405:     data->multi->maxconnects;
                    406:   struct connectdata *conn_candidate = NULL;
                    407: 
                    408:   conn->lastused = Curl_now(); /* it was used up until now */
                    409:   if(maxconnects > 0 &&
                    410:      Curl_conncache_size(data) > maxconnects) {
                    411:     infof(data, "Connection cache is full, closing the oldest one.\n");
                    412: 
                    413:     conn_candidate = Curl_conncache_extract_oldest(data);
                    414:     if(conn_candidate) {
                    415:       /* the winner gets the honour of being disconnected */
                    416:       (void)Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE);
                    417:     }
                    418:   }
                    419: 
                    420:   return (conn_candidate == conn) ? FALSE : TRUE;
                    421: 
                    422: }
                    423: 
                    424: /*
                    425:  * This function finds the connection in the connection bundle that has been
                    426:  * unused for the longest time.
                    427:  *
                    428:  * Does not lock the connection cache!
                    429:  *
                    430:  * Returns the pointer to the oldest idle connection, or NULL if none was
                    431:  * found.
                    432:  */
                    433: struct connectdata *
                    434: Curl_conncache_extract_bundle(struct Curl_easy *data,
                    435:                               struct connectbundle *bundle)
                    436: {
                    437:   struct curl_llist_element *curr;
                    438:   timediff_t highscore = -1;
                    439:   timediff_t score;
                    440:   struct curltime now;
                    441:   struct connectdata *conn_candidate = NULL;
                    442:   struct connectdata *conn;
                    443: 
                    444:   (void)data;
                    445: 
                    446:   now = Curl_now();
                    447: 
                    448:   curr = bundle->conn_list.head;
                    449:   while(curr) {
                    450:     conn = curr->ptr;
                    451: 
                    452:     if(!CONN_INUSE(conn) && !conn->data) {
                    453:       /* Set higher score for the age passed since the connection was used */
                    454:       score = Curl_timediff(now, conn->lastused);
                    455: 
                    456:       if(score > highscore) {
                    457:         highscore = score;
                    458:         conn_candidate = conn;
                    459:       }
                    460:     }
                    461:     curr = curr->next;
                    462:   }
                    463:   if(conn_candidate) {
                    464:     /* remove it to prevent another thread from nicking it */
                    465:     bundle_remove_conn(bundle, conn_candidate);
                    466:     data->state.conn_cache->num_conn--;
                    467:     DEBUGF(infof(data, "The cache now contains %zu members\n",
                    468:                  data->state.conn_cache->num_conn));
                    469:     conn_candidate->data = data; /* associate! */
                    470:   }
                    471: 
                    472:   return conn_candidate;
                    473: }
                    474: 
                    475: /*
                    476:  * This function finds the connection in the connection cache that has been
                    477:  * unused for the longest time and extracts that from the bundle.
                    478:  *
                    479:  * Returns the pointer to the connection, or NULL if none was found.
                    480:  */
                    481: struct connectdata *
                    482: Curl_conncache_extract_oldest(struct Curl_easy *data)
                    483: {
                    484:   struct conncache *connc = data->state.conn_cache;
                    485:   struct curl_hash_iterator iter;
                    486:   struct curl_llist_element *curr;
                    487:   struct curl_hash_element *he;
                    488:   timediff_t highscore =- 1;
                    489:   timediff_t score;
                    490:   struct curltime now;
                    491:   struct connectdata *conn_candidate = NULL;
                    492:   struct connectbundle *bundle;
                    493:   struct connectbundle *bundle_candidate = NULL;
                    494: 
                    495:   now = Curl_now();
                    496: 
                    497:   CONN_LOCK(data);
                    498:   Curl_hash_start_iterate(&connc->hash, &iter);
                    499: 
                    500:   he = Curl_hash_next_element(&iter);
                    501:   while(he) {
                    502:     struct connectdata *conn;
                    503: 
                    504:     bundle = he->ptr;
                    505: 
                    506:     curr = bundle->conn_list.head;
                    507:     while(curr) {
                    508:       conn = curr->ptr;
                    509: 
                    510:       if(!CONN_INUSE(conn) && !conn->data && !conn->bits.close &&
                    511:          !conn->bits.connect_only) {
                    512:         /* Set higher score for the age passed since the connection was used */
                    513:         score = Curl_timediff(now, conn->lastused);
                    514: 
                    515:         if(score > highscore) {
                    516:           highscore = score;
                    517:           conn_candidate = conn;
                    518:           bundle_candidate = bundle;
                    519:         }
                    520:       }
                    521:       curr = curr->next;
                    522:     }
                    523: 
                    524:     he = Curl_hash_next_element(&iter);
                    525:   }
                    526:   if(conn_candidate) {
                    527:     /* remove it to prevent another thread from nicking it */
                    528:     bundle_remove_conn(bundle_candidate, conn_candidate);
                    529:     connc->num_conn--;
                    530:     DEBUGF(infof(data, "The cache now contains %zu members\n",
                    531:                  connc->num_conn));
                    532:     conn_candidate->data = data; /* associate! */
                    533:   }
                    534:   CONN_UNLOCK(data);
                    535: 
                    536:   return conn_candidate;
                    537: }
                    538: 
                    539: void Curl_conncache_close_all_connections(struct conncache *connc)
                    540: {
                    541:   struct connectdata *conn;
                    542: 
                    543:   conn = conncache_find_first_connection(connc);
                    544:   while(conn) {
                    545:     SIGPIPE_VARIABLE(pipe_st);
                    546:     conn->data = connc->closure_handle;
                    547: 
                    548:     sigpipe_ignore(conn->data, &pipe_st);
                    549:     /* This will remove the connection from the cache */
                    550:     connclose(conn, "kill all");
                    551:     (void)Curl_disconnect(connc->closure_handle, conn, FALSE);
                    552:     sigpipe_restore(&pipe_st);
                    553: 
                    554:     conn = conncache_find_first_connection(connc);
                    555:   }
                    556: 
                    557:   if(connc->closure_handle) {
                    558:     SIGPIPE_VARIABLE(pipe_st);
                    559:     sigpipe_ignore(connc->closure_handle, &pipe_st);
                    560: 
                    561:     Curl_hostcache_clean(connc->closure_handle,
                    562:                          connc->closure_handle->dns.hostcache);
                    563:     Curl_close(&connc->closure_handle);
                    564:     sigpipe_restore(&pipe_st);
                    565:   }
                    566: }
                    567: 
                    568: #if 0
                    569: /* Useful for debugging the connection cache */
                    570: void Curl_conncache_print(struct conncache *connc)
                    571: {
                    572:   struct curl_hash_iterator iter;
                    573:   struct curl_llist_element *curr;
                    574:   struct curl_hash_element *he;
                    575: 
                    576:   if(!connc)
                    577:     return;
                    578: 
                    579:   fprintf(stderr, "=Bundle cache=\n");
                    580: 
                    581:   Curl_hash_start_iterate(connc->hash, &iter);
                    582: 
                    583:   he = Curl_hash_next_element(&iter);
                    584:   while(he) {
                    585:     struct connectbundle *bundle;
                    586:     struct connectdata *conn;
                    587: 
                    588:     bundle = he->ptr;
                    589: 
                    590:     fprintf(stderr, "%s -", he->key);
                    591:     curr = bundle->conn_list->head;
                    592:     while(curr) {
                    593:       conn = curr->ptr;
                    594: 
                    595:       fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
                    596:       curr = curr->next;
                    597:     }
                    598:     fprintf(stderr, "\n");
                    599: 
                    600:     he = Curl_hash_next_element(&iter);
                    601:   }
                    602: }
                    603: #endif

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>