From: Stefan Eissing Date: Mon, 27 Jul 2026 07:49:33 +0000 (+0200) Subject: conncache: conn upkeep/alive: move and enhance X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c9ead9bd1cdc7fa03d5e9eb20ebed37e27110407;p=thirdparty%2Fcurl.git 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 --- diff --git a/lib/cfilters.c b/lib/cfilters.c index bef39e6f5b..0af8c4bede 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -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, diff --git a/lib/cfilters.h b/lib/cfilters.h index f55a572e2e..cfdb5782ee 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -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 diff --git a/lib/conncache.c b/lib/conncache.c index eb88d67dc8..897b41cc42 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -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) diff --git a/lib/conncache.h b/lib/conncache.h index 78ccd8282f..6798a3b8b3 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -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 */ diff --git a/lib/url.c b/lib/url.c index 1c837c586e..4cb2535033 100644 --- a/lib/url.c +++ b/lib/url.c @@ -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; } diff --git a/lib/url.h b/lib/url.h index 02f1bd3943..c1df2fe811 100644 --- a/lib/url.h +++ b/lib/url.h @@ -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)