Introducing a new persistent connection caching system using "bundles".
[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  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at http://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22
23 #include "setup.h"
24
25 #include <curl/curl.h>
26
27 #include "urldata.h"
28 #include "url.h"
29 #include "progress.h"
30 #include "multiif.h"
31 #include "sendf.h"
32 #include "rawstr.h"
33 #include "bundles.h"
34 #include "conncache.h"
35
36 #include "curl_memory.h"
37 /* The last #include file should be: */
38 #include "memdebug.h"
39
40 #define CONNECTION_HASH_SIZE 97
41
42 static void free_bundle_hash_entry(void *freethis)
43 {
44   struct connectbundle *b = (struct connectbundle *) freethis;
45
46   Curl_bundle_destroy(b);
47 }
48
49 struct conncache *Curl_conncache_init(int type)
50 {
51   struct conncache *connc;
52
53   connc = calloc(1, sizeof(struct conncache));
54   if(!connc)
55     return NULL;
56
57   connc->hash = Curl_hash_alloc(CONNECTION_HASH_SIZE, Curl_hash_str,
58                                 Curl_str_key_compare, free_bundle_hash_entry);
59
60   if(!connc->hash) {
61     free(connc);
62     return NULL;
63   }
64
65   connc->type = type;
66   connc->num_connections = 0;
67
68   return connc;
69 }
70
71 void Curl_conncache_destroy(struct conncache *connc)
72 {
73   Curl_hash_destroy(connc->hash);
74   free(connc);
75 }
76
77 struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc,
78                                                  char *hostname)
79 {
80   struct connectbundle *bundle = NULL;
81
82   if(connc)
83     bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1);
84
85   return bundle;
86 }
87
88 static bool conncache_add_bundle(struct conncache *connc,
89                                  char *hostname,
90                                  struct connectbundle *bundle)
91 {
92   void *p;
93
94   p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle);
95
96   return p?TRUE:FALSE;
97 }
98
99 static void conncache_remove_bundle(struct conncache *connc,
100                                     struct connectbundle *bundle)
101 {
102   struct curl_hash_iterator iter;
103   struct curl_hash_element *he;
104
105   if(!connc)
106     return;
107
108   Curl_hash_start_iterate(connc->hash, &iter);
109
110   he = Curl_hash_next_element(&iter);
111   while(he) {
112     if(he->ptr == bundle) {
113       /* The bundle is destroyed by the hash destructor function,
114          free_bundle_hash_entry() */
115       Curl_hash_delete(connc->hash, he->key, he->key_len);
116       return;
117     }
118
119     he = Curl_hash_next_element(&iter);
120   }
121 }
122
123 CURLcode Curl_conncache_add_conn(struct conncache *connc,
124                                  struct connectdata *conn)
125 {
126   CURLcode result;
127   struct connectbundle *bundle;
128   struct SessionHandle *data = conn->data;
129
130   bundle = Curl_conncache_find_bundle(data->state.conn_cache,
131                                       conn->host.name);
132   if(!bundle) {
133     result = Curl_bundle_create(data, &bundle);
134     if(result != CURLE_OK)
135       return result;
136
137     if(!conncache_add_bundle(data->state.conn_cache,
138                              conn->host.name, bundle))
139       return CURLE_OUT_OF_MEMORY;
140   }
141
142   result = Curl_bundle_add_conn(bundle, conn);
143   if(result != CURLE_OK)
144     return result;
145
146   connc->num_connections++;
147
148   return CURLE_OK;
149 }
150
151 void Curl_conncache_remove_conn(struct conncache *connc,
152                                 struct connectdata *conn)
153 {
154   struct connectbundle *bundle = conn->bundle;
155
156   /* The bundle pointer can be NULL, since this function can be called
157      due to a failed connection attempt, before being added to a bundle */
158   if(bundle) {
159     Curl_bundle_remove_conn(bundle, conn);
160     if(bundle->num_connections == 0) {
161       conncache_remove_bundle(connc, bundle);
162     }
163     connc->num_connections--;
164
165     DEBUGF(infof(conn->data, "The cache now contains %d members\n",
166                  connc->num_connections));
167   }
168 }
169
170 /* This function iterates the entire connection cache and calls the
171    function func() with the connection pointer as the first argument
172    and the supplied 'param' argument as the other */
173 void Curl_conncache_foreach(struct conncache *connc,
174                             void *param,
175                             void (*func)(void *conn, void *param))
176 {
177   struct curl_hash_iterator iter;
178   struct curl_llist_element *curr;
179   struct curl_hash_element *he;
180
181   if(!connc)
182     return;
183
184   Curl_hash_start_iterate(connc->hash, &iter);
185
186   he = Curl_hash_next_element(&iter);
187   while(he) {
188     struct connectbundle *bundle;
189     struct connectdata *conn;
190
191     bundle = he->ptr;
192
193     curr = bundle->conn_list->head;
194     while(curr) {
195       /* Yes, we need to update curr before calling func(), because func()
196          might decide to remove the connection */
197       conn = curr->ptr;
198       curr = curr->next;
199
200       func(conn, param);
201     }
202
203     he = Curl_hash_next_element(&iter);
204   }
205 }
206
207 /* Return the first connection found in the cache. Used when closing all
208    connections */
209 struct connectdata *
210 Curl_conncache_find_first_connection(struct conncache *connc)
211 {
212   struct curl_hash_iterator iter;
213   struct curl_llist_element *curr;
214   struct curl_hash_element *he;
215   struct connectbundle *bundle;
216
217   Curl_hash_start_iterate(connc->hash, &iter);
218
219   he = Curl_hash_next_element(&iter);
220   while(he) {
221     bundle = he->ptr;
222
223     curr = bundle->conn_list->head;
224     if(curr) {
225       return curr->ptr;
226     }
227
228     he = Curl_hash_next_element(&iter);
229   }
230
231   return NULL;
232 }
233
234
235 #if 0
236 /* Useful for debugging the connection cache */
237 void Curl_conncache_print(struct conncache *connc)
238 {
239   struct curl_hash_iterator iter;
240   struct curl_llist_element *curr;
241   struct curl_hash_element *he;
242
243   if(!connc)
244     return;
245
246   fprintf(stderr, "=Bundle cache=\n");
247
248   Curl_hash_start_iterate(connc->hash, &iter);
249
250   he = Curl_hash_next_element(&iter);
251   while(he) {
252     struct connectbundle *bundle;
253     struct connectdata *conn;
254
255     bundle = he->ptr;
256
257     fprintf(stderr, "%s -", he->key);
258     curr = bundle->conn_list->head;
259     while(curr) {
260       conn = curr->ptr;
261
262       fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
263       curr = curr->next;
264     }
265     fprintf(stderr, "\n");
266
267     he = Curl_hash_next_element(&iter);
268   }
269 }
270 #endif