~ubuntu-branches/ubuntu/wily/dovecot/wily

« back to all changes in this revision

Viewing changes to src/lib-http/http-client-peer.c

  • Committer: Package Import Robot
  • Author(s): Jaldhar H. Vyas
  • Date: 2013-09-09 00:57:32 UTC
  • mfrom: (1.13.11)
  • mto: (4.8.5 experimental) (1.16.1)
  • mto: This revision was merged to the branch mainline in revision 97.
  • Revision ID: package-import@ubuntu.com-20130909005732-dn1eell8srqbhh0e
Tags: upstream-2.2.5
ImportĀ upstreamĀ versionĀ 2.2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
 
2
 
 
3
#include "lib.h"
 
4
#include "net.h"
 
5
#include "str.h"
 
6
#include "hash.h"
 
7
#include "array.h"
 
8
#include "llist.h"
 
9
#include "istream.h"
 
10
#include "ostream.h"
 
11
#include "iostream-ssl.h"
 
12
#include "http-response-parser.h"
 
13
 
 
14
#include "http-client-private.h"
 
15
 
 
16
/*
 
17
 * Logging
 
18
 */
 
19
 
 
20
static inline void
 
21
http_client_peer_debug(struct http_client_peer *peer,
 
22
        const char *format, ...) ATTR_FORMAT(2, 3);
 
23
 
 
24
static inline void
 
25
http_client_peer_debug(struct http_client_peer *peer,
 
26
        const char *format, ...)
 
27
{
 
28
        va_list args;
 
29
 
 
30
        if (peer->client->set.debug) {
 
31
                va_start(args, format); 
 
32
                i_debug("http-client: peer %s:%u: %s", 
 
33
                        net_ip2addr(&peer->addr.ip), peer->addr.port,
 
34
                        t_strdup_vprintf(format, args));
 
35
                va_end(args);
 
36
        }
 
37
}
 
38
 
 
39
/*
 
40
 * Peer address
 
41
 */
 
42
 
 
43
unsigned int http_client_peer_addr_hash
 
44
(const struct http_client_peer_addr *peer)
 
45
{
 
46
        return net_ip_hash(&peer->ip) + peer->port +
 
47
                (peer->https_name == NULL ? 0 : str_hash(peer->https_name));
 
48
}
 
49
 
 
50
int http_client_peer_addr_cmp
 
51
(const struct http_client_peer_addr *peer1,
 
52
        const struct http_client_peer_addr *peer2)
 
53
{
 
54
        int ret;
 
55
 
 
56
        if ((ret=net_ip_cmp(&peer1->ip, &peer2->ip)) != 0)
 
57
                return ret;
 
58
        if (peer1->port != peer2->port)
 
59
                return (peer1->port > peer2->port ? 1 : -1);
 
60
        return null_strcmp(peer1->https_name, peer2->https_name);
 
61
}
 
62
 
 
63
/*
 
64
 * Peer
 
65
 */
 
66
 
 
67
static void
 
68
http_client_peer_connect(struct http_client_peer *peer, unsigned int count)
 
69
{
 
70
        unsigned int i;
 
71
 
 
72
        for (i = 0; i < count; i++) {
 
73
                http_client_peer_debug(peer, "Making new connection %u of %u", i+1, count);
 
74
                (void)http_client_connection_create(peer);
 
75
        }
 
76
}
 
77
 
 
78
static unsigned int
 
79
http_client_peer_requests_pending(struct http_client_peer *peer,
 
80
                                  unsigned int *num_urgent_r)
 
81
{
 
82
        struct http_client_host *const *host;
 
83
        unsigned int num_requests = 0, num_urgent = 0, requests, urgent;
 
84
 
 
85
        array_foreach(&peer->hosts, host) {
 
86
                requests = http_client_host_requests_pending(*host, &peer->addr, &urgent);
 
87
 
 
88
                num_requests += requests;
 
89
                num_urgent += urgent;
 
90
        }
 
91
        *num_urgent_r = num_urgent;
 
92
        return num_requests;
 
93
}
 
94
 
 
95
static bool
 
96
http_client_peer_next_request(struct http_client_peer *peer,
 
97
                              bool *created_connections)
 
98
{
 
99
        struct http_client_connection *const *conn_idx;
 
100
        struct http_client_connection *conn = NULL;
 
101
        unsigned int connecting = 0, closing = 0, min_waiting = UINT_MAX;
 
102
        unsigned int num_urgent, new_connections, working_conn_count;
 
103
 
 
104
        if (http_client_peer_requests_pending(peer, &num_urgent) == 0)
 
105
                return FALSE;
 
106
 
 
107
        /* find the least busy connection */
 
108
        array_foreach(&peer->conns, conn_idx) {
 
109
                if (http_client_connection_is_ready(*conn_idx)) {
 
110
                        unsigned int waiting = http_client_connection_count_pending(*conn_idx);
 
111
 
 
112
                        if (waiting < min_waiting) {
 
113
                                min_waiting = waiting;
 
114
                                conn = *conn_idx;
 
115
                                if (min_waiting == 0) {
 
116
                                        /* found idle connection, use it now */
 
117
                                        break;
 
118
                                }
 
119
                        }
 
120
                }
 
121
                /* count the number of connecting and closing connections */
 
122
                if ((*conn_idx)->closing)
 
123
                        closing++;
 
124
                else if (!(*conn_idx)->connected)
 
125
                        connecting++;
 
126
        }
 
127
        working_conn_count = array_count(&peer->conns) - closing;
 
128
 
 
129
        /* did we find an idle connection? */
 
130
        if (conn != NULL && min_waiting == 0) {
 
131
                /* yes, use it */
 
132
                return http_client_connection_next_request(conn);
 
133
        }
 
134
 
 
135
        /* no, but can we create a new connection? */           
 
136
        if (num_urgent == 0 &&
 
137
            working_conn_count >= peer->client->set.max_parallel_connections) {
 
138
                /* no */
 
139
                if (conn == NULL) {
 
140
                        http_client_peer_debug(peer,
 
141
                                "Only non-urgent requests, and we already have "
 
142
                                "%u pending connections", working_conn_count);
 
143
                        return FALSE;
 
144
                }
 
145
                /* pipeline it */
 
146
                return http_client_connection_next_request(conn);
 
147
        }
 
148
 
 
149
        /* yes, determine how many connections to set up */
 
150
        if (peer->last_connect_failed && working_conn_count > 0 &&
 
151
            working_conn_count == connecting) {
 
152
                /* don't create new connections until the existing ones have
 
153
                   finished connecting successfully. */
 
154
                new_connections = 0;
 
155
        } else if (num_urgent == 0) {
 
156
                new_connections = connecting == 0 ? 1 : 0;
 
157
        } else {
 
158
                new_connections = (num_urgent > connecting ? num_urgent - connecting : 0);
 
159
        }
 
160
        http_client_peer_debug(peer,
 
161
                "Creating %u new connections to handle requests "
 
162
                "(already %u usable, connecting to %u, closing %u)",
 
163
                new_connections, working_conn_count - connecting,
 
164
                connecting, closing);
 
165
        if (new_connections > 0) {
 
166
                *created_connections = TRUE;
 
167
                http_client_peer_connect(peer, new_connections);
 
168
        }
 
169
 
 
170
        /* now we wait until it is connected */
 
171
        return FALSE;
 
172
}
 
173
 
 
174
bool http_client_peer_handle_requests(struct http_client_peer *peer)
 
175
{
 
176
        bool created_connections = FALSE;
 
177
 
 
178
        while (http_client_peer_next_request(peer, &created_connections)) ;
 
179
        return created_connections;
 
180
}
 
181
 
 
182
static struct http_client_peer *
 
183
http_client_peer_create(struct http_client *client,
 
184
                              const struct http_client_peer_addr *addr)
 
185
{
 
186
        struct http_client_peer *peer;
 
187
 
 
188
        i_assert(addr->https_name == NULL || client->ssl_ctx != NULL);
 
189
 
 
190
        peer = i_new(struct http_client_peer, 1);
 
191
        peer->client = client;
 
192
        peer->addr = *addr;
 
193
        peer->addr.https_name = i_strdup(addr->https_name);
 
194
        i_array_init(&peer->hosts, 16);
 
195
        i_array_init(&peer->conns, 16);
 
196
 
 
197
        hash_table_insert
 
198
                (client->peers, (const struct http_client_peer_addr *)&peer->addr, peer);
 
199
        DLLIST_PREPEND(&client->peers_list, peer);
 
200
 
 
201
        http_client_peer_debug(peer, "Peer created");
 
202
        http_client_peer_connect(peer, 1);
 
203
        return peer;
 
204
}
 
205
 
 
206
void http_client_peer_free(struct http_client_peer **_peer)
 
207
{
 
208
        struct http_client_peer *peer = *_peer;
 
209
        struct http_client_connection **conn;
 
210
        ARRAY_TYPE(http_client_connection) conns;
 
211
 
 
212
        if (peer->destroyed)
 
213
                return;
 
214
        peer->destroyed = TRUE;
 
215
 
 
216
        http_client_peer_debug(peer, "Peer destroy");
 
217
 
 
218
        /* make a copy of the connection array; freed connections modify it */
 
219
        t_array_init(&conns, array_count(&peer->conns));
 
220
        array_copy(&conns.arr, 0, &peer->conns.arr, 0, array_count(&peer->conns));
 
221
 
 
222
        array_foreach_modifiable(&conns, conn) {
 
223
                http_client_connection_unref(conn);
 
224
        }
 
225
 
 
226
        i_assert(array_count(&peer->conns) == 0);
 
227
        array_free(&peer->conns);
 
228
        array_free(&peer->hosts);
 
229
 
 
230
        hash_table_remove
 
231
                (peer->client->peers, (const struct http_client_peer_addr *)&peer->addr);
 
232
        DLLIST_REMOVE(&peer->client->peers_list, peer);
 
233
 
 
234
        i_free(peer->addr.https_name);
 
235
        i_free(peer);
 
236
        *_peer = NULL;
 
237
}
 
238
 
 
239
struct http_client_peer *
 
240
http_client_peer_get(struct http_client *client,
 
241
                           const struct http_client_peer_addr *addr)
 
242
{
 
243
        struct http_client_peer *peer;
 
244
 
 
245
        peer = hash_table_lookup(client->peers, addr);
 
246
        if (peer == NULL)
 
247
                peer = http_client_peer_create(client, addr);
 
248
 
 
249
        return peer;
 
250
}
 
251
 
 
252
bool http_client_peer_have_host(struct http_client_peer *peer,
 
253
                                struct http_client_host *host)
 
254
{
 
255
        struct http_client_host *const *host_idx;
 
256
 
 
257
        array_foreach(&peer->hosts, host_idx) {
 
258
                if (*host_idx == host)
 
259
                        return TRUE;
 
260
        }
 
261
        return FALSE;
 
262
}
 
263
 
 
264
void http_client_peer_add_host(struct http_client_peer *peer,
 
265
                               struct http_client_host *host)
 
266
{
 
267
        if (!http_client_peer_have_host(peer, host))
 
268
                array_append(&peer->hosts, &host, 1);
 
269
}
 
270
 
 
271
struct http_client_request *
 
272
http_client_peer_claim_request(struct http_client_peer *peer, bool no_urgent)
 
273
{
 
274
        struct http_client_host *const *host_idx;
 
275
        struct http_client_request *req;
 
276
 
 
277
        array_foreach(&peer->hosts, host_idx) {
 
278
                if ((req=http_client_host_claim_request
 
279
                        (*host_idx, &peer->addr, no_urgent)) != NULL) {
 
280
                        req->peer = peer;
 
281
                        return req;
 
282
                }
 
283
        }
 
284
 
 
285
        return NULL;
 
286
}
 
287
 
 
288
void http_client_peer_connection_success(struct http_client_peer *peer)
 
289
{
 
290
        struct http_client_host *const *host;
 
291
 
 
292
        peer->last_connect_failed = FALSE;
 
293
 
 
294
        array_foreach(&peer->hosts, host) {
 
295
                http_client_host_connection_success(*host, &peer->addr);
 
296
        }
 
297
}
 
298
 
 
299
void http_client_peer_connection_failure(struct http_client_peer *peer,
 
300
                                         const char *reason)
 
301
{
 
302
        struct http_client_host *const *host;
 
303
        unsigned int num_urgent;
 
304
 
 
305
        i_assert(array_count(&peer->conns) > 0);
 
306
 
 
307
        http_client_peer_debug(peer, "Failed to make connection");
 
308
 
 
309
        peer->last_connect_failed = TRUE;
 
310
        if (array_count(&peer->conns) > 1) {
 
311
                /* if there are other connections attempting to connect, wait
 
312
                   for them before failing the requests. remember that we had
 
313
                   trouble with connecting so in future we don't try to create
 
314
                   more than one connection until connects work again. */
 
315
        } else {
 
316
                /* this was the only/last connection and connecting to it
 
317
                   failed. a second connect will probably also fail, so just
 
318
                   abort all requests. */
 
319
                array_foreach(&peer->hosts, host) {
 
320
                        http_client_host_connection_failure(*host, &peer->addr, reason);
 
321
                }
 
322
        }
 
323
        if (array_count(&peer->conns) == 0 &&
 
324
            http_client_peer_requests_pending(peer, &num_urgent) == 0)
 
325
                http_client_peer_free(&peer);
 
326
}
 
327
 
 
328
void http_client_peer_connection_lost(struct http_client_peer *peer)
 
329
{
 
330
        unsigned int num_urgent;
 
331
 
 
332
        /* we get here when an already connected connection fails. if the
 
333
           connect itself fails, http_client_peer_connection_failure() is
 
334
           called instead. */
 
335
 
 
336
        if (peer->destroyed)
 
337
                return;
 
338
 
 
339
        http_client_peer_debug(peer, "Lost a connection (%d connections left)",
 
340
                array_count(&peer->conns));
 
341
 
 
342
        /* if there are pending requests for this peer, create a new connection
 
343
           for them. */
 
344
        http_client_peer_handle_requests(peer);
 
345
 
 
346
        if (array_count(&peer->conns) == 0 &&
 
347
            http_client_peer_requests_pending(peer, &num_urgent) == 0)
 
348
                http_client_peer_free(&peer);
 
349
}
 
350
 
 
351
unsigned int http_client_peer_idle_connections(struct http_client_peer *peer)
 
352
{
 
353
    struct http_client_connection *const *conn_idx;
 
354
    unsigned int idle = 0;
 
355
 
 
356
        /* find idle connections */
 
357
    array_foreach(&peer->conns, conn_idx) {
 
358
        if (http_client_connection_is_idle(*conn_idx))
 
359
                        idle++;
 
360
    }
 
361
 
 
362
        return idle;
 
363
}
 
364