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 http://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
conn_llist_dtor(void * user,void * element)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
bundle_create(struct SessionHandle * data,struct connectbundle ** cb_ptr)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
bundle_destroy(struct connectbundle * cb_ptr)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 */
bundle_add_conn(struct connectbundle * cb_ptr,struct connectdata * conn)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 */
bundle_remove_conn(struct connectbundle * cb_ptr,struct connectdata * conn)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
free_bundle_hash_entry(void * freethis)113 static void free_bundle_hash_entry(void *freethis)
114 {
115 struct connectbundle *b = (struct connectbundle *) freethis;
116
117 bundle_destroy(b);
118 }
119
Curl_conncache_init(struct conncache * connc,int size)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
Curl_conncache_destroy(struct conncache * connc)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 */
hashkey(struct connectdata * conn)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. */
Curl_conncache_find_bundle(struct connectdata * conn,struct conncache * connc)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
conncache_add_bundle(struct conncache * connc,char * key,struct connectbundle * bundle)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
conncache_remove_bundle(struct conncache * connc,struct connectbundle * bundle)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
Curl_conncache_add_conn(struct conncache * connc,struct connectdata * conn)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
Curl_conncache_remove_conn(struct conncache * connc,struct connectdata * conn)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 */
Curl_conncache_foreach(struct conncache * connc,void * param,int (* func)(struct connectdata * conn,void * param))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 *
Curl_conncache_find_first_connection(struct conncache * connc)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
365