comparison mupdf-source/thirdparty/curl/lib/conncache.c @ 2:b50eed0cc0ef upstream

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