bundles connection caching: some out of memory handling fixes
[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, 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 "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 "bundles.h"
35 #include "conncache.h"
36
37 #include "curl_memory.h"
38 /* The last #include file should be: */
39 #include "memdebug.h"
40
41 #define CONNECTION_HASH_SIZE 97
42
43 static void free_bundle_hash_entry(void *freethis)
44 {
45   struct connectbundle *b = (struct connectbundle *) freethis;
46
47   Curl_bundle_destroy(b);
48 }
49
50 struct conncache *Curl_conncache_init(conncachetype type)
51 {
52   struct conncache *connc;
53
54   connc = calloc(1, sizeof(struct conncache));
55   if(!connc)
56     return NULL;
57
58   connc->hash = Curl_hash_alloc(CONNECTION_HASH_SIZE, Curl_hash_str,
59                                 Curl_str_key_compare, free_bundle_hash_entry);
60
61   if(!connc->hash) {
62     free(connc);
63     return NULL;
64   }
65
66   connc->type = type;
67   connc->num_connections = 0;
68
69   return connc;
70 }
71
72 void Curl_conncache_destroy(struct conncache *connc)
73 {
74   if(connc) {
75     Curl_hash_destroy(connc->hash);
76     connc->hash = NULL;
77     free(connc);
78   }
79 }
80
81 struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc,
82                                                  char *hostname)
83 {
84   struct connectbundle *bundle = NULL;
85
86   if(connc)
87     bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1);
88
89   return bundle;
90 }
91
92 static bool conncache_add_bundle(struct conncache *connc,
93                                  char *hostname,
94                                  struct connectbundle *bundle)
95 {
96   void *p;
97
98   p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle);
99
100   return p?TRUE:FALSE;
101 }
102
103 static void conncache_remove_bundle(struct conncache *connc,
104                                     struct connectbundle *bundle)
105 {
106   struct curl_hash_iterator iter;
107   struct curl_hash_element *he;
108
109   if(!connc)
110     return;
111
112   Curl_hash_start_iterate(connc->hash, &iter);
113
114   he = Curl_hash_next_element(&iter);
115   while(he) {
116     if(he->ptr == bundle) {
117       /* The bundle is destroyed by the hash destructor function,
118          free_bundle_hash_entry() */
119       Curl_hash_delete(connc->hash, he->key, he->key_len);
120       return;
121     }
122
123     he = Curl_hash_next_element(&iter);
124   }
125 }
126
127 CURLcode Curl_conncache_add_conn(struct conncache *connc,
128                                  struct connectdata *conn)
129 {
130   CURLcode result;
131   struct connectbundle *bundle;
132   struct connectbundle *new_bundle = NULL;
133   struct SessionHandle *data = conn->data;
134
135   bundle = Curl_conncache_find_bundle(data->state.conn_cache,
136                                       conn->host.name);
137   if(!bundle) {
138     result = Curl_bundle_create(data, &new_bundle);
139     if(result != CURLE_OK)
140       return result;
141
142     if(!conncache_add_bundle(data->state.conn_cache,
143                              conn->host.name, new_bundle)) {
144       Curl_bundle_destroy(new_bundle);
145       return CURLE_OUT_OF_MEMORY;
146     }
147     bundle = new_bundle;
148   }
149
150   result = Curl_bundle_add_conn(bundle, conn);
151   if(result != CURLE_OK) {
152     if(new_bundle)
153       conncache_remove_bundle(data->state.conn_cache, new_bundle);
154     return result;
155   }
156
157   connc->num_connections++;
158
159   return CURLE_OK;
160 }
161
162 void Curl_conncache_remove_conn(struct conncache *connc,
163                                 struct connectdata *conn)
164 {
165   struct connectbundle *bundle = conn->bundle;
166
167   /* The bundle pointer can be NULL, since this function can be called
168      due to a failed connection attempt, before being added to a bundle */
169   if(bundle) {
170     Curl_bundle_remove_conn(bundle, conn);
171     if(bundle->num_connections == 0) {
172       conncache_remove_bundle(connc, bundle);
173     }
174     connc->num_connections--;
175
176     DEBUGF(infof(conn->data, "The cache now contains %d members\n",
177                  connc->num_connections));
178   }
179 }
180
181 /* This function iterates the entire connection cache and calls the
182    function func() with the connection pointer as the first argument
183    and the supplied 'param' argument as the other */
184 void Curl_conncache_foreach(struct conncache *connc,
185                             void *param,
186                             void (*func)(void *conn, void *param))
187 {
188   struct curl_hash_iterator iter;
189   struct curl_llist_element *curr;
190   struct curl_hash_element *he;
191
192   if(!connc)
193     return;
194
195   Curl_hash_start_iterate(connc->hash, &iter);
196
197   he = Curl_hash_next_element(&iter);
198   while(he) {
199     struct connectbundle *bundle;
200     struct connectdata *conn;
201
202     bundle = he->ptr;
203
204     curr = bundle->conn_list->head;
205     while(curr) {
206       /* Yes, we need to update curr before calling func(), because func()
207          might decide to remove the connection */
208       conn = curr->ptr;
209       curr = curr->next;
210
211       func(conn, param);
212     }
213
214     he = Curl_hash_next_element(&iter);
215   }
216 }
217
218 /* Return the first connection found in the cache. Used when closing all
219    connections */
220 struct connectdata *
221 Curl_conncache_find_first_connection(struct conncache *connc)
222 {
223   struct curl_hash_iterator iter;
224   struct curl_llist_element *curr;
225   struct curl_hash_element *he;
226   struct connectbundle *bundle;
227
228   Curl_hash_start_iterate(connc->hash, &iter);
229
230   he = Curl_hash_next_element(&iter);
231   while(he) {
232     bundle = he->ptr;
233
234     curr = bundle->conn_list->head;
235     if(curr) {
236       return curr->ptr;
237     }
238
239     he = Curl_hash_next_element(&iter);
240   }
241
242   return NULL;
243 }
244
245
246 #if 0
247 /* Useful for debugging the connection cache */
248 void Curl_conncache_print(struct conncache *connc)
249 {
250   struct curl_hash_iterator iter;
251   struct curl_llist_element *curr;
252   struct curl_hash_element *he;
253
254   if(!connc)
255     return;
256
257   fprintf(stderr, "=Bundle cache=\n");
258
259   Curl_hash_start_iterate(connc->hash, &iter);
260
261   he = Curl_hash_next_element(&iter);
262   while(he) {
263     struct connectbundle *bundle;
264     struct connectdata *conn;
265
266     bundle = he->ptr;
267
268     fprintf(stderr, "%s -", he->key);
269     curr = bundle->conn_list->head;
270     while(curr) {
271       conn = curr->ptr;
272
273       fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
274       curr = curr->next;
275     }
276     fprintf(stderr, "\n");
277
278     he = Curl_hash_next_element(&iter);
279   }
280 }
281 #endif