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