void http_client_connection_check_idle(struct http_client_connection *conn)
{
+ struct http_client_peer_pool *ppool = conn->ppool;
unsigned int timeout, count;
if (conn->connected &&
/* instant death for (urgent) connections above limit */
timeout = 0;
} else {
- unsigned int idle_count = http_client_peer_idle_connections(conn->peer);
+ 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);
+ 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);
conn->to_idle =
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);
}
}
+static void
+http_client_connection_stop_idle(struct http_client_connection *conn)
+{
+ struct http_client_connection *const *conn_idx;
+ ARRAY_TYPE(http_client_connection) *conn_arr;
+
+ if (conn->to_idle != NULL)
+ timeout_remove(&conn->to_idle);
+
+ conn_arr = &conn->ppool->idle_conns;
+ array_foreach(conn_arr, conn_idx) {
+ if (*conn_idx == conn) {
+ array_delete(conn_arr, array_foreach_idx(conn_arr, conn_idx), 1);
+ break;
+ }
+ }
+}
+
+void http_client_connection_use_idle(struct http_client_connection *conn,
+ struct http_client_peer *peer)
+{
+ http_client_connection_debug(conn, "Reusing idle connection");
+
+ i_assert(peer->ppool == conn->ppool);
+ http_client_connection_stop_idle(conn);
+}
+
static void
http_client_connection_request_timeout(struct http_client_connection *conn)
{
i_assert(req->state == HTTP_REQUEST_STATE_QUEUED);
- timeout_remove(&conn->to_idle);
+ http_client_connection_stop_idle(conn);
req->payload_sync_continue = FALSE;
if (conn->peer->no_payload_sync)
http_client_connection_create(struct http_client_peer *peer)
{
struct http_client_context *cctx = peer->client->cctx;
+ struct http_client_peer_pool *ppool = peer->ppool;
struct http_client_connection *conn;
static unsigned int id = 0;
const struct http_client_peer_addr *addr = &peer->addr;
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)
i_array_init(&conn->request_wait_list, 16);
}
array_append(&peer->conns, &conn, 1);
+ array_append(&ppool->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",
- conn_type, array_count(&peer->conns),
+ conn_type, array_count(&ppool->conns),
(conn->to_input == NULL ? "" : " [broken]"));
return conn;
}
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;
timeout_remove(&conn->to_response);
/* remove this connection from the list */
+ conn_arr = &ppool->conns;
+ array_foreach(conn_arr, conn_idx) {
+ if (*conn_idx == conn) {
+ array_delete(conn_arr, array_foreach_idx(conn_arr, conn_idx), 1);
+ break;
+ }
+ }
conn_arr = &peer->conns;
array_foreach(conn_arr, conn_idx) {
if (*conn_idx == conn) {
if (conn->connect_succeeded)
http_client_peer_connection_lost(peer, conn->lost_prematurely);
+
+ http_client_connection_stop_idle(conn); // FIXME: needed?
}
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);
i_free(conn->label);
i_free(conn);
+
+ http_client_peer_pool_unref(&ppool);
+ http_client_peer_unref(&peer);
return FALSE;
}
* Logging
*/
+/* Peer pool */
+
+static inline void
+http_client_peer_pool_debug(struct http_client_peer_pool *ppool,
+ const char *format, ...) ATTR_FORMAT(2, 3);
+
+static inline void
+http_client_peer_pool_debug(struct http_client_peer_pool *ppool,
+ const char *format, ...)
+{
+ struct http_client_peer *peer = ppool->peer;
+ va_list args;
+
+ if (peer->client->cctx->set.debug) {
+ va_start(args, format);
+ i_debug("http-client: peer %s: %s",
+ http_client_peer_label(peer), t_strdup_vprintf(format, args));
+ va_end(args);
+ }
+}
+
+/* Peer */
+
static inline void
http_client_peer_debug(struct http_client_peer *peer,
const char *format, ...) ATTR_FORMAT(2, 3);
i_unreached();
}
+/*
+ * Peer pool
+ */
+
+static struct http_client_peer_pool *
+http_client_peer_pool_create(struct http_client_peer *peer)
+{
+ struct http_client_peer_pool *ppool;
+
+ ppool = i_new(struct http_client_peer_pool, 1);
+ ppool->refcount = 1;
+ ppool->peer = peer;
+
+ i_array_init(&ppool->conns, 16);
+ i_array_init(&ppool->idle_conns, 16);
+
+ http_client_peer_pool_debug(ppool, "Peer pool created");
+
+ return ppool;
+}
+
+void http_client_peer_pool_ref(struct http_client_peer_pool *ppool)
+{
+ if (ppool->destroyed)
+ return;
+ ppool->refcount++;
+}
+
+void http_client_peer_pool_close(struct http_client_peer_pool **_ppool)
+{
+ struct http_client_peer_pool *ppool = *_ppool;
+ struct http_client_connection **conn;
+ ARRAY_TYPE(http_client_connection) conns;
+
+ http_client_peer_pool_ref(ppool);
+
+ /* make a copy of the connection array; freed connections modify it */
+ t_array_init(&conns, array_count(&ppool->conns));
+ array_copy(&conns.arr, 0, &ppool->conns.arr, 0, array_count(&ppool->conns));
+ array_foreach_modifiable(&conns, conn)
+ http_client_connection_unref(conn);
+ i_assert(array_count(&ppool->idle_conns) == 0);
+ i_assert(array_count(&ppool->conns) == 0);
+
+ http_client_peer_pool_unref(_ppool);
+}
+
+void http_client_peer_pool_unref(struct http_client_peer_pool **_ppool)
+{
+ struct http_client_peer_pool *ppool = *_ppool;
+
+ *_ppool = NULL;
+
+ if (ppool->destroyed)
+ return;
+
+ i_assert(ppool->refcount > 0);
+ if (--ppool->refcount > 0)
+ return;
+
+ http_client_peer_pool_debug(ppool, "Peer pool destroy");
+ ppool->destroyed = TRUE;
+
+ i_assert(array_count(&ppool->idle_conns) == 0);
+ i_assert(array_count(&ppool->conns) == 0);
+ array_free(&ppool->idle_conns);
+ array_free(&ppool->conns);
+
+ i_free(ppool);
+}
+
/*
* Peer
*/
http_client_peer_do_connect(struct http_client_peer *peer,
unsigned int count)
{
- unsigned int i;
+ struct http_client_peer_pool *ppool = peer->ppool;
+ struct http_client_connection *const *idle_conns;
+ unsigned int i, idle_count;
+
+ if (count == 0)
+ return;
+
+ 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);
- for (i = 0; i < count; i++) {
+ http_client_peer_debug(peer,
+ "Using idle connection (connections=%u)",
+ array_count(&peer->conns));
+ }
+
+ for (; i < count; i++) {
http_client_peer_debug(peer,
"Making new connection %u of %u", i+1, count);
(void)http_client_connection_create(peer);
{
struct http_client_connection *const *conn_idx;
+ if (array_count(&peer->ppool->idle_conns) > 0)
+ return TRUE;
+
array_foreach(&peer->conns, conn_idx) {
if ((*conn_idx)->connected)
return TRUE;
(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);
+
http_client_peer_debug(peer, "Peer created");
return peer;
}
http_client_peer_debug(peer, "Peer destroy");
http_client_peer_disconnect(peer);
+ http_client_peer_pool_unref(&peer->ppool);
i_assert(array_count(&peer->queues) == 0);
i_free(peer->addr_name);
i_free(peer->label);
i_free(peer);
+
return FALSE;
}
struct http_client_connection {
struct connection conn;
+ unsigned int refcount;
+
+ struct http_client_peer_pool *ppool;
struct http_client_peer *peer;
struct http_client *client;
- unsigned int refcount;
char *label;
unsigned int id; // DEBUG: identify parallel connections
bool in_req_callback:1; /* performing request callback (busy) */
};
+struct http_client_peer_pool {
+ unsigned int refcount;
+ struct http_client_peer *peer;
+ struct http_client_peer_pool *prev, *next;
+
+ /* all connections to this peer */
+ ARRAY_TYPE(http_client_connection) conns;
+
+ /* available connections to this peer */
+ ARRAY_TYPE(http_client_connection) idle_conns;
+
+ bool destroyed:1; /* peer pool is being destroyed */
+};
+
struct http_client_peer {
unsigned int refcount;
struct http_client_peer_addr addr;
struct http_client *client;
struct http_client_peer *prev, *next;
+ struct http_client_peer_pool *ppool;
+
/* queues using this peer */
ARRAY_TYPE(http_client_queue) queues;
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,
+ struct http_client_peer *peer);
/*
* Peer
*/
+/* address */
+
unsigned int http_client_peer_addr_hash
(const struct http_client_peer_addr *peer) ATTR_PURE;
int http_client_peer_addr_cmp
(const struct http_client_peer_addr *peer1,
const struct http_client_peer_addr *peer2) ATTR_PURE;
+/* connection pool */
+
+void http_client_peer_pool_ref(struct http_client_peer_pool *ppool);
+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 */
+
const char *
http_client_peer_label(struct http_client_peer *peer);