1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se>
9 * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
22 ***************************************************************************/
24 #include "curl_setup.h"
26 #include <curl/curl.h>
33 #include "conncache.h"
39 /* The last 3 #include files should be in this order */
40 #include "curl_printf.h"
41 #include "curl_memory.h"
44 #define HASHKEY_SIZE 128
46 static void conn_llist_dtor(void *user, void *element)
48 struct connectdata *conn = element;
53 static CURLcode bundle_create(struct connectbundle **bundlep)
55 DEBUGASSERT(*bundlep == NULL);
56 *bundlep = malloc(sizeof(struct connectbundle));
58 return CURLE_OUT_OF_MEMORY;
60 (*bundlep)->num_connections = 0;
61 (*bundlep)->multiuse = BUNDLE_UNKNOWN;
63 Curl_llist_init(&(*bundlep)->conn_list, (Curl_llist_dtor) conn_llist_dtor);
67 static void bundle_destroy(struct connectbundle *bundle)
72 Curl_llist_destroy(&bundle->conn_list, NULL);
77 /* Add a connection to a bundle */
78 static void bundle_add_conn(struct connectbundle *bundle,
79 struct connectdata *conn)
81 Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn,
83 conn->bundle = bundle;
84 bundle->num_connections++;
87 /* Remove a connection from a bundle */
88 static int bundle_remove_conn(struct connectbundle *bundle,
89 struct connectdata *conn)
91 struct Curl_llist_element *curr;
93 curr = bundle->conn_list.head;
95 if(curr->ptr == conn) {
96 Curl_llist_remove(&bundle->conn_list, curr, NULL);
97 bundle->num_connections--;
99 return 1; /* we removed a handle */
107 static void free_bundle_hash_entry(void *freethis)
109 struct connectbundle *b = (struct connectbundle *) freethis;
114 int Curl_conncache_init(struct conncache *connc, int size)
116 /* allocate a new easy handle to use when closing cached connections */
117 connc->closure_handle = curl_easy_init();
118 if(!connc->closure_handle)
121 Curl_hash_init(&connc->hash, size, Curl_hash_str,
122 Curl_str_key_compare, free_bundle_hash_entry);
123 connc->closure_handle->state.conn_cache = connc;
128 void Curl_conncache_destroy(struct conncache *connc)
131 Curl_hash_destroy(&connc->hash);
134 /* creates a key to find a bundle for this connection */
135 static void hashkey(struct connectdata *conn, char *buf, size_t len)
137 const char *hostname;
138 long port = conn->remote_port;
139 DEBUGASSERT(len >= HASHKEY_SIZE);
140 #ifndef CURL_DISABLE_PROXY
141 if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
142 hostname = conn->http_proxy.host.name;
147 if(conn->bits.conn_to_host)
148 hostname = conn->conn_to_host.name;
150 hostname = conn->host.name;
152 /* put the numbers first so that the hostname gets cut off if too long */
154 msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname);
156 msnprintf(buf, len, "%ld/%s", port, hostname);
158 Curl_strntolower(buf, buf, len);
161 /* Returns number of connections currently held in the connection cache.
162 Locks/unlocks the cache itself!
164 size_t Curl_conncache_size(struct Curl_easy *data)
167 CONNCACHE_LOCK(data);
168 num = data->state.conn_cache->num_conn;
169 CONNCACHE_UNLOCK(data);
173 /* Look up the bundle with all the connections to the same host this
174 connectdata struct is setup to use.
176 **NOTE**: When it returns, it holds the connection cache lock! */
177 struct connectbundle *
178 Curl_conncache_find_bundle(struct Curl_easy *data,
179 struct connectdata *conn,
180 struct conncache *connc)
182 struct connectbundle *bundle = NULL;
183 CONNCACHE_LOCK(data);
185 char key[HASHKEY_SIZE];
186 hashkey(conn, key, sizeof(key));
187 bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
193 static void *conncache_add_bundle(struct conncache *connc,
195 struct connectbundle *bundle)
197 return Curl_hash_add(&connc->hash, key, strlen(key), bundle);
200 static void conncache_remove_bundle(struct conncache *connc,
201 struct connectbundle *bundle)
203 struct Curl_hash_iterator iter;
204 struct Curl_hash_element *he;
209 Curl_hash_start_iterate(&connc->hash, &iter);
211 he = Curl_hash_next_element(&iter);
213 if(he->ptr == bundle) {
214 /* The bundle is destroyed by the hash destructor function,
215 free_bundle_hash_entry() */
216 Curl_hash_delete(&connc->hash, he->key, he->key_len);
220 he = Curl_hash_next_element(&iter);
224 CURLcode Curl_conncache_add_conn(struct Curl_easy *data)
226 CURLcode result = CURLE_OK;
227 struct connectbundle *bundle = NULL;
228 struct connectdata *conn = data->conn;
229 struct conncache *connc = data->state.conn_cache;
232 /* *find_bundle() locks the connection cache */
233 bundle = Curl_conncache_find_bundle(data, conn, data->state.conn_cache);
235 char key[HASHKEY_SIZE];
237 result = bundle_create(&bundle);
242 hashkey(conn, key, sizeof(key));
244 if(!conncache_add_bundle(data->state.conn_cache, key, bundle)) {
245 bundle_destroy(bundle);
246 result = CURLE_OUT_OF_MEMORY;
251 bundle_add_conn(bundle, conn);
252 conn->connection_id = connc->next_connection_id++;
255 DEBUGF(infof(data, "Added connection %ld. "
256 "The cache now contains %zu members",
257 conn->connection_id, connc->num_conn));
260 CONNCACHE_UNLOCK(data);
266 * Removes the connectdata object from the connection cache, but the transfer
267 * still owns this connection.
269 * Pass TRUE/FALSE in the 'lock' argument depending on if the parent function
270 * already holds the lock or not.
272 void Curl_conncache_remove_conn(struct Curl_easy *data,
273 struct connectdata *conn, bool lock)
275 struct connectbundle *bundle = conn->bundle;
276 struct conncache *connc = data->state.conn_cache;
278 /* The bundle pointer can be NULL, since this function can be called
279 due to a failed connection attempt, before being added to a bundle */
282 CONNCACHE_LOCK(data);
284 bundle_remove_conn(bundle, conn);
285 if(bundle->num_connections == 0)
286 conncache_remove_bundle(connc, bundle);
287 conn->bundle = NULL; /* removed from it */
290 DEBUGF(infof(data, "The cache now contains %zu members",
294 CONNCACHE_UNLOCK(data);
299 /* This function iterates the entire connection cache and calls the function
300 func() with the connection pointer as the first argument and the supplied
301 'param' argument as the other.
303 The conncache lock is still held when the callback is called. It needs it,
304 so that it can safely continue traversing the lists once the callback
307 Returns 1 if the loop was aborted due to the callback's return code.
309 Return 0 from func() to continue the loop, return 1 to abort it.
311 bool Curl_conncache_foreach(struct Curl_easy *data,
312 struct conncache *connc,
314 int (*func)(struct Curl_easy *data,
315 struct connectdata *conn, void *param))
317 struct Curl_hash_iterator iter;
318 struct Curl_llist_element *curr;
319 struct Curl_hash_element *he;
324 CONNCACHE_LOCK(data);
325 Curl_hash_start_iterate(&connc->hash, &iter);
327 he = Curl_hash_next_element(&iter);
329 struct connectbundle *bundle;
332 he = Curl_hash_next_element(&iter);
334 curr = bundle->conn_list.head;
336 /* Yes, we need to update curr before calling func(), because func()
337 might decide to remove the connection */
338 struct connectdata *conn = curr->ptr;
341 if(1 == func(data, conn, param)) {
342 CONNCACHE_UNLOCK(data);
347 CONNCACHE_UNLOCK(data);
351 /* Return the first connection found in the cache. Used when closing all
354 NOTE: no locking is done here as this is presumably only done when cleaning
357 static struct connectdata *
358 conncache_find_first_connection(struct conncache *connc)
360 struct Curl_hash_iterator iter;
361 struct Curl_hash_element *he;
362 struct connectbundle *bundle;
364 Curl_hash_start_iterate(&connc->hash, &iter);
366 he = Curl_hash_next_element(&iter);
368 struct Curl_llist_element *curr;
371 curr = bundle->conn_list.head;
376 he = Curl_hash_next_element(&iter);
383 * Give ownership of a connection back to the connection cache. Might
384 * disconnect the oldest existing in there to make space.
386 * Return TRUE if stored, FALSE if closed.
388 bool Curl_conncache_return_conn(struct Curl_easy *data,
389 struct connectdata *conn)
391 /* data->multi->maxconnects can be negative, deal with it. */
393 (data->multi->maxconnects < 0) ? data->multi->num_easy * 4:
394 data->multi->maxconnects;
395 struct connectdata *conn_candidate = NULL;
397 conn->lastused = Curl_now(); /* it was used up until now */
398 if(maxconnects > 0 &&
399 Curl_conncache_size(data) > maxconnects) {
400 infof(data, "Connection cache is full, closing the oldest one");
402 conn_candidate = Curl_conncache_extract_oldest(data);
404 /* the winner gets the honour of being disconnected */
405 Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE);
409 return (conn_candidate == conn) ? FALSE : TRUE;
414 * This function finds the connection in the connection bundle that has been
415 * unused for the longest time.
417 * Does not lock the connection cache!
419 * Returns the pointer to the oldest idle connection, or NULL if none was
423 Curl_conncache_extract_bundle(struct Curl_easy *data,
424 struct connectbundle *bundle)
426 struct Curl_llist_element *curr;
427 timediff_t highscore = -1;
430 struct connectdata *conn_candidate = NULL;
431 struct connectdata *conn;
437 curr = bundle->conn_list.head;
441 if(!CONN_INUSE(conn)) {
442 /* Set higher score for the age passed since the connection was used */
443 score = Curl_timediff(now, conn->lastused);
445 if(score > highscore) {
447 conn_candidate = conn;
453 /* remove it to prevent another thread from nicking it */
454 bundle_remove_conn(bundle, conn_candidate);
455 data->state.conn_cache->num_conn--;
456 DEBUGF(infof(data, "The cache now contains %zu members",
457 data->state.conn_cache->num_conn));
460 return conn_candidate;
464 * This function finds the connection in the connection cache that has been
465 * unused for the longest time and extracts that from the bundle.
467 * Returns the pointer to the connection, or NULL if none was found.
470 Curl_conncache_extract_oldest(struct Curl_easy *data)
472 struct conncache *connc = data->state.conn_cache;
473 struct Curl_hash_iterator iter;
474 struct Curl_llist_element *curr;
475 struct Curl_hash_element *he;
476 timediff_t highscore =- 1;
479 struct connectdata *conn_candidate = NULL;
480 struct connectbundle *bundle;
481 struct connectbundle *bundle_candidate = NULL;
485 CONNCACHE_LOCK(data);
486 Curl_hash_start_iterate(&connc->hash, &iter);
488 he = Curl_hash_next_element(&iter);
490 struct connectdata *conn;
494 curr = bundle->conn_list.head;
498 if(!CONN_INUSE(conn) && !conn->bits.close &&
499 !conn->bits.connect_only) {
500 /* Set higher score for the age passed since the connection was used */
501 score = Curl_timediff(now, conn->lastused);
503 if(score > highscore) {
505 conn_candidate = conn;
506 bundle_candidate = bundle;
512 he = Curl_hash_next_element(&iter);
515 /* remove it to prevent another thread from nicking it */
516 bundle_remove_conn(bundle_candidate, conn_candidate);
518 DEBUGF(infof(data, "The cache now contains %zu members",
521 CONNCACHE_UNLOCK(data);
523 return conn_candidate;
526 void Curl_conncache_close_all_connections(struct conncache *connc)
528 struct connectdata *conn;
529 char buffer[READBUFFER_MIN + 1];
530 SIGPIPE_VARIABLE(pipe_st);
531 if(!connc->closure_handle)
533 connc->closure_handle->state.buffer = buffer;
534 connc->closure_handle->set.buffer_size = READBUFFER_MIN;
536 conn = conncache_find_first_connection(connc);
538 sigpipe_ignore(connc->closure_handle, &pipe_st);
539 /* This will remove the connection from the cache */
540 connclose(conn, "kill all");
541 Curl_conncache_remove_conn(connc->closure_handle, conn, TRUE);
542 Curl_disconnect(connc->closure_handle, conn, FALSE);
543 sigpipe_restore(&pipe_st);
545 conn = conncache_find_first_connection(connc);
548 connc->closure_handle->state.buffer = NULL;
549 sigpipe_ignore(connc->closure_handle, &pipe_st);
551 Curl_hostcache_clean(connc->closure_handle,
552 connc->closure_handle->dns.hostcache);
553 Curl_close(&connc->closure_handle);
554 sigpipe_restore(&pipe_st);
558 /* Useful for debugging the connection cache */
559 void Curl_conncache_print(struct conncache *connc)
561 struct Curl_hash_iterator iter;
562 struct Curl_llist_element *curr;
563 struct Curl_hash_element *he;
568 fprintf(stderr, "=Bundle cache=\n");
570 Curl_hash_start_iterate(connc->hash, &iter);
572 he = Curl_hash_next_element(&iter);
574 struct connectbundle *bundle;
575 struct connectdata *conn;
579 fprintf(stderr, "%s -", he->key);
580 curr = bundle->conn_list->head;
584 fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
587 fprintf(stderr, "\n");
589 he = Curl_hash_next_element(&iter);