]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-http: client: shared context: Allow sharing peer state between clients.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Wed, 27 Sep 2017 17:21:24 +0000 (19:21 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Thu, 7 Dec 2017 16:40:45 +0000 (18:40 +0200)
src/lib-http/http-client-connection.c
src/lib-http/http-client-peer.c
src/lib-http/http-client-private.h
src/lib-http/http-client-queue.c
src/lib-http/http-client-request.c
src/lib-http/http-client.c

index f0d314bb421520d05aad8f945edf64ff46d20fd0..3d48053cc64dcc1924326eb17847b7d912cb4dc1 100644 (file)
@@ -32,8 +32,7 @@ http_client_connection_debug(struct http_client_connection *conn,
 {
        va_list args;
 
-       if (conn->client->set.debug) {
-
+       if (conn->debug || conn->ppool->peer->cctx->set.debug) {
                va_start(args, format); 
                i_debug("http-client: conn %s: %s",
                        conn->label, t_strdup_vprintf(format, args));
@@ -109,7 +108,7 @@ static void
 http_client_connection_retry_requests(struct http_client_connection *conn,
        unsigned int status, const char *error)
 {
-       const struct http_client_settings *set = &conn->client->set;
+       const struct http_client_settings *set = &conn->peer->client->set;
        struct http_client_request *req, **req_idx;
 
        if (!array_is_created(&conn->request_wait_list))
@@ -143,6 +142,8 @@ static void
 http_client_connection_server_close(struct http_client_connection **_conn)
 {
        struct http_client_connection *conn = *_conn;
+       struct http_client_peer *peer = conn->peer;
+       struct http_client *client = peer->client;
        struct http_client_request *req, **req_idx;
 
        http_client_connection_debug(conn,
@@ -159,8 +160,8 @@ http_client_connection_server_close(struct http_client_connection **_conn)
        }       
        array_clear(&conn->request_wait_list);
 
-       if (conn->client->ioloop != NULL)
-               io_loop_stop(conn->client->ioloop);
+       if (client != NULL && client->ioloop != NULL)
+               io_loop_stop(client->ioloop);
 
        http_client_connection_close(_conn);
 }
@@ -296,6 +297,7 @@ http_client_connection_lost(struct http_client_connection **_conn,
 
 int http_client_connection_check_ready(struct http_client_connection *conn)
 {
+       const struct http_client_settings *set = &conn->peer->client->set;
        int ret;
 
        if (conn->in_req_callback) {
@@ -310,7 +312,7 @@ int http_client_connection_check_ready(struct http_client_connection *conn)
        if (!conn->connected || conn->output_locked || conn->output_broken ||
                conn->close_indicated || conn->tunneling ||
                http_client_connection_count_pending(conn) >=
-                       conn->client->set.max_pipelined_requests)
+                       set->max_pipelined_requests)
                return 0;
 
        if (conn->last_ioloop != NULL && conn->last_ioloop != current_ioloop) {
@@ -338,6 +340,37 @@ int http_client_connection_check_ready(struct http_client_connection *conn)
        return 1;
 }
 
+static void
+http_client_connection_detach_peer(struct http_client_connection *conn)
+{
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_connection *const *conn_idx;
+       ARRAY_TYPE(http_client_connection) *conn_arr;
+       bool found = FALSE;
+
+       if (peer == NULL)
+               return;
+
+       http_client_peer_ref(peer);
+       conn_arr = &peer->conns;
+       array_foreach(conn_arr, conn_idx) {
+               if (*conn_idx == conn) {
+                       array_delete(conn_arr, array_foreach_idx(conn_arr, conn_idx), 1);
+                       found = TRUE;
+                       break;
+               }
+       }
+
+       i_assert(found);
+
+       conn->peer = NULL;
+       http_client_connection_debug(conn, "Detached peer");
+
+       if (conn->connect_succeeded)
+               http_client_peer_connection_lost(peer, conn->lost_prematurely);
+       http_client_peer_unref(&peer);
+}
+
 static void
 http_client_connection_idle_timeout(struct http_client_connection *conn)
 {
@@ -349,31 +382,95 @@ http_client_connection_idle_timeout(struct http_client_connection *conn)
        http_client_connection_close(&conn);
 }
 
+void http_client_connection_lost_peer(struct http_client_connection *conn)
+{
+       struct http_client_peer *peer = conn->peer;
+       struct http_client *client = peer->client;
+       const struct http_client_settings *set = &client->set;
+       struct http_client_peer_pool *ppool = conn->ppool;
+       struct http_client_peer_shared *pshared = ppool->peer;
+       unsigned int timeout, count;
+
+       if (!conn->connected) {
+               http_client_connection_unref(&conn);
+               return;
+       }
+
+       i_assert(!conn->in_req_callback);
+
+       if (conn->to_idle == NULL) {
+               count = array_count(&ppool->conns);
+               i_assert(count > 0);
+
+               /* set timeout for this connection */
+               if (count > http_client_peer_shared_max_connections(pshared)) {
+                       /* instant death for (urgent) connections above limit */
+                       timeout = 0;
+               } else {
+                       unsigned int idle_count = array_count(&ppool->idle_conns);
+
+                       /* kill duplicate connections quicker;
+                                linearly based on the number of connections */
+                       i_assert(count >= idle_count + 1);
+                       timeout = (set->max_parallel_connections - idle_count) *
+                               (set->max_idle_time_msecs / set->max_parallel_connections);
+               }
+
+               http_client_connection_debug(conn,
+                       "Lost peer; going idle (timeout = %u msecs)",
+                       timeout);
+
+               conn->to_idle =
+                       timeout_add(timeout, http_client_connection_idle_timeout, conn);
+               array_append(&ppool->idle_conns, &conn, 1);
+       } else {
+               http_client_connection_debug(conn,
+                       "Lost peer; already idle");
+       }
+
+       http_client_connection_detach_peer(conn);
+}
+
 void http_client_connection_check_idle(struct http_client_connection *conn)
 {
+       struct http_client_peer *peer;
        struct http_client_peer_pool *ppool = conn->ppool;
+       struct http_client *client;
+       const struct http_client_settings *set;
        unsigned int timeout, count;
 
+       peer = conn->peer;
+       if (peer == NULL) {
+               i_assert(conn->to_idle != NULL);
+               return;
+       }
+
+       if (conn->to_idle != NULL) {
+               /* timeout already set */
+               return;
+       }
+
+       client = peer->client;
+       set = &client->set;
+
        if (conn->connected &&
                array_is_created(&conn->request_wait_list) &&
                array_count(&conn->request_wait_list) == 0 &&
                !conn->in_req_callback &&
                conn->incoming_payload == NULL &&
-               conn->client->set.max_idle_time_msecs > 0) {
+               set->max_idle_time_msecs > 0) {
 
-               if (conn->to_idle != NULL) {
-                       /* timeout already set */
-                       return;
-               }
+               i_assert(peer != NULL);
+               client = peer->client;
 
-               if (conn->client->ioloop != NULL)
-                       io_loop_stop(conn->client->ioloop);
+               if (client->ioloop != NULL)
+                       io_loop_stop(client->ioloop);
 
-               count = array_count(&conn->peer->conns);
+               count = array_count(&peer->conns);
                i_assert(count > 0);
 
                /* set timeout for this connection */
-               if (count > conn->client->set.max_parallel_connections) {
+               if (count > set->max_parallel_connections) {
                        /* instant death for (urgent) connections above limit */
                        timeout = 0;
                } else {
@@ -382,9 +479,8 @@ void http_client_connection_check_idle(struct http_client_connection *conn)
                        /* kill duplicate connections quicker;
                                 linearly based on the number of connections */
                        i_assert(array_count(&ppool->conns) >= idle_count + 1);
-                       timeout = (conn->client->set.max_parallel_connections - idle_count) *
-                               (conn->client->set.max_idle_time_msecs /
-                                       conn->client->set.max_parallel_connections);
+                       timeout = (set->max_parallel_connections - idle_count) *
+                               (set->max_idle_time_msecs / set->max_parallel_connections);
                }
 
                http_client_connection_debug(conn, 
@@ -395,10 +491,6 @@ void http_client_connection_check_idle(struct http_client_connection *conn)
                        timeout_add(timeout, http_client_connection_idle_timeout, conn);
 
                array_append(&ppool->idle_conns, &conn, 1);
-
-       } else {
-               /* there should be no idle timeout */
-               i_assert(conn->to_idle == NULL);
        }
 }
 
@@ -420,13 +512,21 @@ http_client_connection_stop_idle(struct http_client_connection *conn)
        }
 }
 
-void http_client_connection_use_idle(struct http_client_connection *conn,
+void http_client_connection_claim_idle(struct http_client_connection *conn,
        struct http_client_peer *peer)
 {
-       http_client_connection_debug(conn, "Reusing idle connection");
+       http_client_connection_debug(conn, "Claimed as idle");
 
        i_assert(peer->ppool == conn->ppool);
        http_client_connection_stop_idle(conn);
+
+       if (conn->peer == NULL || conn->peer != peer) {
+               http_client_connection_detach_peer(conn);
+
+               conn->peer = peer;
+               conn->debug = peer->client->set.debug;
+               array_append(&peer->conns, &conn, 1);
+       }
 }
 
 static void
@@ -440,8 +540,11 @@ http_client_connection_request_timeout(struct http_client_connection *conn)
 void http_client_connection_start_request_timeout(
        struct http_client_connection *conn)
 {
+       struct http_client_peer *peer = conn->peer;
+       struct http_client *client = peer->client;
+       const struct http_client_settings *set = &client->set;
        unsigned int timeout_msecs =
-               conn->client->set.request_timeout_msecs;
+               set->request_timeout_msecs;
 
        if (conn->pending_request != NULL)
                return;
@@ -479,6 +582,7 @@ void http_client_connection_stop_request_timeout(
 static void
 http_client_connection_continue_timeout(struct http_client_connection *conn)
 {
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
        struct http_client_request *const *wait_reqs;
        struct http_client_request *req;
        unsigned int wait_count;
@@ -487,7 +591,7 @@ http_client_connection_continue_timeout(struct http_client_connection *conn)
        i_assert(conn->pending_request == NULL);
 
        timeout_remove(&conn->to_response);
-       conn->peer->no_payload_sync = TRUE;
+       pshared->no_payload_sync = TRUE;
 
        http_client_connection_debug(conn, 
                "Expected 100-continue response timed out; sending payload anyway");
@@ -505,6 +609,8 @@ http_client_connection_continue_timeout(struct http_client_connection *conn)
 
 int http_client_connection_next_request(struct http_client_connection *conn)
 {
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
        struct http_client_request *req = NULL;
        const char *error;
        bool pipelined;
@@ -521,7 +627,7 @@ int http_client_connection_next_request(struct http_client_connection *conn)
        /* claim request, but no urgent request can be second in line */
        pipelined = array_count(&conn->request_wait_list) > 0 ||
                conn->pending_request != NULL;
-       req = http_client_peer_claim_request(conn->peer, pipelined);
+       req = http_client_peer_claim_request(peer, pipelined);
        if (req == NULL)
                return 0;       
 
@@ -530,7 +636,7 @@ int http_client_connection_next_request(struct http_client_connection *conn)
        http_client_connection_stop_idle(conn);
 
        req->payload_sync_continue = FALSE;
-       if (conn->peer->no_payload_sync)
+       if (pshared->no_payload_sync)
                req->payload_sync = FALSE;
 
        /* add request to wait list and add a reference */
@@ -558,7 +664,7 @@ int http_client_connection_next_request(struct http_client_connection *conn)
                    an HTTP/1.0 intermediary, such a client SHOULD NOT wait for an
                    indefinite period before sending the message body.
         */
-       if (req->payload_sync && !conn->peer->seen_100_response) {
+       if (req->payload_sync && !pshared->seen_100_response) {
                i_assert(!pipelined);
                i_assert(req->payload_chunked || req->payload_size > 0);
                i_assert(conn->to_response == NULL);
@@ -712,6 +818,7 @@ http_client_connection_return_response(
        struct http_client_request *req,
        struct http_response *response)
 {
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
        struct istream *payload;
        bool retrying;
 
@@ -788,7 +895,7 @@ http_client_connection_return_response(
 
        if (conn->incoming_payload == NULL && conn->conn.input != NULL) {
                i_assert(conn->conn.io != NULL ||
-                       conn->peer->addr.type == HTTP_CLIENT_PEER_ADDR_RAW);
+                       pshared->addr.type == HTTP_CLIENT_PEER_ADDR_RAW);
                return http_client_connection_unref(&conn);
        }
        http_client_connection_unref(&conn);
@@ -799,6 +906,8 @@ static void http_client_connection_input(struct connection *_conn)
 {
        struct http_client_connection *conn =
                (struct http_client_connection *)_conn;
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
        struct http_response response;
        struct http_client_request *const *reqs;
        struct http_client_request *req = NULL, *req_ref;
@@ -830,7 +939,7 @@ static void http_client_connection_input(struct connection *_conn)
                                i_stream_get_name(conn->conn.input),
                                stream_errno != 0 ?
                                        i_stream_get_error(conn->conn.input) : "EOF");
-                       http_client_peer_connection_failure(conn->peer, error);
+                       http_client_connection_failure(conn, error);
                        http_client_connection_debug(conn, "%s", error);
                        http_client_connection_close(&conn);
                        return;
@@ -915,8 +1024,8 @@ static void http_client_connection_input(struct connection *_conn)
                                continue;
                        }
 
-                       conn->peer->no_payload_sync = FALSE;
-                       conn->peer->seen_100_response = TRUE;
+                       pshared->no_payload_sync = FALSE;
+                       pshared->seen_100_response = TRUE;
                        req->payload_sync_continue = TRUE;
 
                        http_client_connection_debug(conn,
@@ -998,7 +1107,7 @@ static void http_client_connection_input(struct connection *_conn)
                                        /* drop Expect: continue */
                                        req->payload_sync = FALSE;
                                        conn->output_locked = FALSE;
-                                       conn->peer->no_payload_sync = TRUE;
+                                       pshared->no_payload_sync = TRUE;
                                        if (http_client_request_try_retry(req))
                                                handled = TRUE;
                                /* redirection */
@@ -1088,11 +1197,12 @@ static void http_client_connection_input(struct connection *_conn)
        if (finished > 0) {
                /* connection still alive after (at least one) request;
                   we can pipeline -> mark for subsequent connections */
-               conn->peer->allows_pipelining = TRUE;
+               pshared->allows_pipelining = TRUE;
 
                /* room for new requests */
-               if (http_client_connection_check_ready(conn) > 0)
-                       http_client_peer_trigger_request_handler(conn->peer);
+               if (peer != NULL &&
+                       http_client_connection_check_ready(conn) > 0)
+                       http_client_peer_trigger_request_handler(peer);
        }
 }
 
@@ -1185,6 +1295,13 @@ http_client_connection_start_tunnel(struct http_client_connection **_conn,
 static void 
 http_client_connection_ready(struct http_client_connection *conn)
 {
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_peer_pool *ppool = conn->ppool;
+       struct http_client_peer_shared *pshared = ppool->peer;
+       ARRAY_TYPE(http_client_connection) *conn_arr;
+       struct http_client_connection *const *conn_idx;
+       const struct http_client_settings *set = &peer->client->set;
+
        http_client_connection_debug(conn, "Ready for requests");
 
        /* connected */
@@ -1192,20 +1309,30 @@ http_client_connection_ready(struct http_client_connection *conn)
        conn->last_ioloop = current_ioloop;
        timeout_remove(&conn->to_connect);
 
+       /* remove from pending list */
+       conn_arr = &ppool->pending_conns;
+       array_foreach(conn_arr, conn_idx) {
+               if (*conn_idx == conn) {
+                       array_delete(conn_arr, array_foreach_idx(conn_arr, conn_idx), 1);
+                       break;
+               }
+       }
+
        /* indicate connection success */
        conn->connect_succeeded = TRUE;
-       http_client_peer_connection_success(conn->peer);
+       http_client_peer_connection_success(peer);
 
        /* start raw log */
-       if (conn->client->set.rawlog_dir != NULL) {
-               iostream_rawlog_create(conn->client->set.rawlog_dir,
+       if (ppool->rawlog_dir != NULL) {
+               iostream_rawlog_create(ppool->rawlog_dir,
                                       &conn->conn.input, &conn->conn.output);
        }
 
        /* direct tunneling connections handle connect requests just by providing a
           raw connection */
-       if (conn->peer->addr.type == HTTP_CLIENT_PEER_ADDR_RAW) {
+       if (pshared->addr.type == HTTP_CLIENT_PEER_ADDR_RAW) {
                struct http_client_request *req;
+
                
                req = http_client_peer_claim_request(conn->peer, FALSE);
                if (req != NULL) {
@@ -1229,7 +1356,7 @@ http_client_connection_ready(struct http_client_connection *conn)
 
        /* start protocol I/O */
        conn->http_parser = http_response_parser_init
-               (conn->conn.input, &conn->client->set.response_hdr_limits, 0);
+               (conn->conn.input, &set->response_hdr_limits, 0);
        o_stream_set_flush_callback(conn->conn.output,
     http_client_connection_output, conn);
 }
@@ -1238,11 +1365,14 @@ static int
 http_client_connection_ssl_handshaked(const char **error_r, void *context)
 {
        struct http_client_connection *conn = context;
-       const char *error, *host = conn->peer->addr.a.tcp.https_name;
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
+       const struct http_client_settings *set = &peer->client->set;
+       const char *error, *host = pshared->addr.a.tcp.https_name;
 
        if (ssl_iostream_check_cert_validity(conn->ssl_iostream, host, &error) == 0)
                http_client_connection_debug(conn, "SSL handshake successful");
-       else if (conn->client->set.ssl->allow_invalid_cert) {
+       else if (set->ssl->allow_invalid_cert) {
                http_client_connection_debug(conn, "SSL handshake successful, "
                        "ignoring invalid certificate: %s", error);
        } else {
@@ -1256,21 +1386,26 @@ static int
 http_client_connection_ssl_init(struct http_client_connection *conn,
                                const char **error_r)
 {
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_peer_pool *ppool = conn->ppool;
+       struct http_client_peer_shared *pshared = ppool->peer;
+       const struct http_client_settings *set = &peer->client->set;
        struct ssl_iostream_settings ssl_set;
+       struct ssl_iostream_context *ssl_ctx = ppool->ssl_ctx;
        const char *error;
 
-       i_assert(conn->client->ssl_ctx != NULL);
+       i_assert(ssl_ctx != NULL);
 
        i_zero(&ssl_set);
-       if (!conn->client->set.ssl->allow_invalid_cert) {
+       if (!set->ssl->allow_invalid_cert) {
                ssl_set.verbose_invalid_cert = TRUE;
        }
 
-       if (conn->client->set.debug)
+       if (set->debug)
                http_client_connection_debug(conn, "Starting SSL handshake");
 
-       if (io_stream_create_ssl_client(conn->client->ssl_ctx,
-                                       conn->peer->addr.a.tcp.https_name, &ssl_set,
+       if (io_stream_create_ssl_client(ssl_ctx,
+                                       pshared->addr.a.tcp.https_name, &ssl_set,
                                        &conn->conn.input, &conn->conn.output,
                                        &conn->ssl_iostream, &error) < 0) {
                *error_r = t_strdup_printf(
@@ -1302,7 +1437,9 @@ http_client_connection_connected(struct connection *_conn, bool success)
 {
        struct http_client_connection *conn =
                (struct http_client_connection *)_conn;
-       const struct http_client_settings *set = &conn->client->set;
+       struct http_client_peer *peer = conn->peer;
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
+       const struct http_client_settings *set = &peer->client->set;
        const char *error;
 
        if (!success) {
@@ -1326,7 +1463,7 @@ http_client_connection_connected(struct connection *_conn, bool success)
                                        set->socket_recv_buffer_size);
                }
 
-               if (http_client_peer_addr_is_https(&conn->peer->addr)) {
+               if (http_client_peer_addr_is_https(&pshared->addr)) {
                        if (http_client_connection_ssl_init(conn, &error) < 0) {
                                http_client_connection_debug(conn, "%s", error);
                                http_client_connection_failure(conn, error);
@@ -1374,10 +1511,9 @@ static void http_client_connect_timeout(struct http_client_connection *conn)
 }
 
 static void
-http_client_connection_connect(struct http_client_connection *conn)
+http_client_connection_connect(struct http_client_connection *conn,
+       unsigned int timeout_msecs)
 {
-       unsigned int msecs;
-
        conn->connect_start_timestamp = ioloop_timeval;
        if (connection_client_connect(&conn->conn) < 0) {
                conn->connect_errno = errno;
@@ -1389,19 +1525,17 @@ http_client_connection_connect(struct http_client_connection *conn)
 
        /* don't use connection.h timeout because we want this timeout
           to include also the SSL handshake */
-       msecs = conn->client->set.connect_timeout_msecs;
-       if (msecs == 0)
-               msecs = conn->client->set.request_timeout_msecs;
-       if (msecs > 0) {
-               conn->to_connect =
-                       timeout_add(msecs, http_client_connect_timeout, conn);
+       if (timeout_msecs > 0) {
+               conn->to_connect = timeout_add(timeout_msecs,
+                       http_client_connect_timeout, conn);
        }
 }
 
 static void
 http_client_connect_tunnel_timeout(struct http_client_connection *conn)
 {
-       const char *error, *name = http_client_peer_addr2str(&conn->peer->addr);
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
+       const char *error, *name = http_client_peer_addr2str(&pshared->addr);
        unsigned int msecs;
 
        msecs = timeval_diff_msecs(&ioloop_timeval,
@@ -1412,7 +1546,7 @@ http_client_connect_tunnel_timeout(struct http_client_connection *conn)
                name, msecs/1000, msecs%1000);
 
        http_client_connection_debug(conn, "%s", error);
-       http_client_peer_connection_failure(conn->peer, error);
+       http_client_connection_failure(conn, error);
        http_client_connection_close(&conn);
 }
 
@@ -1420,9 +1554,10 @@ static void
 http_client_connection_tunnel_response(const struct http_response *response,
                               struct http_client_connection *conn)
 {
-       struct http_client_context *cctx = conn->client->cctx;
+       struct http_client_peer_shared *pshared = conn->ppool->peer;
+       struct http_client_context *cctx = pshared->cctx;
        struct http_client_tunnel tunnel;
-       const char *name = http_client_peer_addr2str(&conn->peer->addr);
+       const char *name = http_client_peer_addr2str(&pshared->addr);
        struct http_client_request *req = conn->connect_request;
 
        conn->connect_request = NULL;
@@ -1445,39 +1580,41 @@ http_client_connection_tunnel_response(const struct http_response *response,
 
 static void
 http_client_connection_connect_tunnel(struct http_client_connection *conn,
-       const struct ip_addr *ip, in_port_t port)
+       const struct ip_addr *ip, in_port_t port,
+       unsigned int timeout_msecs)
 {
-       unsigned int msecs;
+       struct http_client *client = conn->peer->client;
 
        conn->connect_start_timestamp = ioloop_timeval;
 
        conn->connect_request = http_client_request_connect_ip
-               (conn->client, ip, port, http_client_connection_tunnel_response, conn);
+               (client, ip, port, http_client_connection_tunnel_response, conn);
        http_client_request_set_urgent(conn->connect_request);
        http_client_request_submit(conn->connect_request);
 
        /* don't use connection.h timeout because we want this timeout
           to include also the SSL handshake */
-       msecs = conn->client->set.connect_timeout_msecs;
-       if (msecs == 0)
-               msecs = conn->client->set.request_timeout_msecs;
-       if (msecs > 0) {
-               conn->to_connect =
-                       timeout_add(msecs, http_client_connect_tunnel_timeout, conn);
+       if (timeout_msecs > 0) {
+               conn->to_connect = timeout_add(timeout_msecs,
+                       http_client_connect_tunnel_timeout, conn);
        }
 }
 
 struct http_client_connection *
 http_client_connection_create(struct http_client_peer *peer)
 {
-       struct http_client_context *cctx = peer->client->cctx;
+       struct http_client_peer_shared *pshared = peer->shared;
        struct http_client_peer_pool *ppool = peer->ppool;
+       struct http_client_context *cctx = pshared->cctx;
+       struct http_client *client = peer->client;
+       const struct http_client_settings *set = &client->set;
        struct http_client_connection *conn;
        static unsigned int id = 0;
-       const struct http_client_peer_addr *addr = &peer->addr;
+       const struct http_client_peer_addr *addr = &pshared->addr;
        const char *conn_type = "UNKNOWN";
+       unsigned int timeout_msecs;
 
-       switch (peer->addr.type) {
+       switch (pshared->addr.type) {
        case HTTP_CLIENT_PEER_ADDR_HTTP:
                conn_type = "HTTP";
                break;
@@ -1495,42 +1632,46 @@ http_client_connection_create(struct http_client_peer *peer)
                break;
        }
 
+       timeout_msecs = set->connect_timeout_msecs;
+       if (timeout_msecs == 0)
+               timeout_msecs = set->request_timeout_msecs;
+
        conn = i_new(struct http_client_connection, 1);
        conn->refcount = 1;
-       conn->client = peer->client;
        conn->id = id++;
        conn->ppool = ppool;
        conn->peer = peer;
-       if (peer->addr.type != HTTP_CLIENT_PEER_ADDR_RAW)
+       conn->debug = client->set.debug;
+       if (pshared->addr.type != HTTP_CLIENT_PEER_ADDR_RAW)
                i_array_init(&conn->request_wait_list, 16);
        conn->io_wait_timer = io_wait_timer_add();
 
        conn->label = i_strdup_printf("%s [%d]",
-               http_client_peer_label(peer), conn->id);
+               http_client_peer_shared_label(pshared), conn->id);
 
-       switch (peer->addr.type) {
+       switch (pshared->addr.type) {
        case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
                http_client_connection_connect_tunnel
-                       (conn, &addr->a.tcp.ip, addr->a.tcp.port);
+                       (conn, &addr->a.tcp.ip, addr->a.tcp.port, timeout_msecs);
                break;
        case HTTP_CLIENT_PEER_ADDR_UNIX:
                connection_init_client_unix(cctx->conn_list, &conn->conn,
                        addr->a.un.path);
                conn->connect_initialized = TRUE;
-               http_client_connection_connect(conn);
+               http_client_connection_connect(conn, timeout_msecs);
                break;
        default:
                connection_init_client_ip(cctx->conn_list, &conn->conn,
                        &addr->a.tcp.ip, addr->a.tcp.port);
                conn->connect_initialized = TRUE;
-               http_client_connection_connect(conn);
+               http_client_connection_connect(conn, timeout_msecs);
        }
 
-       array_append(&peer->conns, &conn, 1);
+       array_append(&ppool->pending_conns, &conn, 1);
        array_append(&ppool->conns, &conn, 1);
+       array_append(&peer->conns, &conn, 1);
 
        http_client_peer_pool_ref(ppool);
-       http_client_peer_ref(peer);
 
        http_client_connection_debug(conn,
                "%s connection created (%d parallel connections exist)%s",
@@ -1548,7 +1689,6 @@ void http_client_connection_ref(struct http_client_connection *conn)
 static void
 http_client_connection_disconnect(struct http_client_connection *conn)
 {
-       struct http_client_peer *peer = conn->peer;
        struct http_client_peer_pool *ppool = conn->ppool;
        ARRAY_TYPE(http_client_connection) *conn_arr;
        struct http_client_connection *const *conn_idx;
@@ -1584,10 +1724,9 @@ http_client_connection_disconnect(struct http_client_connection *conn)
        timeout_remove(&conn->to_requests);
        timeout_remove(&conn->to_connect);
        timeout_remove(&conn->to_input);
-       timeout_remove(&conn->to_idle);
        timeout_remove(&conn->to_response);
 
-       /* remove this connection from the list */
+       /* remove this connection from the lists */
        conn_arr = &ppool->conns;
        array_foreach(conn_arr, conn_idx) {
                if (*conn_idx == conn) {
@@ -1595,7 +1734,7 @@ http_client_connection_disconnect(struct http_client_connection *conn)
                        break;
                }
        }
-       conn_arr = &peer->conns;
+       conn_arr = &ppool->pending_conns;
        array_foreach(conn_arr, conn_idx) {
                if (*conn_idx == conn) {
                        array_delete(conn_arr, array_foreach_idx(conn_arr, conn_idx), 1);
@@ -1603,8 +1742,7 @@ http_client_connection_disconnect(struct http_client_connection *conn)
                }
        }
 
-       if (conn->connect_succeeded)
-               http_client_peer_connection_lost(peer, conn->lost_prematurely);
+       http_client_connection_detach_peer(conn);
 
        http_client_connection_stop_idle(conn); // FIXME: needed?
 }
@@ -1612,7 +1750,6 @@ http_client_connection_disconnect(struct http_client_connection *conn)
 bool http_client_connection_unref(struct http_client_connection **_conn)
 {
        struct http_client_connection *conn = *_conn;
-       struct http_client_peer *peer = conn->peer;
        struct http_client_peer_pool *ppool = conn->ppool;
 
        i_assert(conn->refcount > 0);
@@ -1646,7 +1783,6 @@ bool http_client_connection_unref(struct http_client_connection **_conn)
        i_free(conn);
 
        http_client_peer_pool_unref(&ppool);
-       http_client_peer_unref(&peer);
        return FALSE;
 }
 
@@ -1661,17 +1797,6 @@ void http_client_connection_close(struct http_client_connection **_conn)
        http_client_connection_unref(_conn);
 }
 
-void http_client_connection_peer_closed(struct http_client_connection **_conn)
-{
-       struct http_client_connection *conn = *_conn;
-
-       http_client_connection_debug(conn, "Peer closed");
-       http_client_connection_disconnect(conn);
-
-       if (http_client_connection_unref(_conn))
-               conn->peer = NULL;
-}
-
 void http_client_connection_switch_ioloop(struct http_client_connection *conn)
 {
        if (conn->io_req_payload != NULL)
index ecb3f7355604c9f289d9f1329b6b4c4d83aedbce..302b6873a794d25138892010edea52cf5c9370f2 100644 (file)
 
 #include "http-client-private.h"
 
+static void
+http_client_peer_connect_backoff(struct http_client_peer *peer);
+
+static void
+http_client_peer_shared_connection_success(
+       struct http_client_peer_shared *pshared);
+static void
+http_client_peer_shared_connection_failure(
+       struct http_client_peer_shared *pshared, const char *reason,
+       unsigned int pending);
+static void
+http_client_peer_connection_failed_any(struct http_client_peer *peer,
+                                        const char *reason);
+
 /*
  * Logging
  */
@@ -28,13 +42,33 @@ static inline void
 http_client_peer_pool_debug(struct http_client_peer_pool *ppool,
        const char *format, ...)
 {
-       struct http_client_peer *peer = ppool->peer;
+       struct http_client_peer_shared *pshared = ppool->peer;
        va_list args;
 
-       if (peer->client->cctx->set.debug) {
+       if (pshared->cctx->set.debug) {
                va_start(args, format);
                i_debug("http-client: peer %s: %s",
-                       http_client_peer_label(peer), t_strdup_vprintf(format, args));
+                       http_client_peer_shared_label(pshared), t_strdup_vprintf(format, args));
+               va_end(args);
+       }
+}
+
+/* Peer (shared) */
+
+static inline void
+http_client_peer_shared_debug(struct http_client_peer_shared *pshared,
+       const char *format, ...) ATTR_FORMAT(2, 3);
+
+static inline void
+http_client_peer_shared_debug(struct http_client_peer_shared *pshared,
+       const char *format, ...)
+{
+       va_list args;
+
+       if (pshared->cctx->set.debug) {
+               va_start(args, format);
+               i_debug("http-client: peer %s (shared): %s",
+                       http_client_peer_shared_label(pshared), t_strdup_vprintf(format, args));
                va_end(args);
        }
 }
@@ -49,12 +83,14 @@ static inline void
 http_client_peer_debug(struct http_client_peer *peer,
        const char *format, ...)
 {
+       struct http_client_peer_shared *pshared = peer->shared;
+       struct http_client *client = peer->client;
        va_list args;
 
-       if (peer->client->set.debug) {
+       if (client->set.debug) {
                va_start(args, format); 
                i_debug("http-client: peer %s: %s", 
-                       http_client_peer_label(peer), t_strdup_vprintf(format, args));
+                       http_client_peer_shared_label(pshared), t_strdup_vprintf(format, args));
                va_end(args);
        }
 }
@@ -127,17 +163,26 @@ int http_client_peer_addr_cmp
  */
 
 static struct http_client_peer_pool *
-http_client_peer_pool_create(struct http_client_peer *peer)
+http_client_peer_pool_create(struct http_client_peer_shared *pshared,
+       struct ssl_iostream_context *ssl_ctx, const char *rawlog_dir)
 {
        struct http_client_peer_pool *ppool;
 
        ppool = i_new(struct http_client_peer_pool, 1);
        ppool->refcount = 1;
-       ppool->peer = peer;
+       ppool->peer = pshared;
+
+       http_client_peer_shared_ref(pshared);
 
        i_array_init(&ppool->conns, 16);
+       i_array_init(&ppool->pending_conns, 16);
        i_array_init(&ppool->idle_conns, 16);
 
+       DLLIST_PREPEND(&pshared->pools_list, ppool);
+
+       ppool->ssl_ctx = ssl_ctx;
+       ppool->rawlog_dir = i_strdup(rawlog_dir);
+
        http_client_peer_pool_debug(ppool, "Peer pool created");
 
        return ppool;
@@ -164,6 +209,7 @@ void http_client_peer_pool_close(struct http_client_peer_pool **_ppool)
        array_foreach_modifiable(&conns, conn)
                http_client_connection_unref(conn);
        i_assert(array_count(&ppool->idle_conns) == 0);
+       i_assert(array_count(&ppool->pending_conns) == 0);
        i_assert(array_count(&ppool->conns) == 0);
 
        http_client_peer_pool_unref(_ppool);
@@ -172,6 +218,7 @@ void http_client_peer_pool_close(struct http_client_peer_pool **_ppool)
 void http_client_peer_pool_unref(struct http_client_peer_pool **_ppool)
 {
        struct http_client_peer_pool *ppool = *_ppool;
+       struct http_client_peer_shared *pshared = ppool->peer;
 
        *_ppool = NULL;
 
@@ -188,141 +235,395 @@ void http_client_peer_pool_unref(struct http_client_peer_pool **_ppool)
        i_assert(array_count(&ppool->idle_conns) == 0);
        i_assert(array_count(&ppool->conns) == 0);
        array_free(&ppool->idle_conns);
+       array_free(&ppool->pending_conns);
        array_free(&ppool->conns);
 
+       DLLIST_REMOVE(&pshared->pools_list, ppool);
+
+       i_free(ppool->rawlog_dir);
        i_free(ppool);
+       http_client_peer_shared_unref(&pshared);
+}
+
+static struct http_client_peer_pool *
+http_client_peer_pool_get(struct http_client_peer_shared *pshared,
+       struct http_client *client)
+{
+       struct http_client_peer_pool *ppool;
+       struct ssl_iostream_context *ssl_ctx = client->ssl_ctx;
+       const char *rawlog_dir = client->set.rawlog_dir;
+
+       i_assert(!http_client_peer_addr_is_https(&pshared->addr) ||
+               ssl_ctx != NULL);
+
+       ppool = pshared->pools_list;
+       while (ppool != NULL) {
+               if (ppool->ssl_ctx == ssl_ctx &&
+                       null_strcmp(ppool->rawlog_dir, rawlog_dir) == 0)
+                       break;
+               ppool = ppool->next;
+       }
+
+       if (ppool == NULL) {
+               ppool = http_client_peer_pool_create
+                       (pshared, ssl_ctx, rawlog_dir);
+       } else {
+               http_client_peer_pool_debug(ppool, "Peer pool reused");
+               http_client_peer_pool_ref(ppool);
+       }
+
+       return ppool;
+}
+
+static void
+http_client_peer_pool_connection_success(
+       struct http_client_peer_pool *ppool)
+{
+       http_client_peer_pool_debug(ppool,
+               "Successfully connected (connections=%u)",
+               array_count(&ppool->conns));
+
+       http_client_peer_shared_connection_success(ppool->peer);
+}
+
+static void
+http_client_peer_pool_connection_failure(
+       struct http_client_peer_pool *ppool, const char *reason)
+{
+       unsigned int pending;
+
+       /* count number of pending connections */
+       pending = array_count(&ppool->pending_conns);
+       i_assert(pending > 0);
+
+       http_client_peer_pool_debug(ppool,
+               "Failed to make connection "
+               "(connections=%u, connecting=%u)",
+               array_count(&ppool->conns), pending);
+
+       http_client_peer_shared_connection_failure(ppool->peer,
+               reason, pending);
 }
 
 /*
- * Peer
+ * Peer (shared)
  */
 
-static void
-http_client_peer_do_connect(struct http_client_peer *peer,
-       unsigned int count);
+static struct http_client_peer_shared *
+http_client_peer_shared_create(struct http_client_context *cctx,
+                             const struct http_client_peer_addr *addr)
+{
+       struct http_client_peer_shared *pshared;
+
+       pshared = i_new(struct http_client_peer_shared, 1);
+       pshared->refcount = 1;
+       pshared->cctx = cctx;
+
+       pshared->addr = *addr;
+       switch (addr->type) {
+       case HTTP_CLIENT_PEER_ADDR_RAW:
+       case HTTP_CLIENT_PEER_ADDR_HTTP:
+               i_assert(pshared->addr.a.tcp.ip.family != 0);
+               break;
+       case HTTP_CLIENT_PEER_ADDR_HTTPS:
+       case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+               i_assert(pshared->addr.a.tcp.ip.family != 0);
+               pshared->addr_name = i_strdup(addr->a.tcp.https_name);
+               pshared->addr.a.tcp.https_name = pshared->addr_name;
+               break;
+       case HTTP_CLIENT_PEER_ADDR_UNIX:
+               pshared->addr_name = i_strdup(addr->a.un.path);
+               pshared->addr.a.un.path = pshared->addr_name;
+               break;
+       default:
+               break;
+       }
+
+       hash_table_insert(cctx->peers,
+               (const struct http_client_peer_addr *)&pshared->addr, pshared);
+       DLLIST_PREPEND(&cctx->peers_list, pshared);
+
+       pshared->backoff_initial_time_msecs =
+               cctx->set.connect_backoff_time_msecs;
+       pshared->backoff_max_time_msecs =
+               cctx->set.connect_backoff_max_time_msecs;
+
+       http_client_peer_shared_debug(pshared, "Peer created");
+       return pshared;
+}
+
+void http_client_peer_shared_ref(struct http_client_peer_shared *pshared)
+{
+       pshared->refcount++;
+}
+
+void http_client_peer_shared_unref(struct http_client_peer_shared **_pshared)
+{
+       struct http_client_peer_shared *pshared = *_pshared;
+
+       *_pshared = NULL;
+
+       i_assert(pshared->refcount > 0);
+       if (--pshared->refcount > 0)
+               return;
+
+       http_client_peer_shared_debug(pshared, "Peer destroy");
+
+       i_assert(pshared->pools_list == NULL);
+
+       /* unlist in client */
+       hash_table_remove(pshared->cctx->peers,
+               (const struct http_client_peer_addr *)&pshared->addr);
+       DLLIST_REMOVE(&pshared->cctx->peers_list, pshared);
+
+       if (pshared->to_backoff != NULL)
+               timeout_remove(&pshared->to_backoff);
+
+       i_free(pshared->addr_name);
+       i_free(pshared->label);
+       i_free(pshared);
+}
+
+static struct http_client_peer_shared *
+http_client_peer_shared_get(struct http_client_context *cctx,
+                          const struct http_client_peer_addr *addr)
+{
+       struct http_client_peer_shared *pshared;
+
+       pshared = hash_table_lookup(cctx->peers, addr);
+       if (pshared == NULL) {
+               pshared = http_client_peer_shared_create(cctx, addr);
+       } else {
+               http_client_peer_shared_debug(pshared, "Peer reused");
+               http_client_peer_shared_ref(pshared);
+       }
+
+       return pshared;
+}
+
+void http_client_peer_shared_close(struct http_client_peer_shared **_pshared)
+{
+       struct http_client_peer_shared *pshared = *_pshared;
+       struct http_client_peer_pool *pool, *next;
+
+       http_client_peer_shared_ref(pshared);
+       pool = pshared->pools_list;
+       while (pool != NULL) {
+               next = pool->next;
+               http_client_peer_pool_close(&pool);
+               pool = next;
+       }
+       http_client_peer_shared_unref(_pshared);
+}
 
 const char *
-http_client_peer_label(struct http_client_peer *peer)
+http_client_peer_shared_label(struct http_client_peer_shared *pshared)
 {
-       if (peer->label == NULL) {
-               switch (peer->addr.type) {
+       if (pshared->label == NULL) {
+               switch (pshared->addr.type) {
                case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
-                       peer->label = i_strconcat
-                               (http_client_peer_addr2str(&peer->addr), " (tunnel)", NULL);
+                       pshared->label = i_strconcat
+                               (http_client_peer_addr2str(&pshared->addr), " (tunnel)", NULL);
                        break;
                default:
-                       peer->label = i_strdup
-                               (http_client_peer_addr2str(&peer->addr));
+                       pshared->label = i_strdup
+                               (http_client_peer_addr2str(&pshared->addr));
                }
        }
-       return peer->label;
+       return pshared->label;
 }
 
 static void
-http_client_peer_connect_backoff(struct http_client_peer *peer)
+http_client_peer_shared_connect_backoff(
+       struct http_client_peer_shared *pshared)
 {
-       i_assert(peer->to_backoff != NULL);
+       struct http_client_peer *peer;
 
-       http_client_peer_debug(peer,
-               "Backoff timer expired");
+       i_assert(pshared->to_backoff != NULL);
 
-       timeout_remove(&peer->to_backoff);
+       http_client_peer_shared_debug(pshared, "Backoff timer expired");
 
-       if (array_count(&peer->queues) == 0) {
-               http_client_peer_close(&peer);
-               return;
-       }
+       timeout_remove(&pshared->to_backoff);
 
-       http_client_peer_do_connect(peer, 1);
+       peer = pshared->peers_list;
+       while (peer != NULL) {
+               http_client_peer_connect_backoff(peer);
+               peer = peer->shared_next;
+       }
 }
 
 static bool
-http_client_peer_start_backoff_timer(struct http_client_peer *peer)
+http_client_peer_shared_start_backoff_timer(
+       struct http_client_peer_shared *pshared)
 {
-       if (peer->to_backoff != NULL)
+       if (pshared->to_backoff != NULL)
                return TRUE;
 
-       if (peer->last_failure.tv_sec > 0) {
+       if (pshared->last_failure.tv_sec > 0) {
                int backoff_time_spent =
-                       timeval_diff_msecs(&ioloop_timeval, &peer->last_failure);
-
-               if (backoff_time_spent < (int)peer->backoff_time_msecs) {
-                       http_client_peer_debug(peer,
-                               "Starting backoff timer for %d msecs",
-                               peer->backoff_time_msecs - backoff_time_spent);
-                       peer->to_backoff = timeout_add
-                               ((unsigned int)(peer->backoff_time_msecs - backoff_time_spent),
-                                       http_client_peer_connect_backoff, peer);
+                       timeval_diff_msecs(&ioloop_timeval, &pshared->last_failure);
+
+               if (backoff_time_spent < (int)pshared->backoff_current_time_msecs) {
+                       unsigned int new_time = (unsigned int)
+                               (pshared->backoff_current_time_msecs - backoff_time_spent);
+                       http_client_peer_shared_debug(pshared,
+                               "Starting backoff timer for %d msecs", new_time);
+                       pshared->to_backoff = timeout_add(new_time,
+                                       http_client_peer_shared_connect_backoff, pshared);
                        return TRUE;
                }
 
-               http_client_peer_debug(peer,
+               http_client_peer_shared_debug(pshared,
                        "Backoff time already exceeded by %d msecs",
-                       backoff_time_spent - peer->backoff_time_msecs);
+                       backoff_time_spent - pshared->backoff_current_time_msecs);
        }
        return FALSE;
 }
 
 static void
-http_client_peer_increase_backoff_timer(struct http_client_peer *peer)
+http_client_peer_shared_increase_backoff_timer(
+       struct http_client_peer_shared *pshared)
 {
-       const struct http_client_settings *set = &peer->client->set;
-
-       if (peer->backoff_time_msecs == 0)
-               peer->backoff_time_msecs = set->connect_backoff_time_msecs;
+       if (pshared->backoff_current_time_msecs == 0)
+               pshared->backoff_current_time_msecs = pshared->backoff_initial_time_msecs;
        else
-               peer->backoff_time_msecs *= 2;
-       if (peer->backoff_time_msecs > set->connect_backoff_max_time_msecs)
-               peer->backoff_time_msecs = set->connect_backoff_max_time_msecs;
+               pshared->backoff_current_time_msecs *= 2;
+       if (pshared->backoff_current_time_msecs >
+               pshared->backoff_max_time_msecs) {
+               pshared->backoff_current_time_msecs = pshared->backoff_max_time_msecs;
+       }
+}
+
+static void
+http_client_peer_shared_reset_backoff_timer(
+       struct http_client_peer_shared *pshared)
+{
+       pshared->backoff_current_time_msecs = 0;
+
+       timeout_remove(&pshared->to_backoff);
+}
+
+static void
+http_client_peer_shared_connection_success(
+       struct http_client_peer_shared *pshared)
+{
+       pshared->last_failure.tv_sec = pshared->last_failure.tv_usec = 0;
+       http_client_peer_shared_reset_backoff_timer(pshared);
+}
+
+static void
+http_client_peer_shared_connection_failure(
+       struct http_client_peer_shared *pshared, const char *reason,
+       unsigned int pending)
+{
+       pshared->last_failure = ioloop_timeval;
+
+       /* manage backoff timer only when this was the only attempt */
+       if (pending == 1)
+               http_client_peer_shared_increase_backoff_timer(pshared);
+
+       if (pending > 1) {
+               /* if there are other connections attempting to connect, wait
+                  for them before failing the requests. remember that we had
+                  trouble with connecting so in future we don't try to create
+                  more than one connection until connects work again. */
+       } else {
+               struct http_client_peer *peer;
+
+               /* this was the only/last connection and connecting to it
+                  failed. notify all interested peers about the failure */
+               peer = pshared->peers_list;
+               while (peer != NULL) {
+                       struct http_client_peer *peer_next = peer->shared_next;
+                       http_client_peer_connection_failed_any(peer, reason);
+                       peer = peer_next;
+               }
+       }
 }
 
 static void
-http_client_peer_reset_backoff_timer(struct http_client_peer *peer)
+http_client_peer_shared_connection_lost(
+       struct http_client_peer_shared *pshared,
+       bool premature)
 {
-       peer->backoff_time_msecs = 0;
+       /* update backoff timer if the connection was lost prematurely.
+          this prevents reconnecting immediately to a server that is
+          misbehaving by disconnecting before sending a response.
+        */
+       if (premature) {
+               pshared->last_failure = ioloop_timeval;
+               http_client_peer_shared_increase_backoff_timer(pshared);
+       }
+}
 
-       timeout_remove(&peer->to_backoff);
+void http_client_peer_shared_switch_ioloop(
+       struct http_client_peer_shared *pshared)
+{
+       if (pshared->to_backoff != NULL) {
+               pshared->to_backoff =
+                       io_loop_move_timeout(&pshared->to_backoff);
+       }
 }
 
+unsigned int
+http_client_peer_shared_max_connections(
+       struct http_client_peer_shared *pshared)
+{
+       struct http_client_peer *peer;
+       unsigned int max_conns = 0;
+
+       peer = pshared->peers_list;
+       while (peer != NULL) {
+               max_conns += peer->client->set.max_parallel_connections;
+               peer = peer->shared_next;
+       }
+
+       return max_conns;
+}
+
+/*
+ * Peer
+ */
+
+static void
+http_client_peer_drop(struct http_client_peer **_peer);
+
 static struct http_client_peer *
 http_client_peer_create(struct http_client *client,
-                             const struct http_client_peer_addr *addr)
+                             struct http_client_peer_shared *pshared)
 {
        struct http_client_peer *peer;
 
        peer = i_new(struct http_client_peer, 1);
        peer->refcount = 1;
        peer->client = client;
-       peer->addr = *addr;
-
-       switch (addr->type) {
-       case HTTP_CLIENT_PEER_ADDR_RAW:
-       case HTTP_CLIENT_PEER_ADDR_HTTP:
-               i_assert(peer->addr.a.tcp.ip.family != 0);
-               break;
-       case HTTP_CLIENT_PEER_ADDR_HTTPS:
-       case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
-               i_assert(peer->addr.a.tcp.ip.family != 0);
-               i_assert(client->ssl_ctx != NULL);
-               peer->addr_name = i_strdup(addr->a.tcp.https_name);
-               peer->addr.a.tcp.https_name = peer->addr_name;
-               break;
-       case HTTP_CLIENT_PEER_ADDR_UNIX:
-               peer->addr_name = i_strdup(addr->a.un.path);
-               peer->addr.a.un.path = peer->addr_name;
-               break;
-       default:
-               break;
-       }
+       peer->shared = pshared;
 
        i_array_init(&peer->queues, 16);
        i_array_init(&peer->conns, 16);
 
-       hash_table_insert
-               (client->peers, (const struct http_client_peer_addr *)&peer->addr, peer);
-       DLLIST_PREPEND(&client->peers_list, peer);
-
-       peer->ppool = http_client_peer_pool_create(peer);
+       DLLIST_PREPEND_FULL
+               (&client->peers_list, peer, client_prev, client_next);
+       DLLIST_PREPEND_FULL
+               (&pshared->peers_list, peer, shared_prev, shared_next);
+       pshared->peers_count++;
+
+       http_client_peer_shared_ref(pshared);
+       peer->ppool = http_client_peer_pool_get(pshared, client);
+
+       /* choose backoff times */
+       if (pshared->peers_list == NULL ||
+               client->set.connect_backoff_time_msecs <
+                       pshared->backoff_initial_time_msecs) {
+               pshared->backoff_initial_time_msecs =
+                       client->set.connect_backoff_time_msecs;
+       }
+       if (pshared->peers_list == NULL ||
+               client->set.connect_backoff_max_time_msecs >
+                       pshared->backoff_max_time_msecs) {
+               pshared->backoff_max_time_msecs =
+                       client->set.connect_backoff_max_time_msecs;
+       }
 
        http_client_peer_debug(peer, "Peer created");
        return peer;
@@ -336,9 +637,11 @@ void http_client_peer_ref(struct http_client_peer *peer)
 static void
 http_client_peer_disconnect(struct http_client_peer *peer)
 {
+       struct http_client_queue *const *queue;
+       struct http_client *client = peer->client;
+       struct http_client_peer_shared *pshared = peer->shared;
        struct http_client_connection **conn;
        ARRAY_TYPE(http_client_connection) conns;
-       struct http_client_queue *const *queue;
 
        if (peer->disconnected)
                return;
@@ -348,21 +651,22 @@ http_client_peer_disconnect(struct http_client_peer *peer)
 
        /* make a copy of the connection array; freed connections modify it */
        t_array_init(&conns, array_count(&peer->conns));
-       array_copy(&conns.arr, 0, &peer->conns.arr, 0, array_count(&peer->conns));
-       array_foreach_modifiable(&conns, conn) {
-               http_client_connection_peer_closed(conn);
-       }
+       array_copy(&conns.arr, 0, &peer->conns.arr,
+               0, array_count(&peer->conns));
+       array_foreach_modifiable(&conns, conn)
+               http_client_connection_lost_peer(*conn);
        i_assert(array_count(&peer->conns) == 0);
 
        if (peer->to_req_handling != NULL)
                timeout_remove(&peer->to_req_handling);
-       if (peer->to_backoff != NULL)
-               timeout_remove(&peer->to_backoff);
 
        /* unlist in client */
-       hash_table_remove
-               (peer->client->peers, (const struct http_client_peer_addr *)&peer->addr);
-       DLLIST_REMOVE(&peer->client->peers_list, peer);
+       DLLIST_REMOVE_FULL
+               (&client->peers_list, peer, client_prev, client_next);
+       /* unlist in peer */
+       DLLIST_REMOVE_FULL
+               (&pshared->peers_list, peer, shared_prev, shared_next);
+       pshared->peers_count--;
 
        /* unlink all queues */
        array_foreach(&peer->queues, queue)
@@ -374,26 +678,44 @@ bool http_client_peer_unref(struct http_client_peer **_peer)
 {
        struct http_client_peer *peer = *_peer;
        struct http_client_peer_pool *ppool = peer->ppool;
-
-       i_assert(peer->refcount > 0);
+       struct http_client_peer_shared *pshared = peer->shared;
 
        *_peer = NULL;
 
+       i_assert(peer->refcount > 0);
        if (--peer->refcount > 0)
                return TRUE;
 
        http_client_peer_debug(peer, "Peer destroy");
 
        http_client_peer_disconnect(peer);
-       http_client_peer_pool_unref(&ppool);
 
        i_assert(array_count(&peer->queues) == 0);
 
        array_free(&peer->conns);
        array_free(&peer->queues);
-       i_free(peer->addr_name);
-       i_free(peer->label);
        i_free(peer);
+
+       /* choose new backoff times */
+       peer = pshared->peers_list;
+       while (peer != NULL) {
+               struct http_client *client = peer->client;
+
+               if (client->set.connect_backoff_time_msecs <
+                               pshared->backoff_initial_time_msecs) {
+                       pshared->backoff_initial_time_msecs =
+                               client->set.connect_backoff_time_msecs;
+               }
+               if (client->set.connect_backoff_max_time_msecs >
+                               pshared->backoff_max_time_msecs) {
+                       pshared->backoff_max_time_msecs =
+                               client->set.connect_backoff_max_time_msecs;
+               }
+               peer = peer->shared_next;
+       }
+
+       http_client_peer_pool_unref(&ppool);
+       http_client_peer_shared_unref(&pshared);
        return FALSE;
 }
 
@@ -408,9 +730,11 @@ void http_client_peer_close(struct http_client_peer **_peer)
        (void)http_client_peer_unref(_peer);
 }
 
-static void http_client_peer_drop(struct http_client_peer **_peer)
+static void
+http_client_peer_drop(struct http_client_peer **_peer)
 {
        struct http_client_peer *peer = *_peer;
+       struct http_client_peer_shared *pshared = peer->shared;
        unsigned int conns_active =
                http_client_peer_active_connections(peer);
 
@@ -421,10 +745,10 @@ static void http_client_peer_drop(struct http_client_peer **_peer)
                return;
        }
 
-       if (peer->to_backoff != NULL)
+       if (pshared->to_backoff != NULL)
                return;
 
-       if (http_client_peer_start_backoff_timer(peer)) {
+       if (http_client_peer_shared_start_backoff_timer(pshared)) {
                http_client_peer_debug(peer,
                        "Dropping peer (waiting for backof timeout)");
 
@@ -443,11 +767,21 @@ http_client_peer_get(struct http_client *client,
                           const struct http_client_peer_addr *addr)
 {
        struct http_client_peer *peer;
+       struct http_client_peer_shared *pshared;
+
+       pshared = http_client_peer_shared_get(client->cctx, addr);
+
+       peer = pshared->peers_list;
+       while (peer != NULL) {
+               if (peer->client == client)
+                       break;
+               peer = peer->shared_next;
+       }
 
-       peer = hash_table_lookup(client->peers, addr);
        if (peer == NULL)
-               peer = http_client_peer_create(client, addr);
+               peer = http_client_peer_create(client, pshared);
 
+       http_client_peer_shared_unref(&pshared);
        return peer;
 }
 
@@ -464,10 +798,10 @@ http_client_peer_do_connect(struct http_client_peer *peer,
 
        idle_conns = array_get(&ppool->idle_conns, &idle_count);
        for (i = 0; i < count && i < idle_count; i++) {
-               http_client_connection_use_idle(idle_conns[i], peer);
+               http_client_connection_claim_idle(idle_conns[i], peer);
 
                http_client_peer_debug(peer,
-                       "Using idle connection (connections=%u)",
+                       "Claimed idle connection (connections=%u)",
                        array_count(&peer->conns));
        }
 
@@ -478,11 +812,28 @@ http_client_peer_do_connect(struct http_client_peer *peer,
        }
 }
 
+static void
+http_client_peer_connect_backoff(struct http_client_peer *peer)
+{
+       if (peer->connect_backoff &&
+               array_count(&peer->queues) == 0) {
+               http_client_peer_close(&peer);
+               return;
+       }
+
+       http_client_peer_do_connect(peer, 1);
+       peer->connect_backoff = FALSE;
+}
+
 static void
 http_client_peer_connect(struct http_client_peer *peer, unsigned int count)
 {
-       if (http_client_peer_start_backoff_timer(peer))
+       peer->connecting = TRUE;
+
+       if (http_client_peer_shared_start_backoff_timer(peer->shared)) {
+               peer->connect_backoff = TRUE;
                return;
+       }
 
        http_client_peer_do_connect(peer, count);
 }
@@ -510,6 +861,8 @@ http_client_peer_cancel(struct http_client_peer *peer)
 
        http_client_peer_debug(peer, "Peer cancel");
 
+       peer->connecting = FALSE;
+
        /* make a copy of the connection array; freed connections modify it */
        t_array_init(&conns, array_count(&peer->conns));
        array_copy(&conns.arr, 0, &peer->conns.arr, 0, array_count(&peer->conns));
@@ -564,6 +917,7 @@ http_client_peer_handle_requests_real(struct http_client_peer *peer)
        struct http_client_connection *const *conn_idx;
        ARRAY(struct _conn_available) conns_avail;
        struct _conn_available *conn_avail_idx;
+       struct http_client_peer_shared *pshared = peer->shared;
        unsigned int connecting, closing, idle;
        unsigned int num_pending, num_urgent, new_connections,  working_conn_count;
        struct http_client_peer *tmp_peer;
@@ -575,7 +929,7 @@ http_client_peer_handle_requests_real(struct http_client_peer *peer)
        /* disconnect pending connections if we're not linked to any queue
           anymore */
        if (array_count(&peer->queues) == 0) {
-               if (array_count(&peer->conns) == 0 && peer->to_backoff == NULL) {
+               if (array_count(&peer->conns) == 0 && pshared->to_backoff == NULL) {
                        /* peer is completely unused and inactive; drop it immediately */
                        http_client_peer_drop(&peer);
                        return;
@@ -700,7 +1054,7 @@ http_client_peer_handle_requests_real(struct http_client_peer *peer)
        i_assert(idle == 0);
 
        /* determine how many new connections we can set up */
-       if (peer->last_failure.tv_sec > 0 && working_conn_count > 0 &&
+       if (pshared->last_failure.tv_sec > 0 && working_conn_count > 0 &&
            working_conn_count == connecting) {
                /* don't create new connections until the existing ones have
                   finished connecting successfully. */
@@ -749,7 +1103,7 @@ http_client_peer_handle_requests_real(struct http_client_peer *peer)
                peer->client->set.max_parallel_connections) {
                unsigned int pipeline_level = 0, total_handled = 0, handled;
 
-               if (!peer->allows_pipelining) {
+               if (!pshared->allows_pipelining) {
                        http_client_peer_debug(peer,
                                "Will not pipeline until peer has shown support");
                        return;
@@ -868,7 +1222,7 @@ http_client_peer_claim_request(struct http_client_peer *peer, bool no_urgent)
 
        array_foreach(&peer->queues, queue_idx) {
                if ((req=http_client_queue_claim_request
-                       (*queue_idx, &peer->addr, no_urgent)) != NULL) {
+                       (*queue_idx, &peer->shared->addr, no_urgent)) != NULL) {
                        req->peer = peer;
                        return req;
                }
@@ -879,57 +1233,49 @@ http_client_peer_claim_request(struct http_client_peer *peer, bool no_urgent)
 
 void http_client_peer_connection_success(struct http_client_peer *peer)
 {
+       struct http_client_peer_pool *ppool = peer->ppool;
        struct http_client_queue *const *queue;
 
+       peer->connecting = FALSE;
+
+       http_client_peer_pool_connection_success(ppool);
+
        http_client_peer_debug(peer,
                "Successfully connected (connections=%u)",
                array_count(&peer->conns));
 
-       peer->last_failure.tv_sec = peer->last_failure.tv_usec = 0;
-       http_client_peer_reset_backoff_timer(peer);
-
-       array_foreach(&peer->queues, queue) {
-               http_client_queue_connection_success(*queue, &peer->addr);
-       }
+       array_foreach(&peer->queues, queue)
+               http_client_queue_connection_success(*queue, peer);
 
        http_client_peer_trigger_request_handler(peer);
 }
 
-void http_client_peer_connection_failure(struct http_client_peer *peer,
+static void
+http_client_peer_connection_failed_any(struct http_client_peer *peer,
                                         const char *reason)
 {
        struct http_client_queue *const *queue;
-       unsigned int pending;
-
-       peer->last_failure = ioloop_timeval;
 
-       /* count number of pending connections */
-       pending = http_client_peer_pending_connections(peer);
-       i_assert(pending > 0);
+       if (!peer->connecting)
+               return;
+       peer->connecting = FALSE;
 
        http_client_peer_debug(peer,
-               "Failed to make connection "
-               "(connections=%u, connecting=%u)",
-               array_count(&peer->conns), pending);
+               "Connection failed: %s", reason);
 
-       /* manage backoff timer only when this was the only attempt */
-       if (pending == 1)
-               http_client_peer_increase_backoff_timer(peer);
+       /* failed to make any connection. a second connect will probably also
+          fail, so just try another IP for the hosts(s) or abort all requests
+          if this was the only/last option. */
+       array_foreach(&peer->queues, queue)
+               http_client_queue_connection_failure(*queue, peer, reason);
+}
 
-       if (pending > 1) {
-               /* if there are other connections attempting to connect, wait
-                  for them before failing the requests. remember that we had
-                  trouble with connecting so in future we don't try to create
-                  more than one connection until connects work again. */
-       } else {
-               /* this was the only/last connection and connecting to it
-                  failed. a second connect will probably also fail, so just
-                  try another IP for the hosts(s) or abort all requests if this
-                  was the only/last option. */
-               array_foreach(&peer->queues, queue) {
-                       http_client_queue_connection_failure(*queue, &peer->addr, reason);
-               }
-       }
+void http_client_peer_connection_failure(struct http_client_peer *peer,
+                                        const char *reason)
+{
+       struct http_client_peer_pool *ppool = peer->ppool;
+
+       http_client_peer_pool_connection_failure(ppool, reason);
 }
 
 void http_client_peer_connection_lost(struct http_client_peer *peer,
@@ -938,12 +1284,14 @@ void http_client_peer_connection_lost(struct http_client_peer *peer,
        unsigned int num_pending, num_urgent;
 
        /* we get here when an already connected connection fails. if the
-          connect itself fails, http_client_peer_connection_failure() is
+          connect itself fails, http_client_peer_shared_connection_failure() is
           called instead. */
 
        if (peer->disconnected)
                return;
 
+       http_client_peer_shared_connection_lost(peer->shared, premature);
+
        num_pending = http_client_peer_requests_pending(peer, &num_urgent);
 
        http_client_peer_debug(peer,
@@ -953,15 +1301,6 @@ void http_client_peer_connection_lost(struct http_client_peer *peer,
                array_count(&peer->queues), array_count(&peer->conns),
                num_pending, num_urgent);
 
-       /* update backoff timer if the connection was lost prematurely.
-          this prevents reconnecting immediately to a server that is
-          misbehaving by disconnecting before sending a response.
-        */
-       if (premature) {
-               peer->last_failure = ioloop_timeval;
-               http_client_peer_increase_backoff_timer(peer);
-       }
-
        if (peer->handling_requests) {
                /* we got here from the request handler loop */
                http_client_peer_debug(peer,
@@ -974,21 +1313,6 @@ void http_client_peer_connection_lost(struct http_client_peer *peer,
        http_client_peer_trigger_request_handler(peer);
 }
 
-unsigned int
-http_client_peer_idle_connections(struct http_client_peer *peer)
-{
-       struct http_client_connection *const *conn_idx;
-       unsigned int idle = 0;
-
-       /* find idle connections */
-       array_foreach(&peer->conns, conn_idx) {
-               if (http_client_connection_is_idle(*conn_idx))
-                       idle++;
-       }
-
-       return idle;
-}
-
 unsigned int
 http_client_peer_active_connections(struct http_client_peer *peer)
 {
@@ -1025,9 +1349,6 @@ void http_client_peer_switch_ioloop(struct http_client_peer *peer)
                peer->to_req_handling =
                        io_loop_move_timeout(&peer->to_req_handling);
        }
-       if (peer->to_backoff != NULL) {
-               peer->to_backoff =
-                       io_loop_move_timeout(&peer->to_backoff);
-       }
-}
 
+       http_client_peer_shared_switch_ioloop(peer->shared);
+}
index 8f89476c226a16b1f382af9363d91cdbf95ef24d..8a93ef10008a6272d3832ffc8576d1d48641f8df 100644 (file)
@@ -25,6 +25,8 @@
  */
 
 struct http_client_connection;
+struct http_client_peer_pool;
+struct http_client_peer_shared;
 struct http_client_peer;
 struct http_client_queue;
 struct http_client_host_shared;
@@ -36,14 +38,18 @@ ARRAY_DEFINE_TYPE(http_client_connection,
        struct http_client_connection *);
 ARRAY_DEFINE_TYPE(http_client_peer,
        struct http_client_peer *);
+ARRAY_DEFINE_TYPE(http_client_peer_shared,
+       struct http_client_peer_shared *);
+ARRAY_DEFINE_TYPE(http_client_peer_pool,
+       struct http_client_peer_pool *);
 ARRAY_DEFINE_TYPE(http_client_queue,
        struct http_client_queue *);
 ARRAY_DEFINE_TYPE(http_client_host,
-       struct http_client_host *);
+       struct http_client_host_shared *);
 
-HASH_TABLE_DEFINE_TYPE(http_client_peer,
+HASH_TABLE_DEFINE_TYPE(http_client_peer_shared,
        const struct http_client_peer_addr *,
-       struct http_client_peer *);
+       struct http_client_peer_shared *);
 HASH_TABLE_DEFINE_TYPE(http_client_host_shared,
        const char *, struct http_client_host_shared *);
 
@@ -157,7 +163,6 @@ struct http_client_connection {
 
        struct http_client_peer_pool *ppool;
        struct http_client_peer *peer;
-       struct http_client *client;
 
        char *label;
        unsigned int id; // DEBUG: identify parallel connections
@@ -194,31 +199,66 @@ struct http_client_connection {
        bool output_locked:1;       /* output is locked; no pipelining */
        bool output_broken:1;       /* output is broken; no more requests */
        bool in_req_callback:1;     /* performing request callback (busy) */
+       bool debug:1;
+};
+
+struct http_client_peer_shared {
+       unsigned int refcount;
+       struct http_client_peer_addr addr;
+       char *addr_name;
+
+       char *label;
+
+       struct http_client_context *cctx;
+       struct http_client_peer_shared *prev, *next;
+
+       struct http_client_peer_pool *pools_list;
+
+       struct http_client_peer *peers_list;
+       unsigned int peers_count;
+
+       /* connection retry */
+       struct timeval last_failure;
+       struct timeout *to_backoff;
+       unsigned int backoff_initial_time_msecs;
+       unsigned int backoff_current_time_msecs;
+       unsigned int backoff_max_time_msecs;
+
+       bool no_payload_sync:1;  /* expect: 100-continue failed before */
+       bool seen_100_response:1;/* expect: 100-continue succeeded before */
+       bool allows_pipelining:1;/* peer is known to allow persistent
+                                   connections */
 };
 
 struct http_client_peer_pool {
        unsigned int refcount;
-       struct http_client_peer *peer;
+       struct http_client_peer_shared *peer;
        struct http_client_peer_pool *prev, *next;
 
        /* all connections to this peer */
        ARRAY_TYPE(http_client_connection) conns;
 
+       /* pending connections (not ready connecting) */
+       ARRAY_TYPE(http_client_connection) pending_conns;
+
        /* available connections to this peer */
        ARRAY_TYPE(http_client_connection) idle_conns;
 
+       /* distinguishing settings for these connections */
+       struct ssl_iostream_context *ssl_ctx;
+       char *rawlog_dir;
+       struct pcap_output *pcap_output;
+
        bool destroyed:1;        /* peer pool is being destroyed */
 };
 
 struct http_client_peer {
        unsigned int refcount;
-       struct http_client_peer_addr addr;
-       char *addr_name;
-
-       char *label;
+       struct http_client_peer_shared *shared;
+       struct http_client_peer *shared_prev, *shared_next;
 
        struct http_client *client;
-       struct http_client_peer *prev, *next;
+       struct http_client_peer *client_prev, *client_next;
 
        struct http_client_peer_pool *ppool;
 
@@ -231,21 +271,16 @@ struct http_client_peer {
        /* zero time-out for consolidating request handling */
        struct timeout *to_req_handling;
 
-       /* connection retry */
-       struct timeval last_failure;
-       struct timeout *to_backoff;
-       unsigned int backoff_time_msecs;
-
+       bool connecting:1;       /* peer is waiting to be connected */
+       bool connect_backoff:1;  /* peer is waiting for backoff timout*/
        bool disconnected:1;     /* peer is already disconnected */
-       bool no_payload_sync:1;  /* expect: 100-continue failed before */
-       bool seen_100_response:1;/* expect: 100-continue succeeded before */
-       bool allows_pipelining:1;/* peer is known to allow persistent
-                                   connections */
        bool handling_requests:1;/* currently running request handler */
 };
 
 struct http_client_queue {
        struct http_client *client;
+       struct http_client_queue *prev, *next;
+
        struct http_client_host *host;
        char *name;
 
@@ -330,8 +365,8 @@ struct http_client {
        struct timeout *to_failing_requests;
 
        struct http_client_host *hosts_list;
-       HASH_TABLE_TYPE(http_client_peer) peers;
        struct http_client_peer *peers_list;
+
        struct http_client_request *requests_list;
        unsigned int requests_count;
 };
@@ -344,6 +379,8 @@ struct http_client_context {
 
        struct connection_list *conn_list;
 
+       HASH_TABLE_TYPE(http_client_peer_shared) peers;
+       struct http_client_peer_shared *peers_list;
        HASH_TABLE_TYPE(http_client_host_shared) hosts;
        struct http_client_host_shared *unix_host;
        struct http_client_host_shared *hosts_list;
@@ -479,7 +516,8 @@ void http_client_connection_check_idle(struct http_client_connection *conn);
 void http_client_connection_switch_ioloop(struct http_client_connection *conn);
 void http_client_connection_start_tunnel(struct http_client_connection **_conn,
        struct http_client_tunnel *tunnel);
-void http_client_connection_use_idle(struct http_client_connection *conn,
+void http_client_connection_lost_peer(struct http_client_connection *conn);
+void http_client_connection_claim_idle(struct http_client_connection *conn,
        struct http_client_peer *peer);
 
 /*
@@ -501,10 +539,21 @@ void http_client_peer_pool_unref(struct http_client_peer_pool **_ppool);
 
 void http_client_peer_pool_close(struct http_client_peer_pool **_ppool);
 
-/* peer */
+/* peer (shared) */
 
 const char *
-http_client_peer_label(struct http_client_peer *peer);
+http_client_peer_shared_label(struct http_client_peer_shared *pshared);
+
+void http_client_peer_shared_ref(struct http_client_peer_shared *pshared);
+void http_client_peer_shared_unref(struct http_client_peer_shared **_pshared);
+void http_client_peer_shared_close(struct http_client_peer_shared **_pshared);
+
+void http_client_peer_shared_switch_ioloop(struct http_client_peer_shared *pshared);
+
+unsigned int
+http_client_peer_shared_max_connections(struct http_client_peer_shared *pshared);
+
+/* peer */
 
 struct http_client_peer *
 http_client_peer_get(struct http_client *client,
@@ -563,9 +612,9 @@ http_client_queue_requests_pending(struct http_client_queue *queue,
 unsigned int
 http_client_queue_requests_active(struct http_client_queue *queue);
 void http_client_queue_connection_success(struct http_client_queue *queue,
-                                        const struct http_client_peer_addr *addr);
+                                        struct http_client_peer *peer);
 void http_client_queue_connection_failure(struct http_client_queue *queue,
-       const struct http_client_peer_addr *addr, const char *reason);
+               struct http_client_peer *peer, const char *reason);
 void http_client_queue_peer_disconnected(struct http_client_queue *queue,
        struct http_client_peer *peer);
 void http_client_queue_switch_ioloop(struct http_client_queue *queue);
index c9634cc6c0c420d6a6f7e1e882a31e9060bdd121..ebc662c657df3a7b926db9306d957d028ce721bd 100644 (file)
@@ -44,6 +44,7 @@ http_client_queue_debug(struct http_client_queue *queue,
 
        if (queue->client->set.debug) {
 
+               // FIXME: find some other method of distinguishing clients
                va_start(args, format); 
                i_debug("http-client: queue %s: %s", 
                        queue->name, t_strdup_vprintf(format, args));
@@ -141,6 +142,9 @@ void http_client_queue_free(struct http_client_queue *queue)
 
        http_client_queue_debug(queue, "Destroy");
 
+       /* currently only called when peer is freed, so there is no need to
+          unlink from the peer */
+
        /* unlink all peers */
        if (queue->cur_peer != NULL) {
                struct http_client_peer *peer = queue->cur_peer;
@@ -260,7 +264,7 @@ http_client_queue_recover_from_lookup(struct http_client_queue *queue)
        }
 
        if (http_client_host_get_ip_idx(host,
-               &queue->cur_peer->addr.a.tcp.ip, &ip_idx)) {
+               &queue->cur_peer->shared->addr.a.tcp.ip, &ip_idx)) {
                /* continue with current peer */
                queue->ips_connect_idx = queue->ips_connect_start_idx = ip_idx;
        } else {
@@ -305,6 +309,7 @@ http_client_queue_soft_connect_timeout(struct http_client_queue *queue)
 static struct http_client_peer *
 http_client_queue_connection_attempt(struct http_client_queue *queue)
 {
+       struct http_client *client = queue->client;
        struct http_client_host *host = queue->host;
        struct http_client_peer *peer;
        struct http_client_peer_addr *addr = &queue->addr;
@@ -343,7 +348,8 @@ http_client_queue_connection_attempt(struct http_client_queue *queue)
                i_assert(array_count(&queue->pending_peers) == 0);
 
                /* is it still the one we want? */
-               if (http_client_peer_addr_cmp(addr, &queue->cur_peer->addr) == 0) {
+               if (http_client_peer_addr_cmp
+                       (addr, &queue->cur_peer->shared->addr) == 0) {
                        /* is it still connected? */
                        if (http_client_peer_is_connected(queue->cur_peer)) {
                                /* yes */
@@ -385,13 +391,16 @@ http_client_queue_connection_attempt(struct http_client_queue *queue)
                        struct http_client_peer *const *peer_idx;
 
                        array_foreach(&queue->pending_peers, peer_idx) {
-                               i_assert(http_client_peer_addr_cmp(&(*peer_idx)->addr, addr) != 0);
+                               i_assert(http_client_peer_addr_cmp
+                                       (&(*peer_idx)->shared->addr, addr) != 0);
                                http_client_peer_unlink_queue(*peer_idx, queue);
                        }
                        array_clear(&queue->pending_peers);
                }
                queue->cur_peer = peer;
 
+               http_client_peer_trigger_request_handler(queue->cur_peer);
+
        } else {
                struct http_client_peer *const *peer_idx;
                unsigned int msecs;
@@ -401,7 +410,8 @@ http_client_queue_connection_attempt(struct http_client_queue *queue)
 
                /* we may be waiting for this peer already */
                array_foreach(&queue->pending_peers, peer_idx) {
-                       if (http_client_peer_addr_cmp(&(*peer_idx)->addr, addr) == 0) {
+                       if (http_client_peer_addr_cmp
+                               (&(*peer_idx)->shared->addr, addr) == 0) {
                                i_assert(*peer_idx == peer);
                                new_peer = FALSE;
                                break;
@@ -418,7 +428,7 @@ http_client_queue_connection_attempt(struct http_client_queue *queue)
 
                /* start soft connect time-out (but only if we have another IP left) */
                if (queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) {
-                       msecs = host->client->set.soft_connect_timeout_msecs;
+                       msecs = client->set.soft_connect_timeout_msecs;
                        if (!http_client_queue_is_last_connect_ip(queue) && msecs > 0 &&
                                queue->to_connect == NULL) {
                                queue->to_connect =
@@ -456,8 +466,9 @@ void http_client_queue_host_lookup_failure(
 
 void
 http_client_queue_connection_success(struct http_client_queue *queue,
-                                        const struct http_client_peer_addr *addr)
+                                        struct http_client_peer *peer)
 {
+       const struct http_client_peer_addr *addr = &peer->shared->addr;
        struct http_client_host *host = queue->host;
 
        if (http_client_host_ready(host) &&
@@ -483,7 +494,7 @@ http_client_queue_connection_success(struct http_client_queue *queue,
                struct http_client_peer *const *peer_idx;
 
                array_foreach(&queue->pending_peers, peer_idx) {
-                       if (http_client_peer_addr_cmp(&(*peer_idx)->addr, addr) == 0) {
+                       if (*peer_idx == peer) {
                                /* don't drop any connections to the successfully
                                   connected peer, even if some of the connections
                                   are pending. they may be intended for urgent
@@ -497,6 +508,7 @@ http_client_queue_connection_success(struct http_client_queue *queue,
                         */
                        http_client_peer_unlink_queue(*peer_idx, queue);
                }
+
                array_clear(&queue->pending_peers);
                i_assert(queue->cur_peer != NULL);
        }
@@ -504,14 +516,14 @@ http_client_queue_connection_success(struct http_client_queue *queue,
 
 void
 http_client_queue_connection_failure(struct http_client_queue *queue,
-       const struct http_client_peer_addr *addr, const char *reason)
+       struct http_client_peer *peer, const char *reason)
 {
        const struct http_client_settings *set =
                &queue->client->set;
+       const struct http_client_peer_addr *addr = &peer->shared->addr;
        const char *https_name = http_client_peer_addr_get_https_name(addr);
        struct http_client_host *host = queue->host;
        unsigned int ips_count = http_client_host_get_ips_count(host);
-       struct http_client_peer *failed_peer;
        struct http_client_peer *const *peer_idx;
 
        http_client_queue_debug(queue,
@@ -523,55 +535,44 @@ http_client_queue_connection_failure(struct http_client_queue *queue,
                reason, array_count(&queue->pending_peers),
                array_count(&queue->requests));
 
-       if (queue->cur_peer != NULL) {
-               if (http_client_peer_is_connected(queue->cur_peer)) {
-                       /* The peer still has some working connections, which means that
-                                pending requests wait until they're picked up by those connections
-                                or the remaining connections fail as well. In the latter case,
-                                connecting to different peer can resolve the situation, but only
-                                if there is more than one IP. In any other case, the requests will
-                                eventually fail. In the future we could start connections to the next
-                                IP at this point already, but that is no small change. */
-                       i_assert(array_count(&queue->pending_peers) == 0);
-                       return;
-               }
+       if (array_count(&queue->pending_peers) == 0) {
+               i_assert(queue->cur_peer == peer);
+       } else {
+               bool found = FALSE;
 
-               failed_peer = queue->cur_peer;
-               http_client_peer_unlink_queue(queue->cur_peer, queue);
-               queue->cur_peer = NULL;
+               i_assert(queue->cur_peer == NULL);
 
-       } else {
                /* we're still doing the initial connections to this hport. if
                         we're also doing parallel connections with soft timeouts
                         (pending_peer_count>1), wait for them to finish
                         first. */
-               failed_peer = NULL;
                array_foreach(&queue->pending_peers, peer_idx) {
-                       if (http_client_peer_addr_cmp(&(*peer_idx)->addr, addr) == 0) {
-                               failed_peer = *peer_idx;
+                       if (*peer_idx == peer) {
                                array_delete(&queue->pending_peers,
                                        array_foreach_idx(&queue->pending_peers, peer_idx), 1);
+                               found = TRUE;
                                break;
                        }
                }
-               i_assert(failed_peer != NULL);
+               i_assert(found);
                if (array_count(&queue->pending_peers) > 0) {
                        http_client_queue_debug(queue,
                                "Waiting for remaining pending peers.");
-                       http_client_peer_unlink_queue(failed_peer, queue);
+                       http_client_peer_unlink_queue(peer, queue);
                        return;
                }
-       }
 
-       /* one of the connections failed. if we're not using soft timeouts,
-          we need to try to connect to the next IP. if we are using soft
-          timeouts, we've already tried all of the IPs by now. */
-       timeout_remove(&queue->to_connect);
+               /* one of the connections failed. if we're not using soft timeouts,
+                  we need to try to connect to the next IP. if we are using soft
+                  timeouts, we've already tried all of the IPs by now. */
+               timeout_remove(&queue->to_connect);
 
-       if (queue->addr.type == HTTP_CLIENT_PEER_ADDR_UNIX) {
-               http_client_queue_fail(queue,
-                       HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, reason);
-               return;
+               if (queue->addr.type == HTTP_CLIENT_PEER_ADDR_UNIX) {
+                       http_client_peer_unlink_queue(peer, queue);
+                       http_client_queue_fail(queue,
+                               HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, reason);
+                       return;
+               }
        }
 
        if (http_client_queue_is_last_connect_ip(queue)) {
@@ -580,8 +581,8 @@ http_client_queue_connection_failure(struct http_client_queue *queue,
                queue->ips_connect_idx = queue->ips_connect_start_idx =
                        (queue->ips_connect_idx + 1) % ips_count;
 
-               if (set->max_connect_attempts == 0 ||
-                       queue->connect_attempts >= set->max_connect_attempts) {
+               if (queue->cur_peer == NULL && (set->max_connect_attempts == 0 ||
+                       queue->connect_attempts >= set->max_connect_attempts)) {
                        http_client_queue_debug(queue,
                                "Failed to set up any connection; failing all queued requests");
                        if (queue->connect_attempts > 1) {
@@ -592,7 +593,7 @@ http_client_queue_connection_failure(struct http_client_queue *queue,
                                        total_msecs/1000, total_msecs%1000);
                        }
                        queue->connect_attempts = 0;
-                       http_client_peer_unlink_queue(failed_peer, queue);
+                       http_client_peer_unlink_queue(peer, queue);
                        http_client_queue_fail(queue,
                                HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, reason);
                        return;
@@ -601,8 +602,10 @@ http_client_queue_connection_failure(struct http_client_queue *queue,
                queue->ips_connect_idx = (queue->ips_connect_idx + 1) % ips_count;
        }
        
-       if (http_client_queue_connection_attempt(queue) != failed_peer)
-               http_client_peer_unlink_queue(failed_peer, queue);
+       if (http_client_queue_connection_attempt(queue) != peer) {
+               http_client_peer_unlink_queue(peer, queue);
+               queue->cur_peer = NULL;
+       }
        return;
 }
 
index 352b6ddfa5fcebe026fac636383fbe6691d105c8..4a1d0689b2194b353b3c4c468574ae50f4e1927d 100644 (file)
@@ -798,10 +798,12 @@ void http_client_request_submit(struct http_client_request *req)
        req->submit_time = ioloop_timeval;
 
        http_client_request_do_submit(req);
-       http_client_request_debug(req, "Submitted");
 
        req->submitted = TRUE;
        http_client_request_add(req);
+
+       http_client_request_debug(req, "Submitted (requests left=%d)",
+               req->client->requests_count);
 }
 
 void
index 81e1989fc46c76f394ef568f0565404c452e3c4e..9d8ee50b6fdbaf2cee8d4f330bcbfa64d745f41b 100644 (file)
@@ -133,8 +133,6 @@ http_client_init_shared(struct http_client_context *cctx,
                                p_strdup(pool, set->proxy_url->password);
                }
 
-               if (set->max_idle_time_msecs > 0)
-                       client->set.max_idle_time_msecs = set->max_idle_time_msecs;
                if (set->max_parallel_connections > 0)
                        client->set.max_parallel_connections = set->max_parallel_connections;
                if (set->max_pipelined_requests > 0)
@@ -159,7 +157,6 @@ http_client_init_shared(struct http_client_context *cctx,
                        client->set.no_ssl_tunnel || set->no_ssl_tunnel;
                if (set->max_redirects > 0)
                        client->set.max_redirects = set->max_redirects;
-               client->set.response_hdr_limits = set->response_hdr_limits;
                if (set->request_absolute_timeout_msecs > 0) {
                        client->set.request_absolute_timeout_msecs =
                                set->request_absolute_timeout_msecs;
@@ -172,16 +169,11 @@ http_client_init_shared(struct http_client_context *cctx,
                        client->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
                if (set->max_auto_retry_delay > 0)
                        client->set.max_auto_retry_delay = set->max_auto_retry_delay;
-               client->set.socket_send_buffer_size = set->socket_send_buffer_size;
-               client->set.socket_recv_buffer_size = set->socket_recv_buffer_size;
                client->set.debug = client->set.debug || set->debug;
        }
 
        i_array_init(&client->delayed_failing_requests, 1);
 
-       hash_table_create(&client->peers, default_pool, 0,
-               http_client_peer_addr_hash, http_client_peer_addr_cmp);
-
        return client;
 }
 
@@ -214,7 +206,6 @@ void http_client_deinit(struct http_client **_client)
                peer = client->peers_list;
                http_client_peer_close(&peer);
        }
-       hash_table_destroy(&client->peers);
 
        /* free hosts */
        while (client->hosts_list != NULL) {
@@ -233,11 +224,12 @@ void http_client_deinit(struct http_client **_client)
 
 void http_client_switch_ioloop(struct http_client *client)
 {
-       struct http_client_host *host;
        struct http_client_peer *peer;
+       struct http_client_host *host;
 
        /* move peers */
-       for (peer = client->peers_list; peer != NULL; peer = peer->next)
+       for (peer = client->peers_list; peer != NULL;
+               peer = peer->client_next)
                http_client_peer_switch_ioloop(peer);
 
        /* move hosts/queues */
@@ -435,6 +427,9 @@ http_client_context_create(const struct http_client_settings *set)
 
        hash_table_create(&cctx->hosts, default_pool, 0, str_hash, strcmp);
 
+       hash_table_create(&cctx->peers, default_pool, 0,
+               http_client_peer_addr_hash, http_client_peer_addr_cmp);
+
        return cctx;
 }
 
@@ -446,6 +441,7 @@ void http_client_context_ref(struct http_client_context *cctx)
 void http_client_context_unref(struct http_client_context **_cctx)
 {
        struct http_client_context *cctx = *_cctx;
+       struct http_client_peer_shared *peer;
        struct http_client_host_shared *hshared;
 
        *_cctx = NULL;
@@ -461,6 +457,14 @@ void http_client_context_unref(struct http_client_context **_cctx)
        }
        hash_table_destroy(&cctx->hosts);
 
+       /* close all idle connections */
+       while (cctx->peers_list != NULL) {
+               peer = cctx->peers_list;
+               http_client_peer_shared_close(&peer);
+               i_assert(peer == NULL);
+       }
+       hash_table_destroy(&cctx->peers);
+
        connection_list_deinit(&cctx->conn_list);
 
        pool_unref(&cctx->pool);