File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / curl / lib / conncache.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Jun 3 10:01:15 2020 UTC (4 years, 1 month ago) by misho
Branches: curl, MAIN
CVS tags: v7_70_0p4, HEAD
curl

    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>