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