Annotation of embedaddon/curl/lib/conncache.c, revision 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>