Multiple pipelines and limiting the number of connections.
[platform/upstream/curl.git] / lib / pipeline.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2013, 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 "curl_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 "pipeline.h"
32 #include "sendf.h"
33 #include "rawstr.h"
34 #include "bundles.h"
35
36 #include "curl_memory.h"
37 /* The last #include file should be: */
38 #include "memdebug.h"
39
40 struct site_blacklist_entry {
41   char *hostname;
42   unsigned short port;
43 };
44
45 static void site_blacklist_llist_dtor(void *user, void *element)
46 {
47   struct site_blacklist_entry *entry = element;
48   (void)user;
49
50   Curl_safefree(entry->hostname);
51   Curl_safefree(entry);
52 }
53
54 static void server_blacklist_llist_dtor(void *user, void *element)
55 {
56   char *server_name = element;
57   (void)user;
58
59   Curl_safefree(server_name);
60 }
61
62 bool Curl_pipeline_penalized(struct SessionHandle *data,
63                              struct connectdata *conn)
64 {
65   if(data) {
66     bool penalized = FALSE;
67     curl_off_t penalty_size =
68       Curl_multi_content_length_penalty_size(data->multi);
69     curl_off_t chunk_penalty_size =
70       Curl_multi_chunk_length_penalty_size(data->multi);
71     curl_off_t recv_size = -2; /* Make it easy to spot in the log */
72
73     /* Find the head of the recv pipe, if any */
74     if(conn->recv_pipe && conn->recv_pipe->head) {
75       struct SessionHandle *recv_handle = conn->recv_pipe->head->ptr;
76
77       recv_size = recv_handle->req.size;
78
79       if(penalty_size > 0 && recv_size > penalty_size)
80         penalized = TRUE;
81     }
82
83     if(chunk_penalty_size > 0 &&
84        (curl_off_t)conn->chunk.datasize > chunk_penalty_size)
85       penalized = TRUE;
86
87     infof(data, "Conn: %d (%p) Receive pipe weight: (%d/%d), penalized: %d\n",
88           conn->connection_id, conn, recv_size,
89           conn->chunk.datasize, penalized);
90     return penalized;
91   }
92   return FALSE;
93 }
94
95 /* Find the best connection in a bundle to use for the next request */
96 struct connectdata *
97 Curl_bundle_find_best(struct SessionHandle *data,
98                       struct connectbundle *cb_ptr)
99 {
100   struct curl_llist_element *curr;
101   struct connectdata *conn;
102   struct connectdata *best_conn = NULL;
103   size_t pipe_len;
104   size_t best_pipe_len = 99;
105
106   (void)data;
107
108   curr = cb_ptr->conn_list->head;
109   while(curr) {
110     conn = curr->ptr;
111     pipe_len = conn->send_pipe->size + conn->recv_pipe->size;
112
113     if(!Curl_pipeline_penalized(conn->data, conn) &&
114        pipe_len < best_pipe_len) {
115       best_conn = conn;
116       best_pipe_len = pipe_len;
117     }
118     curr = curr->next;
119   }
120
121   /* If we haven't found a connection, i.e all pipelines are penalized
122      or full, just pick one. The request will then be queued in
123      Curl_add_handle_to_pipeline(). */
124   if(!best_conn) {
125     best_conn = cb_ptr->conn_list->head->ptr;
126   }
127   return best_conn;
128 }
129
130 CURLcode Curl_add_handle_to_pipeline(struct SessionHandle *handle,
131                                      struct connectdata *conn)
132 {
133   struct curl_llist_element *sendhead = conn->send_pipe->head;
134   struct curl_llist *pipeline;
135   CURLcode rc;
136
137   pipeline = conn->send_pipe;
138
139   infof(conn->data, "Adding handle: conn: %p\n", conn);
140   infof(conn->data, "Adding handle: send: %d\n", conn->send_pipe->size);
141   infof(conn->data, "Adding handle: recv: %d\n", conn->recv_pipe->size);
142   rc = Curl_addHandleToPipeline(handle, pipeline);
143
144   if(pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) {
145     /* this is a new one as head, expire it */
146     conn->writechannel_inuse = FALSE; /* not in use yet */
147 #ifdef DEBUGBUILD
148     infof(conn->data, "%p is at send pipe head!\n",
149           conn->send_pipe->head->ptr);
150 #endif
151     Curl_expire(conn->send_pipe->head->ptr, 1);
152   }
153
154   print_pipeline(conn);
155
156   return rc;
157 }
158
159 /* Move this transfer from the sending list to the receiving list.
160
161    Pay special attention to the new sending list "leader" as it needs to get
162    checked to update what sockets it acts on.
163
164 */
165 void Curl_move_handle_from_send_to_recv_pipe(struct SessionHandle *handle,
166                                              struct connectdata *conn)
167 {
168   struct curl_llist_element *curr;
169
170   curr = conn->send_pipe->head;
171   while(curr) {
172     if(curr->ptr == handle) {
173       Curl_llist_move(conn->send_pipe, curr,
174                       conn->recv_pipe, conn->recv_pipe->tail);
175
176       if(conn->send_pipe->head) {
177         /* Since there's a new easy handle at the start of the send pipeline,
178            set its timeout value to 1ms to make it trigger instantly */
179         conn->writechannel_inuse = FALSE; /* not used now */
180 #ifdef DEBUGBUILD
181         infof(conn->data, "%p is at send pipe head B!\n",
182               conn->send_pipe->head->ptr);
183 #endif
184         Curl_expire(conn->send_pipe->head->ptr, 1);
185       }
186
187       /* The receiver's list is not really interesting here since either this
188          handle is now first in the list and we'll deal with it soon, or
189          another handle is already first and thus is already taken care of */
190
191       break; /* we're done! */
192     }
193     curr = curr->next;
194   }
195 }
196
197 bool Curl_pipeline_site_blacklisted(struct SessionHandle *handle,
198                                     struct connectdata *conn)
199 {
200   if(handle->multi) {
201     struct curl_llist *blacklist =
202       Curl_multi_pipelining_site_bl(handle->multi);
203
204     if(blacklist) {
205       struct curl_llist_element *curr;
206
207       curr = blacklist->head;
208       while(curr) {
209         struct site_blacklist_entry *site;
210
211         site = curr->ptr;
212         if(Curl_raw_equal(site->hostname, conn->host.name) &&
213            site->port == conn->remote_port) {
214           infof(handle, "Site %s:%d is pipeline blacklisted\n",
215                 conn->host.name, conn->remote_port);
216           return TRUE;
217         }
218         curr = curr->next;
219       }
220     }
221   }
222   return FALSE;
223 }
224
225 CURLMcode Curl_pipeline_set_site_blacklist(char **sites,
226                                            struct curl_llist **list_ptr)
227 {
228   struct curl_llist *old_list = *list_ptr;
229   struct curl_llist *new_list = NULL;
230
231   if(sites) {
232     new_list = Curl_llist_alloc((curl_llist_dtor) site_blacklist_llist_dtor);
233     if(!new_list)
234       return CURLM_OUT_OF_MEMORY;
235
236     /* Parse the URLs and populate the list */
237     while(*sites) {
238       char *hostname;
239       char *port;
240       struct site_blacklist_entry *entry;
241
242       entry = malloc(sizeof(struct site_blacklist_entry));
243
244       hostname = strdup(*sites);
245       if(!hostname)
246         return CURLM_OUT_OF_MEMORY;
247
248       port = strchr(hostname, ':');
249       if(port) {
250         *port = '\0';
251         port++;
252         entry->port = (unsigned short)strtol(port, NULL, 10);
253       }
254       else {
255         /* Default port number for HTTP */
256         entry->port = 80;
257       }
258
259       entry->hostname = hostname;
260
261       if(!Curl_llist_insert_next(new_list, new_list->tail, entry))
262         return CURLM_OUT_OF_MEMORY;
263
264       sites++;
265     }
266   }
267
268   /* Free the old list */
269   if(old_list) {
270     Curl_llist_destroy(old_list, NULL);
271   }
272
273   /* This might be NULL if sites == NULL, i.e the blacklist is cleared */
274   *list_ptr = new_list;
275
276   return CURLM_OK;
277 }
278
279 bool Curl_pipeline_server_blacklisted(struct SessionHandle *handle,
280                                       char *server_name)
281 {
282   if(handle->multi) {
283     struct curl_llist *blacklist =
284       Curl_multi_pipelining_server_bl(handle->multi);
285
286     if(blacklist) {
287       struct curl_llist_element *curr;
288
289       curr = blacklist->head;
290       while(curr) {
291         char *bl_server_name;
292
293         bl_server_name = curr->ptr;
294         if(Curl_raw_nequal(bl_server_name, server_name,
295                            strlen(bl_server_name))) {
296           infof(handle, "Server %s is blacklisted\n", server_name);
297           return TRUE;
298         }
299         curr = curr->next;
300       }
301     }
302
303     infof(handle, "Server %s is not blacklisted\n", server_name);
304   }
305   return FALSE;
306 }
307
308 CURLMcode Curl_pipeline_set_server_blacklist(char **servers,
309                                              struct curl_llist **list_ptr)
310 {
311   struct curl_llist *old_list = *list_ptr;
312   struct curl_llist *new_list = NULL;
313
314   if(servers) {
315     new_list = Curl_llist_alloc((curl_llist_dtor) server_blacklist_llist_dtor);
316     if(!new_list)
317       return CURLM_OUT_OF_MEMORY;
318
319     /* Parse the URLs and populate the list */
320     while(*servers) {
321       char *server_name;
322
323       server_name = strdup(*servers);
324       if(!server_name)
325         return CURLM_OUT_OF_MEMORY;
326
327       if(!Curl_llist_insert_next(new_list, new_list->tail, server_name))
328         return CURLM_OUT_OF_MEMORY;
329
330       servers++;
331     }
332   }
333
334   /* Free the old list */
335   if(old_list) {
336     Curl_llist_destroy(old_list, NULL);
337   }
338
339   /* This might be NULL if sites == NULL, i.e the blacklist is cleared */
340   *list_ptr = new_list;
341
342   return CURLM_OK;
343 }
344
345
346 void print_pipeline(struct connectdata *conn)
347 {
348   struct curl_llist_element *curr;
349   struct connectbundle *cb_ptr;
350   struct SessionHandle *data = conn->data;
351
352   cb_ptr = conn->bundle;
353
354   if(cb_ptr) {
355     curr = cb_ptr->conn_list->head;
356     while(curr) {
357       conn = curr->ptr;
358       infof(data, "- Conn %d (%p) send_pipe: %d, recv_pipe: %d\n",
359             conn->connection_id,
360             conn,
361             conn->send_pipe->size,
362             conn->recv_pipe->size);
363       curr = curr->next;
364     }
365   }
366 }