conncache: conn upkeep/alive: move and enhance

- move `Curl_conn_seems_dead()` into conncache.c
- move `Curl_conn_upkeep()` into conncache.c
- when upkeep gives an error on a connection not in use,
  terminate it

Closes #21806
This commit is contained in:
Stefan Eissing 2026-07-27 09:49:33 +02:00 committed by Daniel Stenberg
parent c5ad90bf9a
commit c9ead9bd1c
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
6 changed files with 122 additions and 143 deletions

View file

@ -450,9 +450,9 @@ static CURLcode cf_cntrl_all(struct connectdata *conn,
int event, int arg1, void *arg2)
{
CURLcode result = CURLE_OK;
size_t i;
int i;
for(i = 0; i < CURL_ARRAYSIZE(conn->cfilter); ++i) {
for(i = 0; i < (int)CURL_ARRAYSIZE(conn->cfilter); ++i) {
result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result,
event, arg1, arg2);
if(!ignore_result && result)
@ -753,7 +753,7 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data,
* connect or shutdown does not add poll events for the other. Check
* against the transfer's own interest, before any chain added sockets
* of its own. */
for(i = 0; (i < 2) && !result; ++i) {
for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) {
if(conn->cfilter[i] &&
(want_io || !Curl_conn_is_connected(conn, i) ||
Curl_shutdown_started(conn, i)))
@ -978,15 +978,17 @@ bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn,
}
CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
struct connectdata *conn)
{
struct Curl_cfilter *cf;
CURLcode result = CURLE_OK;
int i;
if(!CONN_SOCK_IDX_VALID(sockindex))
return CURLE_BAD_FUNCTION_ARGUMENT;
cf = conn->cfilter[sockindex];
return cf ? cf->cft->keep_alive(cf, data) : CURLE_OK;
for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) {
struct Curl_cfilter *cf = conn->cfilter[i];
if(cf)
result = cf->cft->keep_alive(cf, data);
}
return result;
}
size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,

View file

@ -558,11 +558,10 @@ bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn,
bool *input_pending);
/**
* Try to upkeep the connection filters at sockindex.
* Try to upkeep the connection filters.
*/
CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
struct connectdata *conn);
/**
* Get the remote hostname and port that the connection is currently

View file

@ -715,7 +715,7 @@ static int cpool_reap_dead_cb(struct Curl_easy *data,
if(!terminate) {
reaper->checked++;
terminate = Curl_conn_seems_dead(conn, data, &reaper->now);
terminate = Curl_cpool_conn_seems_dead(conn, data, &reaper->now);
}
if(terminate) {
/* stop the iteration here, pass back the connection that was pruned */
@ -761,7 +761,21 @@ static int conn_upkeep(struct Curl_easy *data,
void *param)
{
(void)param;
Curl_conn_upkeep(data, conn);
if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->keepalive) >=
data->set.upkeep_interval_ms) {
CURLcode result;
/* briefly attach for action */
Curl_attach_connection(data, conn);
result = Curl_conn_keep_alive(data, conn);
conn->keepalive = *Curl_pgrs_now(data);
Curl_detach_connection(data);
if(result && !CONN_INUSE(conn)) {
Curl_conn_terminate(data, conn, FALSE);
return 1;
}
}
return 0; /* continue iteration */
}
@ -773,7 +787,8 @@ CURLcode Curl_cpool_upkeep(struct Curl_easy *data)
return CURLE_OK;
CPOOL_LOCK(cpool, data);
cpool_foreach(data, cpool, NULL, conn_upkeep);
while(cpool_foreach(data, cpool, NULL, conn_upkeep))
;
CPOOL_UNLOCK(cpool, data);
return CURLE_OK;
}
@ -858,6 +873,84 @@ void Curl_cpool_nw_changed(struct Curl_easy *data)
}
}
/* A connection has to have been idle for less than 'conn_max_idle_ms'
(the success rate is too low after this), or created less than
'conn_max_age_ms' ago, to be subject for reuse. */
static bool cpool_conn_maxage(struct Curl_easy *data,
struct connectdata *conn,
const struct curltime *pnow)
{
timediff_t age_ms;
if(data->set.conn_max_idle_ms) {
age_ms = curlx_ptimediff_ms(pnow, &conn->lastused);
if(age_ms > data->set.conn_max_idle_ms) {
infof(data, "Too old connection (%" FMT_TIMEDIFF_T
" ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it",
age_ms, data->set.conn_max_idle_ms);
return TRUE;
}
}
if(data->set.conn_max_age_ms) {
age_ms = curlx_ptimediff_ms(pnow, &conn->created);
if(age_ms > data->set.conn_max_age_ms) {
infof(data,
"Too old connection (created %" FMT_TIMEDIFF_T
" ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it",
age_ms, data->set.conn_max_age_ms);
return TRUE;
}
}
return FALSE;
}
/*
* Return TRUE iff the given connection is considered dead.
*/
bool Curl_cpool_conn_seems_dead(struct connectdata *conn,
struct Curl_easy *data,
const struct curltime *pnow)
{
bool input_pending = FALSE;
bool dead = FALSE;
DEBUGASSERT(!data->conn);
/* The check only makes sense only if the connection is not in use */
if(CONN_INUSE(conn))
return FALSE;
else if(cpool_conn_maxage(data, conn, pnow)) /* too old? */
return TRUE;
else if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000)
return FALSE;
else if(conn->scheme->run->connection_is_dead) {
Curl_attach_connection(data, conn);
dead = conn->scheme->run->connection_is_dead(data, conn);
Curl_detach_connection(data);
}
else {
Curl_attach_connection(data, conn);
dead = !Curl_conn_is_alive(data, conn, &input_pending);
Curl_detach_connection(data);
}
if(input_pending) {
/* For reuse, we want a "clean" connection state. This includes
* that we expect - in general - no waiting input data. Input
* waiting might be a TLS Notify Close, for example. We reject
* that.
* For protocols where data from other end may arrive at
* any time (HTTP/2 PING for example), the protocol handler needs
* to install its own `connection_check` callback.
*/
DEBUGF(infof(data, "connection has input pending, not reusable"));
dead = TRUE;
}
return dead;
}
#if 0
/* Useful for debugging the connection pool */
void Curl_cpool_print(struct cpool *cpool)

View file

@ -156,4 +156,11 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
/* Close all unused connections, prevent reuse of existing ones. */
void Curl_cpool_nw_changed(struct Curl_easy *data);
/**
* Return TRUE iff the given connection is considered dead.
*/
bool Curl_cpool_conn_seems_dead(struct connectdata *conn,
struct Curl_easy *data,
const struct curltime *pnow);
#endif /* HEADER_CURL_CONNCACHE_H */

113
lib/url.c
View file

@ -564,115 +564,6 @@ static bool proxy_info_matches(const struct proxy_info *data,
}
#endif
/* A connection has to have been idle for less than 'conn_max_idle_ms'
(the success rate is too low after this), or created less than
'conn_max_age_ms' ago, to be subject for reuse. */
static bool conn_maxage(struct Curl_easy *data,
struct connectdata *conn,
struct curltime now)
{
timediff_t age_ms;
if(data->set.conn_max_idle_ms) {
age_ms = curlx_ptimediff_ms(&now, &conn->lastused);
if(age_ms > data->set.conn_max_idle_ms) {
infof(data, "Too old connection (%" FMT_TIMEDIFF_T
" ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it",
age_ms, data->set.conn_max_idle_ms);
return TRUE;
}
}
if(data->set.conn_max_age_ms) {
age_ms = curlx_ptimediff_ms(&now, &conn->created);
if(age_ms > data->set.conn_max_age_ms) {
infof(data,
"Too old connection (created %" FMT_TIMEDIFF_T
" ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it",
age_ms, data->set.conn_max_age_ms);
return TRUE;
}
}
return FALSE;
}
/*
* Return TRUE iff the given connection is considered dead.
*/
bool Curl_conn_seems_dead(struct connectdata *conn,
struct Curl_easy *data,
const struct curltime *pnow)
{
DEBUGASSERT(!data->conn);
if(!CONN_INUSE(conn)) {
/* The check for a dead socket makes sense only if the connection is not in
use */
bool dead;
if(conn_maxage(data, conn, *pnow)) {
/* avoid check if already too old */
dead = TRUE;
}
else if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000)
dead = FALSE;
else if(conn->scheme->run->connection_is_dead) {
/* The protocol has a special method for checking the state of the
connection. Use it to check if the connection is dead. */
/* briefly attach the connection for the check */
Curl_attach_connection(data, conn);
dead = conn->scheme->run->connection_is_dead(data, conn);
Curl_detach_connection(data);
conn->lastchecked = *pnow;
}
else {
bool input_pending = FALSE;
Curl_attach_connection(data, conn);
dead = !Curl_conn_is_alive(data, conn, &input_pending);
if(input_pending) {
/* For reuse, we want a "clean" connection state. This includes
* that we expect - in general - no waiting input data. Input
* waiting might be a TLS Notify Close, for example. We reject
* that.
* For protocols where data from other end may arrive at
* any time (HTTP/2 PING for example), the protocol handler needs
* to install its own `connection_check` callback.
*/
DEBUGF(infof(data, "connection has input pending, not reusable"));
dead = TRUE;
}
Curl_detach_connection(data);
conn->lastchecked = *pnow;
}
if(dead) {
/* remove connection from cpool */
infof(data, "Connection %" FMT_OFF_T " seems to be dead",
conn->connection_id);
return TRUE;
}
}
return FALSE;
}
CURLcode Curl_conn_upkeep(struct Curl_easy *data,
struct connectdata *conn)
{
CURLcode result = CURLE_OK;
if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->keepalive) <=
data->set.upkeep_interval_ms)
return result;
/* briefly attach for action */
Curl_attach_connection(data, conn);
result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
Curl_detach_connection(data);
conn->keepalive = *Curl_pgrs_now(data);
return result;
}
#ifdef USE_SSH
static bool ssh_config_matches(struct connectdata *one,
struct connectdata *two)
@ -1172,8 +1063,10 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
if(!url_match_multiplex_limits(conn, m))
return FALSE;
if(!CONN_INUSE(conn) && Curl_conn_seems_dead(conn, m->data, &m->now)) {
if(Curl_cpool_conn_seems_dead(conn, m->data, &m->now)) {
/* remove and disconnect. */
infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating",
conn->connection_id);
Curl_conn_terminate(m->data, conn, FALSE);
return FALSE;
}

View file

@ -71,23 +71,8 @@ void *Curl_conn_meta_get(struct connectdata *conn, const char *key);
#define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless
specified */
/**
* Return TRUE iff the given connection is considered dead.
*/
bool Curl_conn_seems_dead(struct connectdata *conn,
struct Curl_easy *data,
const struct curltime *pnow);
/**
* Perform upkeep operations on the connection.
*/
CURLcode Curl_conn_upkeep(struct Curl_easy *data,
struct connectdata *conn);
/**
* Always eval all arguments, return the first
* result != (CURLE_OK | CURLE_AGAIN) or `r1`.
*/
/* Always eval all arguments, return the first
* result != (CURLE_OK | CURLE_AGAIN) or `r1`. */
CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2);
#if defined(USE_HTTP2) || defined(USE_HTTP3)