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