← Commit list

082503f1f6

Stefan Eissing
Stefan Eissing
2026-09-23
SUCCESS Duration: 421s

Subject

connection pool: use tbl and bsets

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

lib/conncache.c+315 -342
@@ -34,6 +34,7 @@
#include "conncache.h"
#include "curl_share.h"
#include "sigpipe.h"
+#include "uint-spbset.h"
#define CPOOL_IS_LOCKED(c) ((c) && (c)->locked)
@@ -59,113 +60,127 @@
} \
} while(0)
-/* A list of connections to the same destination. */
+/* A set of connection pool ids to the same destination. */
struct cpool_bundle {
- struct Curl_llist conns; /* connections in the bundle */
+ struct uint32_spbset ids; /* bitset of connection `pool_id`s */
+ uint32_t refcount;
};
-static struct cpool_bundle *cpool_bundle_create(void)
+static void cpool_bundle_unlink(struct cpool_bundle **pbundle)
{
- struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle));
- if(bundle)
- Curl_llist_init(&bundle->conns, NULL);
- return bundle;
-}
-
-static void cpool_bundle_destroy(struct cpool_bundle *bundle)
-{
- DEBUGASSERT(!Curl_llist_count(&bundle->conns));
- curlx_free(bundle);
+ if(*pbundle) {
+ struct cpool_bundle *bundle = *pbundle;
+
+ DEBUGASSERT(bundle->refcount);
+ *pbundle = NULL;
+ if(bundle->refcount)
+ bundle->refcount--;
+ if(!bundle->refcount) {
+ Curl_uint32_spbset_destroy(&bundle->ids);
+ curlx_free(bundle);
+ }
+ }
}
-/* Add a connection to a bundle */
-static void cpool_bundle_add(struct cpool_bundle *bundle,
- struct connectdata *conn)
+static void cpool_bundle_link(struct cpool_bundle **pdest,
+ struct cpool_bundle *src)
{
- DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
- Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
- conn->bits.in_cpool = TRUE;
+ if(*pdest != src) {
+ cpool_bundle_unlink(pdest);
+ *pdest = src;
+ if(src) {
+ DEBUGASSERT(src->refcount < UINT32_MAX);
+ src->refcount++;
+ }
+ }
}
-/* Remove a connection from a bundle */
-static void cpool_bundle_remove(struct cpool_bundle *bundle,
- struct connectdata *conn)
+static struct cpool_bundle *cpool_create_bundle(void)
{
- (void)bundle;
- DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
- Curl_node_remove(&conn->cpool_node);
- conn->bits.in_cpool = FALSE;
-}
+ /* created with refcount == 1 */
+ struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle));
+ if(!bundle)
+ return NULL;
-static void cpool_bundle_free_entry(void *freethis)
-{
- cpool_bundle_destroy((struct cpool_bundle *)freethis);
+ Curl_uint32_spbset_init(&bundle->ids);
+ bundle->refcount = 1;
+ return bundle;
}
-void Curl_cpool_init(struct cpool *cpool,
- struct Curl_share *share,
- size_t size)
+/* Add a connection to a bundle */
+static bool cpool_bundle_add_id(struct cpool *cpool,
+ struct cpool_bundle *bundle,
+ const char *destination,
+ uint32_t pool_id)
{
- Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES,
- cpool_bundle_free_entry);
-
- cpool->share = share;
- cpool->initialized = TRUE;
+ if(!Curl_uint32_spbset_add(&bundle->ids, pool_id))
+ return FALSE;
+ if(Curl_uint32_spbset_count(&bundle->ids) == 1) {
+ /* first one added, register bundle */
+ struct cpool_bundle *new_ref = NULL;
+ cpool_bundle_link(&new_ref, bundle);
+ if(!Curl_hash_add(&cpool->dest2bundle,
+ destination, strlen(destination), new_ref)) {
+ cpool_bundle_unlink(&new_ref);
+ return FALSE;
+ }
+ }
+ return TRUE;
}
-/* Return the "first" connection in the pool or NULL. */
-static struct connectdata *cpool_get_first(struct cpool *cpool)
+/* Remove a connection from a bundle */
+static void cpool_bundle_remove_id(struct cpool *cpool,
+ struct cpool_bundle *bundle,
+ const char *destination,
+ uint32_t pool_id)
{
- struct Curl_hash_iterator iter;
- struct Curl_hash_element *he;
- struct cpool_bundle *bundle;
- struct Curl_llist_node *conn_node;
-
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
- for(he = Curl_hash_next_element(&iter); he;
- he = Curl_hash_next_element(&iter)) {
- bundle = he->ptr;
- conn_node = Curl_llist_head(&bundle->conns);
- if(conn_node)
- return Curl_node_elem(conn_node);
+ Curl_uint32_spbset_remove(&bundle->ids, pool_id);
+ if(!Curl_uint32_spbset_count(&bundle->ids)) {
+ /* Now empty, unregister bundle */
+ Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination));
}
- return NULL;
}
-static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
- const char *destination)
+static void cpool_bundle_hash_dtor(void *entry)
{
- return Curl_hash_pick(
- &cpool->dest2bundle, destination, strlen(destination) + 1);
+ struct cpool_bundle *bundle = entry;
+ cpool_bundle_unlink(&bundle);
}
-static void cpool_remove_bundle(struct cpool *cpool,
- const char *destination)
+static struct cpool_bundle *cpool_get_bundle(struct cpool *cpool,
+ const char *destination)
{
- if(!cpool)
- return;
- Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination) + 1);
+ struct cpool_bundle *bundle = NULL;
+ cpool_bundle_link(&bundle, Curl_hash_pick(
+ &cpool->dest2bundle, destination, strlen(destination)));
+ return bundle;
}
static void cpool_remove_conn(struct cpool *cpool,
struct connectdata *conn)
{
- struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
+ uint32_t cpid = conn->cpid;
+
DEBUGASSERT(cpool);
- if(list) {
- /* The connection is certainly in the pool, but where? */
- struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn->destination);
- if(bundle && (list == &bundle->conns)) {
- cpool_bundle_remove(bundle, conn);
- if(!Curl_llist_count(&bundle->conns))
- cpool_remove_bundle(cpool, conn->destination);
- conn->bits.in_cpool = FALSE;
- cpool->num_conn--;
- }
- else {
- /* Should have been in the bundle list */
- DEBUGASSERT(NULL);
+ if(cpid != UINT32_MAX) {
+ struct cpool_bundle *bundle;
+
+#ifdef DEBUGBUILD
+ {
+ struct connectdata *pooled_conn =
+ Curl_uint32_tbl_get(&cpool->conns, cpid);
+ DEBUGASSERT(conn == pooled_conn);
}
+#endif
+ Curl_uint32_tbl_remove(&cpool->conns, cpid);
+ Curl_uint32_bset_remove(&cpool->idles, cpid);
+ conn->cpid = UINT32_MAX;
+
+ bundle = cpool_get_bundle(cpool, conn->destination);
+ DEBUGASSERT(bundle);
+ if(bundle)
+ cpool_bundle_remove_id(cpool, bundle, conn->destination, cpid);
+ cpool_bundle_unlink(&bundle);
}
}
@@ -180,7 +195,7 @@ static void cpool_discard_conn(struct cpool *cpool,
DEBUGASSERT(data);
DEBUGASSERT(!data->conn);
DEBUGASSERT(cpool);
- DEBUGASSERT(!conn->bits.in_cpool);
+ DEBUGASSERT(conn->cpid == UINT32_MAX);
admin = Curl_get_admin(data);
/*
@@ -217,38 +232,59 @@ static void cpool_discard_conn(struct cpool *cpool,
else {
struct Curl_multi *multi = data->multi;
size_t max_shutdowns = multi->max_total_connections;
- if(cpool->num_conn < max_shutdowns)
- max_shutdowns -= cpool->num_conn;
+ uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
+
+ if(num_conns < max_shutdowns)
+ max_shutdowns -= num_conns;
else if(max_shutdowns)
max_shutdowns = 1;
else /* no connection limit set, let's restrict growth nevertheless */
- max_shutdowns = CURLMAX(cpool->num_conn / 4, 128);
+ max_shutdowns = CURLMAX(num_conns / 4, 128);
Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns);
}
}
+void Curl_cpool_init(struct cpool *cpool,
+ struct Curl_share *share,
+ size_t size)
+{
+ Curl_uint32_tbl_init(&cpool->conns);
+ Curl_uint32_bset_init(&cpool->idles);
+ Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES,
+ cpool_bundle_hash_dtor);
+ cpool->share = share;
+ cpool->initialized = TRUE;
+ cpool->in_shutdown = FALSE;
+ cpool->locked = FALSE;
+}
+
void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin)
{
if(cpool && cpool->initialized && admin) {
- struct connectdata *conn;
- struct Curl_sigpipe_ctx pipe_ctx;
+ void *entry;
+ uint32_t cpid;
- CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections",
- cpool->share ? "[SHARE] " : "", cpool->num_conn);
+ CURL_TRC_M(admin, "%s[CPOOL] destroy, %u connections",
+ cpool->share ? "[SHARE] " : "",
+ Curl_uint32_tbl_count(&cpool->conns));
/* Move all connections to the shutdown list */
- sigpipe_init(&pipe_ctx);
CPOOL_LOCK(cpool, admin);
- conn = cpool_get_first(cpool);
- if(conn)
+ if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) {
+ struct Curl_sigpipe_ctx pipe_ctx;
+
+ sigpipe_init(&pipe_ctx);
sigpipe_apply(admin, &pipe_ctx);
- while(conn) {
- cpool_remove_conn(cpool, conn);
- cpool_discard_conn(cpool, admin, conn, FALSE);
- conn = cpool_get_first(cpool);
+ do {
+ struct connectdata *conn = entry;
+ cpool_remove_conn(cpool, conn);
+ cpool_discard_conn(cpool, admin, conn, FALSE);
+ } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry));
+ sigpipe_restore(&pipe_ctx);
}
CPOOL_UNLOCK(cpool, admin);
- sigpipe_restore(&pipe_ctx);
Curl_hash_destroy(&cpool->dest2bundle);
+ Curl_uint32_bset_destroy(&cpool->idles);
+ Curl_uint32_tbl_destroy(&cpool->conns);
}
}
@@ -280,7 +316,8 @@ void Curl_cpool_xfer_init(struct Curl_easy *data)
data->id = cpool->next_easy_id++;
if(cpool->next_easy_id == CURL_OFF_T_MAX)
cpool->next_easy_id = 0;
- data->state.lastconnect_id = -1;
+ data->state.last_conn_id = -1;
+ data->state.last_cpid = UINT32_MAX;
CPOOL_UNLOCK(cpool, data);
}
@@ -288,87 +325,74 @@ void Curl_cpool_xfer_init(struct Curl_easy *data)
/* We should not get here, but in a non-debug build, do something */
DEBUGASSERT(0);
data->id = 0;
- data->state.lastconnect_id = -1;
+ data->state.last_conn_id = -1;
+ data->state.last_cpid = UINT32_MAX;
}
}
-static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool,
- const char *destination)
+static bool cpool_conn_can_be_closed(struct connectdata *conn)
{
- struct cpool_bundle *bundle;
-
- bundle = cpool_bundle_create();
- if(!bundle)
- return NULL;
-
- if(!Curl_hash_add(&cpool->dest2bundle,
- destination, strlen(destination) + 1, bundle)) {
- cpool_bundle_destroy(bundle);
- return NULL;
- }
- return bundle;
+ /* CONNECT_ONLY sockets remain in use by the application. */
+ return (conn && !CONN_INUSE(conn) && !conn->bits.connect_only);
}
static struct connectdata *cpool_bundle_get_oldest_idle(
+ struct cpool *cpool,
struct cpool_bundle *bundle,
const struct curltime *pnow)
{
- struct Curl_llist_node *curr;
- struct connectdata *oldest_idle = NULL;
+ uint32_t cpid, oldest_id = UINT32_MAX;
timediff_t unused_ms;
timediff_t oldest_ms = -1;
- struct connectdata *conn;
-
- curr = Curl_llist_head(&bundle->conns);
- while(curr) {
- conn = Curl_node_elem(curr);
-
- /* CONNECT_ONLY sockets remain in use by the application. */
- if(!CONN_INUSE(conn) && !conn->bits.close && !conn->bits.connect_only) {
- /* Set higher score for the age passed since the connection was used */
- unused_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
- if(unused_ms > oldest_ms) {
- oldest_ms = unused_ms;
- oldest_idle = conn;
+
+ if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) {
+ do {
+ struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid);
+ if(cpool_conn_can_be_closed(conn)) {
+ /* Set higher score for the age passed since the connection was used */
+ unused_ms =
+ curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
+ if(unused_ms > oldest_ms) {
+ oldest_ms = unused_ms;
+ oldest_id = cpid;
+ }
}
- }
- curr = Curl_node_next(curr);
+ else if(!conn) {
+ DEBUGASSERT(0);
+ Curl_uint32_spbset_remove(&bundle->ids, cpid);
+ }
+ } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid));
}
- return oldest_idle;
+ return Curl_uint32_tbl_get(&cpool->conns, oldest_id);
}
static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool,
const struct curltime *pnow,
timediff_t min_age_ms)
{
- struct Curl_hash_iterator iter;
- struct Curl_llist_node *curr;
- struct Curl_hash_element *he;
- struct cpool_bundle *bundle;
- struct connectdata *oldest_idle = NULL;
+ uint32_t cpid, oldest_id = UINT32_MAX;
timediff_t oldest_idle_ms = -1;
timediff_t idle_ms;
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
-
- for(he = Curl_hash_next_element(&iter); he;
- he = Curl_hash_next_element(&iter)) {
- struct connectdata *conn;
- bundle = he->ptr;
-
- for(curr = Curl_llist_head(&bundle->conns); curr;
- curr = Curl_node_next(curr)) {
- conn = Curl_node_elem(curr);
- if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only)
- continue;
- idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
- if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) {
- oldest_idle_ms = idle_ms;
- oldest_idle = conn;
+ /* Iterate over all connections that once were idle. They may
+ * no longer be, in which case we remove them from the set again. */
+ if(Curl_uint32_bset_first(&cpool->idles, &cpid)) {
+ do {
+ struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid);
+ if(cpool_conn_can_be_closed(conn)) {
+ idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
+ if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) {
+ oldest_idle_ms = idle_ms;
+ oldest_id = cpid;
+ }
}
- }
+ else {
+ DEBUGASSERT(conn);
+ Curl_uint32_bset_remove(&cpool->idles, cpid);
+ }
+ } while(Curl_uint32_bset_next(&cpool->idles, cpid, &cpid));
}
- return oldest_idle;
+ return Curl_uint32_tbl_get(&cpool->conns, oldest_id);
}
static void cpool_conn_close(struct cpool *cpool,
@@ -399,9 +423,9 @@ static void cpool_conn_close(struct cpool *cpool,
if(do_lock)
CPOOL_LOCK(cpool, admin);
- if(conn->bits.in_cpool) {
+ if(conn->cpid != UINT32_MAX) {
cpool_remove_conn(cpool, conn);
- DEBUGASSERT(!conn->bits.in_cpool);
+ DEBUGASSERT(conn->cpid == UINT32_MAX);
}
/* treat the connection as aborted in CONNECT_ONLY situations,
@@ -457,7 +481,7 @@ static int cpool_check_limits(struct Curl_easy *data,
uint32_t max_total,
struct connectdata *to_add,
uint32_t max_host,
- struct cpool_bundle **pbundle,
+ struct cpool_bundle *bundle,
const struct curltime *pnow)
{
struct cpool *cpool = cpool_get_instance(data);
@@ -476,13 +500,13 @@ static int cpool_check_limits(struct Curl_easy *data,
* in shutdown and, if that does not lower it, evict idle connections
* from the pool. */
admin = Curl_get_admin(data);
- if(*pbundle) {
- live = Curl_llist_count(&(*pbundle)->conns);
+ if(bundle) {
+ live = Curl_uint32_spbset_count(&bundle->ids);
if(live >= max_host) {
size_t over = live - max_host + 1;
- for(; over && *pbundle; --over) {
+ for(; over; --over) {
struct connectdata *oldest_idle =
- cpool_bundle_get_oldest_idle(*pbundle, pnow);
+ cpool_bundle_get_oldest_idle(cpool, bundle, pnow);
if(!oldest_idle)
break;
/* disconnect the old conn and continue */
@@ -491,10 +515,8 @@ static int cpool_check_limits(struct Curl_easy *data,
oldest_idle->connection_id, oldest_idle->destination,
max_host);
cpool_evict_conn(cpool, admin, oldest_idle);
- /* bundle may get destroyed in disconnect, look it up again */
- *pbundle = cpool_find_bundle(cpool, to_add->destination);
}
- live = *pbundle ? Curl_llist_count(&(*pbundle)->conns) : 0;
+ live = Curl_uint32_spbset_count(&bundle->ids);
}
}
if(cshutdn) {
@@ -513,11 +535,12 @@ static int cpool_check_limits(struct Curl_easy *data,
/* Internal transfers are outside the total limit */
if(max_total && !data->state.internal) {
size_t shutdowns = 0;
+ uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
if(!admin)
admin = Curl_get_admin(data);
- if(cpool->num_conn >= max_total) {
- size_t over = cpool->num_conn - max_total + 1;
+ if(num_conns >= max_total) {
+ size_t over = num_conns - max_total + 1;
for(; over; --over) {
struct connectdata *oldest_idle =
cpool_get_oldest_idle(cpool, pnow, 0);
@@ -530,18 +553,17 @@ static int cpool_check_limits(struct Curl_easy *data,
max_total);
cpool_evict_conn(cpool, admin, oldest_idle);
}
- /* bundle might be gone due to evictions */
- if(*pbundle)
- *pbundle = cpool_find_bundle(cpool, to_add->destination);
}
+
+ num_conns = Curl_uint32_tbl_count(&cpool->conns);
if(cshutdn) {
shutdowns = Curl_cshutdn_count(cshutdn);
- if(shutdowns && (cpool->num_conn + shutdowns) >= max_total) {
- size_t over = cpool->num_conn + shutdowns - max_total + 1;
+ if(shutdowns && (num_conns + shutdowns) >= max_total) {
+ size_t over = num_conns + shutdowns - max_total + 1;
shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, NULL, over);
}
}
- if((cpool->num_conn + shutdowns) >= max_total)
+ if((num_conns + shutdowns) >= max_total)
return CPOOL_LIMIT_TOTAL;
}
@@ -557,22 +579,23 @@ CURLcode Curl_cpool_add(struct Curl_easy *data,
CURLcode result = CURLE_OK;
struct cpool_bundle *bundle = NULL;
struct cpool *cpool = cpool_get_instance(data);
+ uint32_t capacity, cpid;
DEBUGASSERT(conn);
DEBUGASSERT(cpool);
if(!cpool)
return CURLE_FAILED_INIT;
+ DEBUGASSERT(conn->cpid == UINT32_MAX);
conn->created = *pnow;
conn->shutdown.start_ms[FIRSTSOCKET] =
conn->shutdown.start_ms[SECONDARYSOCKET] = -1;
CPOOL_LOCK(cpool, data);
-
/* Find the bundle, should it exist, and check the limits */
- bundle = cpool_find_bundle(cpool, conn->destination);
+ bundle = cpool_get_bundle(cpool, conn->destination);
- switch(cpool_check_limits(data, max_total, conn, max_host, &bundle, pnow)) {
+ switch(cpool_check_limits(data, max_total, conn, max_host, bundle, pnow)) {
case CPOOL_LIMIT_DEST:
infof(data, "No more connections allowed to host");
result = CURLE_NO_CONNECTION_AVAILABLE;
@@ -586,21 +609,58 @@ CURLcode Curl_cpool_add(struct Curl_easy *data,
break;
}
+ /* We add the connection */
+ capacity = Curl_uint32_tbl_capacity(&cpool->conns);
+ if(capacity == UINT32_MAX) {
+ infof(data, "No connections available, pool at max capacity");
+ result = CURLE_NO_CONNECTION_AVAILABLE;
+ goto out;
+ }
+ if(Curl_uint32_tbl_count(&cpool->conns) >= capacity) {
+ uint32_t growth, ncapacity;
+
+ growth = CURLMAX(capacity, 16);
+ if((UINT32_MAX - growth) >= capacity)
+ ncapacity = capacity + growth;
+ else
+ ncapacity = UINT32_MAX;
+ /* Grow bitset first, so table resize failure does not leave a too
+ * short bitset should that one fail. */
+ if(Curl_uint32_bset_resize(&cpool->idles, ncapacity) ||
+ Curl_uint32_tbl_resize(&cpool->conns, ncapacity)) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ }
+
if(!bundle) {
- bundle = cpool_add_bundle(cpool, conn->destination);
+ bundle = cpool_create_bundle();
if(!bundle) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
- cpool_bundle_add(bundle, conn);
+ if(!Curl_uint32_tbl_add(&cpool->conns, conn, &cpid)) {
+ DEBUGASSERT(0);
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ conn->cpid = cpid;
+
+ if(!cpool_bundle_add_id(cpool, bundle, conn->destination, cpid)) {
+ Curl_uint32_tbl_remove(&cpool->conns, cpid);
+ conn->cpid = UINT32_MAX;
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
conn->connection_id = cpool->next_connection_id++;
- cpool->num_conn++;
CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
- "The cache now contains %zu members",
- conn->connection_id, cpool->num_conn);
+ "The cache now contains %u members",
+ conn->connection_id, Curl_uint32_tbl_count(&cpool->conns));
out:
+ cpool_bundle_unlink(&bundle);
CPOOL_UNLOCK(cpool, data);
return result;
@@ -625,31 +685,18 @@ static bool cpool_foreach(struct Curl_easy *data,
struct Curl_easy *data,
struct connectdata *conn, void *param))
{
- struct Curl_hash_iterator iter;
- struct Curl_hash_element *he;
+ void *entry;
+ uint32_t cpid;
if(!cpool)
return FALSE;
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
-
- he = Curl_hash_next_element(&iter);
- while(he) {
- struct Curl_llist_node *curr;
- struct cpool_bundle *bundle = he->ptr;
- he = Curl_hash_next_element(&iter);
-
- curr = Curl_llist_head(&bundle->conns);
- while(curr) {
- /* Yes, we need to update curr before calling func(), because func()
- might decide to remove the connection */
- struct connectdata *conn = Curl_node_elem(curr);
- curr = Curl_node_next(curr);
-
- if(func(cpool, data, conn, param) == 1) {
+ if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) {
+ do {
+ struct connectdata *conn = entry;
+ if(conn && func(cpool, data, conn, param) == 1)
return TRUE;
- }
- }
+ } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry));
}
return FALSE;
}
@@ -667,17 +714,19 @@ static bool cpool_conn_now_idle(struct cpool *cpool,
{
struct connectdata *oldest_idle = NULL;
struct Curl_easy *admin;
- unsigned int maxconnects;
+ uint32_t maxconnects;
bool kept = TRUE;
timediff_t min_age_ms = 0;
if(!data || !data->multi)
return kept;
+ Curl_uint32_bset_add(&cpool->idles, conn->cpid);
+
if(!data->multi->maxconnects) {
/* Attached transfers is a weak indicator of business. */
uint32_t attached = Curl_multi_xfers_attached(data->multi);
- maxconnects = (attached <= UINT_MAX / 2) ? attached * 2 : UINT_MAX;
+ maxconnects = (attached <= UINT32_MAX / 2) ? attached * 2 : UINT32_MAX;
/* We are guessing. So, only evict a "seemingly superfluous" connection
* when has not been used for this long, */
min_age_ms = 1000;
@@ -686,12 +735,12 @@ static bool cpool_conn_now_idle(struct cpool *cpool,
maxconnects = data->multi->maxconnects;
}
- if(cpool && maxconnects) {
+ if(cpool && maxconnects && (maxconnects < UINT32_MAX)) {
+ uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
admin = Curl_get_admin(data);
- if(cpool->num_conn > maxconnects) {
- infof(data, "Connection pool is full, closing the oldest of %zu/%u",
- cpool->num_conn, maxconnects);
-
+ if(num_conns > maxconnects) {
+ infof(data, "Connection pool is full, closing the oldest of %u/%u",
+ num_conns, maxconnects);
oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms);
kept = (oldest_idle != conn);
if(oldest_idle) {
@@ -709,7 +758,6 @@ static void cpool_prune_dead(struct cpool *cpool,
static bool cpool_find_act(struct cpool *cpool,
struct Curl_easy *data,
- struct cpool_bundle *bundle,
struct connectdata *conn,
cpool_match_result match)
{
@@ -717,16 +765,10 @@ static bool cpool_find_act(struct cpool *cpool,
case CPOOL_MATCH_FOUND:
return TRUE;
case CPOOL_MATCH_TOO_OLD:
- if(CONN_INUSE(conn))
+ if(!cpool_conn_can_be_closed(conn))
return FALSE;
FALLTHROUGH();
case CPOOL_MATCH_CLOSE:
- DEBUGASSERT(conn->bits.in_cpool);
- if(conn->bits.in_cpool) {
- cpool_bundle_remove(bundle, conn);
- conn->bits.in_cpool = FALSE;
- cpool->num_conn--;
- }
cpool_conn_close(cpool, data, conn, FALSE);
break;
default:
@@ -744,9 +786,7 @@ bool Curl_cpool_find(struct Curl_easy *data,
void *userdata)
{
struct cpool *cpool = cpool_get_instance(data);
- struct cpool_bundle *bundle;
- struct Curl_llist_node *curr;
- struct connectdata *conn;
+ struct cpool_bundle *bundle = NULL;
bool found = FALSE;
DEBUGASSERT(cpool);
@@ -759,46 +799,49 @@ bool Curl_cpool_find(struct Curl_easy *data,
if(prune_dead)
cpool_prune_dead(cpool, data, pnow);
- bundle = Curl_hash_pick(&cpool->dest2bundle,
- destination, strlen(destination) + 1);
- if(bundle) {
- if(data->state.lastconnect_id >= 0) {
- /* Try to find the previously used connection in this bundle
- * and if it still matches, use that one. This assures that
- * an authentication involving several requests is using
- * the same connection again. */
- curr = Curl_llist_head(&bundle->conns);
- while(!found && curr) {
- conn = Curl_node_elem(curr);
- curr = Curl_node_next(curr);
- if(data->state.lastconnect_id != conn->connection_id)
- continue;
- found = cpool_find_act(cpool, data, bundle, conn,
- conn_cb(conn, userdata));
- break;
- }
+ if(data->state.last_cpid != UINT32_MAX) {
+ struct connectdata *conn =
+ Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid);
+ if(conn && (data->state.last_conn_id == conn->connection_id)) {
+ /* Indeed the last connection used by `data` still exists.
+ * Check that first, so that connection use gets "sticky". */
+ found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata));
+ if(found)
+ goto out;
}
-
- if(!found) {
- curr = Curl_llist_head(&bundle->conns);
- while(!found && curr) {
- conn = Curl_node_elem(curr);
- curr = Curl_node_next(curr);
- /* Already tried a matching connection above. */
- if(data->state.lastconnect_id == conn->connection_id)
- continue;
- found = cpool_find_act(cpool, data, bundle, conn,
- conn_cb(conn, userdata));
- }
+ else {
+ /* No longer found here. */
+ data->state.last_conn_id = -1;
+ data->state.last_cpid = UINT32_MAX;
}
+ }
- if(!Curl_llist_count(&bundle->conns))
- cpool_remove_bundle(cpool, destination);
+ bundle = cpool_get_bundle(cpool, destination);
+ if(bundle) {
+ uint32_t cpid;
+ if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) {
+ do {
+ struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid);
+ if(conn) {
+ if(data->state.last_conn_id != conn->connection_id) {
+ found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata));
+ if(found)
+ goto out;
+ }
+ }
+ else {
+ DEBUGASSERT(0);
+ Curl_uint32_spbset_remove(&bundle->ids, cpid);
+ }
+ } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid));
+ }
}
+out:
if(done_cb) {
found = done_cb(userdata);
}
+ cpool_bundle_unlink(&bundle);
CPOOL_UNLOCK(cpool, data);
return found;
}
@@ -814,13 +857,12 @@ static int cpool_reap_dead_cb(struct cpool *cpool,
{
struct cpool_reaper_ctx *reaper = param;
- if(!CONN_INUSE(conn)) {
+ if(cpool_conn_can_be_closed(conn)) {
if(conn->bits.no_reuse || conn->bits.close ||
!Curl_cpool_conn_seems_healthy(conn, admin, reaper->pnow)) {
/* terminate conn and stop the iteration */
reaper->reaped++;
cpool_conn_close(cpool, admin, conn, FALSE);
- return 1;
}
}
return 0; /* continue iteration */
@@ -843,8 +885,7 @@ static void cpool_prune_dead(struct cpool *cpool,
memset(&reaper, 0, sizeof(reaper));
reaper.pnow = pnow;
- while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb))
- ;
+ cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb);
cpool->last_cleanup = *pnow;
}
}
@@ -867,9 +908,8 @@ static int conn_upkeep(struct cpool *cpool,
result = Curl_conn_keep_alive(admin, conn);
Curl_detach_connection(admin);
- if(result && !CONN_INUSE(conn)) {
+ if(result && cpool_conn_can_be_closed(conn)) {
cpool_conn_close(cpool, admin, conn, FALSE);
- return 1;
}
}
return 0; /* continue iteration */
@@ -884,45 +924,27 @@ CURLcode Curl_cpool_upkeep(struct Curl_easy *data)
return CURLE_OK;
CPOOL_LOCK(cpool, admin);
- while(cpool_foreach(admin, cpool, NULL, conn_upkeep))
- ;
+ cpool_foreach(admin, cpool, NULL, conn_upkeep);
CPOOL_UNLOCK(cpool, admin);
return CURLE_OK;
}
-struct cpool_find_ctx {
- curl_off_t id;
- struct connectdata *conn;
-};
-
-static int cpool_find_conn(struct cpool *cpool,
- struct Curl_easy *data,
- struct connectdata *conn, void *param)
-{
- struct cpool_find_ctx *fctx = param;
- (void)cpool;
- (void)data;
- if(conn->connection_id == fctx->id) {
- fctx->conn = conn;
- return 1;
- }
- return 0;
-}
-
-struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
- curl_off_t conn_id)
+struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data)
{
struct cpool *cpool = cpool_get_instance(data);
- struct cpool_find_ctx fctx;
+ if(cpool && (data->state.last_cpid != UINT32_MAX)) {
+ struct Curl_easy *admin = Curl_get_admin(data);
+ struct connectdata *conn;
- if(!cpool)
- return NULL;
- fctx.id = conn_id;
- fctx.conn = NULL;
- CPOOL_LOCK(cpool, data);
- cpool_foreach(data, cpool, &fctx, cpool_find_conn);
- CPOOL_UNLOCK(cpool, data);
- return fctx.conn;
+ CPOOL_LOCK(cpool, admin);
+ conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid);
+ CPOOL_UNLOCK(cpool, admin);
+ if(conn && (data->state.last_conn_id == conn->connection_id))
+ return conn;
+ data->state.last_cpid = UINT32_MAX;
+ data->state.last_conn_id = -1;
+ }
+ return NULL;
}
void Curl_cpool_return(struct Curl_easy *data,
@@ -949,8 +971,10 @@ void Curl_cpool_return(struct Curl_easy *data,
conn->connection_id, conn->origin->user_hostname,
conn->origin->port);
}
- else /* connection was removed from the cpool and destroyed. */
- data->state.lastconnect_id = -1;
+ else { /* connection was removed from the cpool and destroyed. */
+ data->state.last_conn_id = -1;
+ data->state.last_cpid = UINT32_MAX;
+ }
break;
case CPOOL_DO_CLOSE:
Curl_conn_close(data, conn, FALSE);
@@ -963,26 +987,14 @@ void Curl_cpool_return(struct Curl_easy *data,
CPOOL_UNLOCK(cpool, data);
}
-static int cpool_mark_stale(struct cpool *cpool,
- struct Curl_easy *admin,
- struct connectdata *conn, void *param)
-{
- (void)cpool;
- (void)admin;
- (void)param;
- conn->bits.no_reuse = TRUE;
- return 0;
-}
-
static int cpool_reap_no_reuse(struct cpool *cpool,
struct Curl_easy *admin,
struct connectdata *conn, void *param)
{
(void)param;
- if(!CONN_INUSE(conn) && conn->bits.no_reuse) {
+ conn->bits.no_reuse = TRUE;
+ if(cpool_conn_can_be_closed(conn))
cpool_conn_close(cpool, admin, conn, FALSE);
- return 1;
- }
return 0; /* continue iteration */
}
@@ -990,9 +1002,7 @@ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin)
{
if(cpool && admin) {
CPOOL_LOCK(cpool, admin);
- cpool_foreach(admin, cpool, NULL, cpool_mark_stale);
- while(cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse))
- ;
+ cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse);
CPOOL_UNLOCK(cpool, admin);
}
}
@@ -1038,7 +1048,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn,
bool healthy = TRUE;
DEBUGASSERT(!data->conn);
- if(!CONN_INUSE(conn) && cpool_conn_too_old(data, conn, pnow))
+ if(cpool_conn_can_be_closed(conn) && cpool_conn_too_old(data, conn, pnow))
return FALSE;
if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000)
return TRUE;
@@ -1055,8 +1065,8 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn,
Curl_attach_connection(admin, conn, FALSE);
healthy = Curl_conn_is_alive(admin, conn, &input_pending);
Curl_detach_connection(admin);
- if(healthy && input_pending &&
- !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) {
+ if(healthy && input_pending && cpool_conn_can_be_closed(conn) &&
+ !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) {
/* Non-multiplexed connections without attached transfers should
* not have input pending. The input might be a TLS Notify Close,
* for all we know. */
@@ -1085,40 +1095,3 @@ timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data,
(void)data;
return curlx_ptimediff_ms(pnow, &conn->created);
}
-
-#if 0
-/* Useful for debugging the connection pool */
-void Curl_cpool_print(struct cpool *cpool)
-{
- struct Curl_hash_iterator iter;
- struct Curl_llist_node *curr;
- struct Curl_hash_element *he;
-
- if(!cpool)
- return;
-
- curl_mfprintf(stderr, "=Bundle cache=\n");
-
- Curl_hash_start_iterate(cpool->dest2bundle, &iter);
-
- he = Curl_hash_next_element(&iter);
- while(he) {
- struct cpool_bundle *bundle;
- struct connectdata *conn;
-
- bundle = he->ptr;
-
- curl_mfprintf(stderr, "%s -", he->key);
- curr = Curl_llist_head(bundle->conns);
- while(curr) {
- conn = Curl_node_elem(curr);
-
- curl_mfprintf(stderr, " [%p %d]", (void *)conn, conn->refcount);
- curr = Curl_node_next(curr);
- }
- curl_mfprintf(stderr, "\n");
-
- he = Curl_hash_next_element(&iter);
- }
-}
-#endif
lib/conncache.h+9 -6
@@ -24,6 +24,8 @@
* SPDX-License-Identifier: curl
*
***************************************************************************/
+#include "uint-bset.h"
+#include "uint-table.h"
#include "curlx/timeval.h"
struct connectdata;
@@ -47,15 +49,16 @@ void Curl_conn_close(struct Curl_easy *data,
bool aborted);
struct cpool {
- /* the pooled connections, bundled per destination */
- struct Curl_hash dest2bundle;
- size_t num_conn;
+ struct uint32_tbl conns; /* connections added to this pool */
+ struct uint32_bset idles; /* pool_ids of conns being idle */
+ struct Curl_hash dest2bundle; /* conn destination sets */
curl_off_t next_connection_id;
curl_off_t next_easy_id;
struct curltime last_cleanup;
struct Curl_share *share; /* != NULL if pool belongs to share */
BIT(locked);
BIT(initialized);
+ BIT(in_shutdown);
};
/* Get connection pool instance for data or NULL if none exists */
@@ -76,9 +79,9 @@ void Curl_cpool_destroy(struct cpool *cpool,
* Assigns `data->id`. */
void Curl_cpool_xfer_init(struct Curl_easy *data);
-/* Get the connection with the given id from `data`'s conn pool. */
-struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
- curl_off_t conn_id);
+/* Get the connection last used by data,
+ * if there was one and it still exists. */
+struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data);
/* Add the connection to the pool. */
CURLcode Curl_cpool_add(struct Curl_easy *data,
lib/connect.c+3 -19
@@ -121,31 +121,15 @@ timediff_t Curl_timeleft_now_ms(struct Curl_easy *data,
curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
struct connectdata **connp)
{
- DEBUGASSERT(data);
-
/* this works for an easy handle:
* - that has been used for curl_easy_perform()
* - that is associated with a multi handle, and whose connection
* was detached with CURLOPT_CONNECT_ONLY
*/
- if(data->state.lastconnect_id != -1) {
- struct connectdata *conn;
-
- conn = Curl_cpool_get_conn(data, data->state.lastconnect_id);
- if(!conn) {
- data->state.lastconnect_id = -1;
- if(connp)
- *connp = NULL;
- return CURL_SOCKET_BAD;
- }
-
- if(connp)
- *connp = conn;
- return conn->sock[FIRSTSOCKET];
- }
+ struct connectdata *conn = Curl_cpool_get_last_conn(data);
if(connp)
- *connp = NULL;
- return CURL_SOCKET_BAD;
+ *connp = conn;
+ return conn ? conn->sock[FIRSTSOCKET] : CURL_SOCKET_BAD;
}
void Curl_conncontrol(struct connectdata *conn, int ctrl)
lib/cshutdn.c+1 -1
@@ -247,7 +247,7 @@ void Curl_cshutdn_terminate(struct Curl_easy *admin,
/* there must be a connection to close */
DEBUGASSERT(conn);
/* it must be removed from the connection pool */
- DEBUGASSERT(!conn->bits.in_cpool);
+ DEBUGASSERT(conn->cpid == UINT32_MAX);
/* the transfer must be detached from the connection */
DEBUGASSERT(admin && !admin->conn);
DEBUGASSERT(!admin->mid);
lib/curl_share.c+2 -1
@@ -424,7 +424,8 @@ CURLcode Curl_share_easy_unlink(struct Curl_easy *data)
if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) {
if(data->conn)
Curl_detach_connection(data);
- data->state.lastconnect_id = -1;
+ data->state.last_conn_id = -1;
+ data->state.last_cpid = UINT32_MAX;
}
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
lib/curl_trc.c+1 -1
@@ -92,7 +92,7 @@ static struct curl_trc_feat Curl_trc_feat_ids = {
static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen)
{
- curl_off_t cid = data->state.lastconnect_id;
+ curl_off_t cid = data->state.last_conn_id;
if(data->id >= 0) {
if(cid >= 0)
return curl_msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid);
lib/easy.c+5 -3
@@ -1030,7 +1030,8 @@ CURL *curl_easy_duphandle(CURL *curl)
Curl_netrc_init(&outcurl->state.netrc);
/* the connection pool is setup on demand */
- outcurl->state.lastconnect_id = -1;
+ outcurl->state.last_conn_id = -1;
+ outcurl->state.last_cpid = UINT32_MAX;
outcurl->id = -1;
outcurl->mid = UINT32_MAX;
outcurl->master_mid = UINT32_MAX;
@@ -1148,7 +1149,8 @@ void curl_easy_reset(CURL *curl)
if(CURL_EAPI_ENTER(&guard, curl, easy_reset, NULL)) {
struct Curl_easy *data = curl;
- data->state.lastconnect_id = -1; /* clear remembered connection id */
+ data->state.last_conn_id = -1; /* clear remembered connection id */
+ data->state.last_cpid = UINT32_MAX;
Curl_req_hard_reset(&data->req, data);
Curl_hash_clean(&data->meta_hash);
@@ -1267,7 +1269,7 @@ static CURLcode easy_connection(struct Curl_easy *data,
if(sfd == CURL_SOCKET_BAD) {
failf(data, "Failed to get last socket used for connection #%" FMT_OFF_T,
- data->state.lastconnect_id);
+ data->state.last_conn_id);
return CURLE_UNSUPPORTED_PROTOCOL;
}
lib/getinfo.c+1 -1
@@ -500,7 +500,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info,
*param_offt = data->id;
break;
case CURLINFO_CONN_ID:
- *param_offt = data->state.lastconnect_id;
+ *param_offt = data->state.last_conn_id;
break;
case CURLINFO_EARLYDATA_SENT_T:
*param_offt = data->progress.earlydata_sent;
lib/multi.c+5 -3
@@ -825,7 +825,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi,
if(data->set.connect_only) {
if(data->multi_easy) {
- if(data->state.lastconnect_id != -1) {
+ if(data->state.last_conn_id != -1) {
/* Mark any connect-only connection for closure */
struct connectdata *conn;
(void)Curl_getconnectinfo(data, &conn);
@@ -943,8 +943,10 @@ void Curl_attach_connection(struct Curl_easy *data,
DEBUGASSERT(conn);
DEBUGASSERT(conn->attached_xfers < UINT32_MAX);
data->conn = conn;
- if(matched)
- data->state.lastconnect_id = conn->connection_id;
+ if(matched) {
+ data->state.last_conn_id = conn->connection_id;
+ data->state.last_cpid = conn->cpid;
+ }
else
DEBUGASSERT(!data->mid); /* admin handle */
conn->attached_xfers++;
lib/url.c+3 -1
@@ -463,7 +463,8 @@ CURLcode Curl_open(struct Curl_easy **curl)
data->magic = CURLEASY_MAGIC_NUMBER;
/* most recent connection is not yet defined */
- data->state.lastconnect_id = -1;
+ data->state.last_conn_id = -1;
+ data->state.last_cpid = UINT32_MAX;
/* and not assigned an id yet */
data->id = -1;
data->mid = UINT32_MAX;
@@ -1129,6 +1130,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
conn->recv_idx = 0; /* default for receiving transfer data */
conn->send_idx = 0; /* default for sending transfer data */
conn->connection_id = -1; /* no ID */
+ conn->cpid = UINT32_MAX; /* not in connection pool yet */
conn->attached_xfers = 0;
conn->shutdown.start_ms[FIRSTSOCKET] =
conn->shutdown.start_ms[SECONDARYSOCKET] = -1;
lib/urldata.h+6 -3
@@ -220,7 +220,6 @@ struct ConnectBits {
BIT(no_reuse); /* connection should not be reused */
BIT(shutdown_handler); /* connection shutdown: handler shut down */
BIT(shutdown_filters); /* connection shutdown: filters shut down */
- BIT(in_cpool); /* connection is kept in a connection pool */
BIT(dns_resolved); /* DNS records for connection were resolved */
};
@@ -286,7 +285,6 @@ struct connectdata {
* the connection is cleaned up (see Curl_hash_add2()).*/
struct Curl_hash meta_hash;
- struct Curl_llist_node cpool_node; /* conncache lists */
struct Curl_llist_node cshutdn_node; /* cshutdn list */
char *destination; /* hostname+port, used in conncache */
@@ -357,6 +355,8 @@ struct connectdata {
#define CONN_INUSE(c) (!!(c)->attached_xfers)
uint32_t attached_xfers; /* # of attached easy handles */
+ uint32_t cpid; /* connection pool id */
+
#ifdef USE_IPV6
uint32_t scope_id; /* Scope id for IPv6 */
#endif
@@ -527,7 +527,6 @@ struct urlpieces {
};
struct UrlState {
- curl_off_t lastconnect_id; /* The last assigned connection or -1 */
/* Origin of the initial (e.g. not followed) request of a transfer.
Credentials from CURLOPT_* are only valid for this origin.
Always set once a transfer starts searching for connections. */
@@ -630,6 +629,10 @@ struct UrlState {
uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */
uint32_t rtsp_CSeq_recv; /* most recent CSeq received */
#endif
+
+ curl_off_t last_conn_id; /* The last assigned connection or -1 */
+ uint32_t last_cpid; /* last connection pool id used */
+
#if defined(USE_HTTP2) || defined(USE_HTTP3)
int weight; /* shallow copy of data->set */
#endif