6e03caf3c9881f3d6f918f823ba8e1bba06632e2
[platform/upstream/curl.git] / lib / conncache.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se>
9  * Copyright (C) 2012 - 2015, 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 "rawstr.h"
34 #include "conncache.h"
35 #include "curl_printf.h"
36
37 #include "curl_memory.h"
38 /* The last #include file should be: */
39 #include "memdebug.h"
40
41 static void conn_llist_dtor(void *user, void *element)
42 {
43   struct connectdata *data = element;
44   (void)user;
45
46   data->bundle = NULL;
47 }
48
49 static CURLcode bundle_create(struct SessionHandle *data,
50                               struct connectbundle **cb_ptr)
51 {
52   (void)data;
53   DEBUGASSERT(*cb_ptr == NULL);
54   *cb_ptr = malloc(sizeof(struct connectbundle));
55   if(!*cb_ptr)
56     return CURLE_OUT_OF_MEMORY;
57
58   (*cb_ptr)->num_connections = 0;
59   (*cb_ptr)->multiuse = BUNDLE_UNKNOWN;
60
61   (*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor);
62   if(!(*cb_ptr)->conn_list) {
63     Curl_safefree(*cb_ptr);
64     return CURLE_OUT_OF_MEMORY;
65   }
66   return CURLE_OK;
67 }
68
69 static void bundle_destroy(struct connectbundle *cb_ptr)
70 {
71   if(!cb_ptr)
72     return;
73
74   if(cb_ptr->conn_list) {
75     Curl_llist_destroy(cb_ptr->conn_list, NULL);
76     cb_ptr->conn_list = NULL;
77   }
78   free(cb_ptr);
79 }
80
81 /* Add a connection to a bundle */
82 static CURLcode bundle_add_conn(struct connectbundle *cb_ptr,
83                               struct connectdata *conn)
84 {
85   if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn))
86     return CURLE_OUT_OF_MEMORY;
87
88   conn->bundle = cb_ptr;
89
90   cb_ptr->num_connections++;
91   return CURLE_OK;
92 }
93
94 /* Remove a connection from a bundle */
95 static int bundle_remove_conn(struct connectbundle *cb_ptr,
96                               struct connectdata *conn)
97 {
98   struct curl_llist_element *curr;
99
100   curr = cb_ptr->conn_list->head;
101   while(curr) {
102     if(curr->ptr == conn) {
103       Curl_llist_remove(cb_ptr->conn_list, curr, NULL);
104       cb_ptr->num_connections--;
105       conn->bundle = NULL;
106       return 1; /* we removed a handle */
107     }
108     curr = curr->next;
109   }
110   return 0;
111 }
112
113 static void free_bundle_hash_entry(void *freethis)
114 {
115   struct connectbundle *b = (struct connectbundle *) freethis;
116
117   bundle_destroy(b);
118 }
119
120 int Curl_conncache_init(struct conncache *connc, int size)
121 {
122   return Curl_hash_init(&connc->hash, size, Curl_hash_str,
123                         Curl_str_key_compare, free_bundle_hash_entry);
124 }
125
126 void Curl_conncache_destroy(struct conncache *connc)
127 {
128   if(connc)
129     Curl_hash_destroy(&connc->hash);
130 }
131
132 /* returns an allocated key to find a bundle for this connection */
133 static char *hashkey(struct connectdata *conn)
134 {
135   return aprintf("%s:%d",
136                  conn->bits.proxy?conn->proxy.name:conn->host.name,
137                  conn->localport);
138 }
139
140 /* Look up the bundle with all the connections to the same host this
141    connectdata struct is setup to use. */
142 struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn,
143                                                  struct conncache *connc)
144 {
145   struct connectbundle *bundle = NULL;
146   if(connc) {
147     char *key = hashkey(conn);
148     if(key) {
149       bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
150       free(key);
151     }
152   }
153
154   return bundle;
155 }
156
157 static bool conncache_add_bundle(struct conncache *connc,
158                                  char *key,
159                                  struct connectbundle *bundle)
160 {
161   void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle);
162
163   return p?TRUE:FALSE;
164 }
165
166 static void conncache_remove_bundle(struct conncache *connc,
167                                     struct connectbundle *bundle)
168 {
169   struct curl_hash_iterator iter;
170   struct curl_hash_element *he;
171
172   if(!connc)
173     return;
174
175   Curl_hash_start_iterate(&connc->hash, &iter);
176
177   he = Curl_hash_next_element(&iter);
178   while(he) {
179     if(he->ptr == bundle) {
180       /* The bundle is destroyed by the hash destructor function,
181          free_bundle_hash_entry() */
182       Curl_hash_delete(&connc->hash, he->key, he->key_len);
183       return;
184     }
185
186     he = Curl_hash_next_element(&iter);
187   }
188 }
189
190 CURLcode Curl_conncache_add_conn(struct conncache *connc,
191                                  struct connectdata *conn)
192 {
193   CURLcode result;
194   struct connectbundle *bundle;
195   struct connectbundle *new_bundle = NULL;
196   struct SessionHandle *data = conn->data;
197
198   bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache);
199   if(!bundle) {
200     char *key;
201     int rc;
202
203     result = bundle_create(data, &new_bundle);
204     if(result)
205       return result;
206
207     key = hashkey(conn);
208     if(!key) {
209       bundle_destroy(new_bundle);
210       return CURLE_OUT_OF_MEMORY;
211     }
212
213     rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle);
214     free(key);
215     if(!rc) {
216       bundle_destroy(new_bundle);
217       return CURLE_OUT_OF_MEMORY;
218     }
219     bundle = new_bundle;
220   }
221
222   result = bundle_add_conn(bundle, conn);
223   if(result) {
224     if(new_bundle)
225       conncache_remove_bundle(data->state.conn_cache, new_bundle);
226     return result;
227   }
228
229   conn->connection_id = connc->next_connection_id++;
230   connc->num_connections++;
231
232   DEBUGF(infof(conn->data, "Added connection %ld. "
233                "The cache now contains %" CURL_FORMAT_CURL_OFF_TU " members\n",
234                conn->connection_id, (curl_off_t) connc->num_connections));
235
236   return CURLE_OK;
237 }
238
239 void Curl_conncache_remove_conn(struct conncache *connc,
240                                 struct connectdata *conn)
241 {
242   struct connectbundle *bundle = conn->bundle;
243
244   /* The bundle pointer can be NULL, since this function can be called
245      due to a failed connection attempt, before being added to a bundle */
246   if(bundle) {
247     bundle_remove_conn(bundle, conn);
248     if(bundle->num_connections == 0) {
249       conncache_remove_bundle(connc, bundle);
250     }
251
252     if(connc) {
253       connc->num_connections--;
254
255       DEBUGF(infof(conn->data, "The cache now contains %"
256                    CURL_FORMAT_CURL_OFF_TU " members\n",
257                    (curl_off_t) connc->num_connections));
258     }
259   }
260 }
261
262 /* This function iterates the entire connection cache and calls the
263    function func() with the connection pointer as the first argument
264    and the supplied 'param' argument as the other,
265
266    Return 0 from func() to continue the loop, return 1 to abort it.
267  */
268 void Curl_conncache_foreach(struct conncache *connc,
269                             void *param,
270                             int (*func)(struct connectdata *conn, void *param))
271 {
272   struct curl_hash_iterator iter;
273   struct curl_llist_element *curr;
274   struct curl_hash_element *he;
275
276   if(!connc)
277     return;
278
279   Curl_hash_start_iterate(&connc->hash, &iter);
280
281   he = Curl_hash_next_element(&iter);
282   while(he) {
283     struct connectbundle *bundle;
284
285     bundle = he->ptr;
286     he = Curl_hash_next_element(&iter);
287
288     curr = bundle->conn_list->head;
289     while(curr) {
290       /* Yes, we need to update curr before calling func(), because func()
291          might decide to remove the connection */
292       struct connectdata *conn = curr->ptr;
293       curr = curr->next;
294
295       if(1 == func(conn, param))
296         return;
297     }
298   }
299 }
300
301 /* Return the first connection found in the cache. Used when closing all
302    connections */
303 struct connectdata *
304 Curl_conncache_find_first_connection(struct conncache *connc)
305 {
306   struct curl_hash_iterator iter;
307   struct curl_hash_element *he;
308   struct connectbundle *bundle;
309
310   Curl_hash_start_iterate(&connc->hash, &iter);
311
312   he = Curl_hash_next_element(&iter);
313   while(he) {
314     struct curl_llist_element *curr;
315     bundle = he->ptr;
316
317     curr = bundle->conn_list->head;
318     if(curr) {
319       return curr->ptr;
320     }
321
322     he = Curl_hash_next_element(&iter);
323   }
324
325   return NULL;
326 }
327
328
329 #if 0
330 /* Useful for debugging the connection cache */
331 void Curl_conncache_print(struct conncache *connc)
332 {
333   struct curl_hash_iterator iter;
334   struct curl_llist_element *curr;
335   struct curl_hash_element *he;
336
337   if(!connc)
338     return;
339
340   fprintf(stderr, "=Bundle cache=\n");
341
342   Curl_hash_start_iterate(connc->hash, &iter);
343
344   he = Curl_hash_next_element(&iter);
345   while(he) {
346     struct connectbundle *bundle;
347     struct connectdata *conn;
348
349     bundle = he->ptr;
350
351     fprintf(stderr, "%s -", he->key);
352     curr = bundle->conn_list->head;
353     while(curr) {
354       conn = curr->ptr;
355
356       fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
357       curr = curr->next;
358     }
359     fprintf(stderr, "\n");
360
361     he = Curl_hash_next_element(&iter);
362   }
363 }
364 #endif