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