VirtualBox

source: vbox/trunk/src/libs/curl-7.64.0/lib/conncache.c@ 86643

Last change on this file since 86643 was 85671, checked in by vboxsync, 4 years ago

Export out internal curl copy to make it a lot simpler to build VBox (OSE) on Windows. bugref:9814

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette