connections: use admin handles only for maintenance

Maintenance tasks like connection upkeep, liveliness checks and cache
eviction must only run on an admin handle, not the application transfer
from the calling context.

Then application transfers will get only attached to the connections
they actually work on. This makes `data->state.recent_conn_id`
meaningful for checks if a subsequent address is finding the previous
connection again.

One thing led to another:
- add Curl_get_admin(data), to obtain an admin handle for
  an application handle, inheriting some properties for
  connection operation.
- rename `Curl_easy *data` parameter to `Curl_easy *admin`
  where only admin handles should be passed. Add DEBUGASSERTs
  in the called function.
- rename `Curl_conn_terminate()` to `Curl_conn_close()`
  because this makes connections enter the shutdown close
  where possible.
- rename `Curl_cshutdn_terminate()` to `Curl_conn_terminate()`
  since this definitely kills the connection and does not
  involve a `cshutdn` instance.
- add Curl_share_lock_share(), Curl_share_unlock_share()
  so it possible to operate on a share without the passed
  easy handle having the share set.
  This catches a case where a shared needed to be locked
  but was not before. Adjust test1554 results.

Fixes #22567
Reported-by: cybertron10 on github
Closes #22572
This commit is contained in:
Stefan Eissing 2026-08-13 13:23:53 +02:00 committed by Daniel Stenberg
parent 6e9880366f
commit 7103a93b05
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
15 changed files with 399 additions and 312 deletions

View file

@ -42,7 +42,7 @@
do { \
if(c) { \
if(CURL_SHARE_KEEP_CONNECT((c)->share)) \
Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \
Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT, \
CURL_LOCK_ACCESS_SINGLE); \
DEBUGASSERT(!(c)->locked); \
(c)->locked = TRUE; \
@ -55,7 +55,7 @@
DEBUGASSERT((c)->locked); \
(c)->locked = FALSE; \
if(CURL_SHARE_KEEP_CONNECT((c)->share)) \
Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \
Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \
} \
} while(0)
@ -111,16 +111,12 @@ static void cpool_bundle_free_entry(void *freethis)
}
void Curl_cpool_init(struct cpool *cpool,
struct Curl_easy *idata,
struct Curl_share *share,
size_t size)
{
Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
curlx_str_key_compare, cpool_bundle_free_entry);
DEBUGASSERT(idata);
cpool->idata = idata;
cpool->share = share;
cpool->initialized = TRUE;
}
@ -186,6 +182,8 @@ static void cpool_discard_conn(struct cpool *cpool,
struct connectdata *conn,
bool aborted)
{
struct cshutdn *cshutdn;
struct Curl_easy *admin;
bool done = FALSE;
DEBUGASSERT(data);
@ -193,12 +191,13 @@ static void cpool_discard_conn(struct cpool *cpool,
DEBUGASSERT(cpool);
DEBUGASSERT(!conn->bits.in_cpool);
admin = Curl_get_admin(data);
/*
* If this connection is not marked to force-close, leave it open if there
* are other users of it
*/
if(CONN_INUSE(conn) && !aborted) {
CURL_TRC_M(data, "[CPOOL] not discarding #%" FMT_OFF_T
CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T
" still in use by %u transfers", conn->connection_id,
conn->attached_xfers);
return;
@ -219,35 +218,36 @@ static void cpool_discard_conn(struct cpool *cpool,
done = TRUE;
if(!done) {
/* Attempt to shutdown the connection right away. */
Curl_cshutdn_run_once(cpool->idata, conn, &done);
Curl_conn_shutdown_once(admin, conn, &done);
}
if(done || !data->multi)
Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
cshutdn = Curl_cshutdn_get(data);
if(done || !cshutdn)
Curl_conn_terminate(admin, conn, FALSE);
else
Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
Curl_cshutdn_add(cshutdn, conn, cpool->num_conn);
}
void Curl_cpool_destroy(struct cpool *cpool)
void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin)
{
if(cpool && cpool->initialized && cpool->idata) {
if(cpool && cpool->initialized && admin) {
struct connectdata *conn;
struct Curl_sigpipe_ctx pipe_ctx;
CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections",
cpool->share ? "[SHARE] " : "", cpool->num_conn);
/* Move all connections to the shutdown list */
sigpipe_init(&pipe_ctx);
CPOOL_LOCK(cpool, cpool->idata);
CPOOL_LOCK(cpool, admin);
conn = cpool_get_first(cpool);
if(conn)
sigpipe_apply(cpool->idata, &pipe_ctx);
sigpipe_apply(admin, &pipe_ctx);
while(conn) {
cpool_remove_conn(cpool, conn);
cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
cpool_discard_conn(cpool, admin, conn, FALSE);
conn = cpool_get_first(cpool);
}
CPOOL_UNLOCK(cpool, cpool->idata);
CPOOL_UNLOCK(cpool, admin);
sigpipe_restore(&pipe_ctx);
Curl_hash_destroy(&cpool->dest2bundle);
}
@ -255,17 +255,22 @@ void Curl_cpool_destroy(struct cpool *cpool)
static struct cpool *cpool_get_instance(struct Curl_easy *data)
{
if(data) {
if(CURL_SHARE_KEEP_CONNECT(data->share))
return &data->share->cpool;
else if(data->multi_easy)
return &data->multi_easy->cpool;
else if(data->multi)
return &data->multi->cpool;
}
/* admin handles do not necessarily find the correct pool */
DEBUGASSERT(data->mid);
if(CURL_SHARE_KEEP_CONNECT(data->share))
return &data->share->cpool;
else if(data->multi_easy)
return &data->multi_easy->cpool;
else if(data->multi)
return &data->multi->cpool;
return NULL;
}
struct cpool *Curl_cpool_get_instance(struct Curl_easy *data)
{
return cpool_get_instance(data);
}
void Curl_cpool_xfer_init(struct Curl_easy *data)
{
struct cpool *cpool = cpool_get_instance(data);
@ -367,26 +372,90 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool,
return oldest_idle;
}
static void cpool_conn_close(struct cpool *cpool,
struct Curl_easy *data,
struct connectdata *conn,
bool aborted)
{
struct Curl_easy *admin;
bool do_lock;
DEBUGASSERT(cpool);
DEBUGASSERT(data && !data->conn);
if(!cpool)
return;
/* If this connection is not marked to force-close, leave it open if there
* are other users of it */
if(CONN_INUSE(conn) && !aborted) {
DEBUGASSERT(0); /* does this ever happen? */
DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers));
return;
}
/* This method may be called while we are under lock, e.g. from a
* user callback in find. */
admin = Curl_get_admin(data);
do_lock = !CPOOL_IS_LOCKED(cpool);
if(do_lock)
CPOOL_LOCK(cpool, admin);
if(conn->bits.in_cpool) {
cpool_remove_conn(cpool, conn);
DEBUGASSERT(!conn->bits.in_cpool);
}
/* treat the connection as aborted in CONNECT_ONLY situations,
* so no graceful shutdown is attempted. */
if(conn->bits.connect_only)
aborted = TRUE;
if(data->multi) {
/* Add it to the multi's cpool for shutdown handling */
infof(data, "%s connection #%" FMT_OFF_T,
aborted ? "closing" : "shutting down", conn->connection_id);
cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
}
else {
/* No multi available, terminate */
infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
Curl_conn_terminate(admin, conn, !aborted);
}
if(do_lock)
CPOOL_UNLOCK(cpool, admin);
}
void Curl_conn_close(struct Curl_easy *data,
struct connectdata *conn,
bool aborted)
{
struct cpool *cpool = cpool_get_instance(data);
cpool_conn_close(cpool, data, conn, aborted);
}
/* Evict an idle connection to make room in the pool. A pool owned by
* a share has no multi that could perform a controlled shutdown of the
* connection; terminate it right away. Otherwise, hand it to the
* transfer's multi for shutdown. Expects the pool to be locked. */
static void cpool_evict_conn(struct cpool *cpool,
struct Curl_easy *data,
struct Curl_easy *admin,
struct connectdata *conn)
{
if(cpool->share) {
cpool_remove_conn(cpool, conn);
Curl_cshutdn_terminate(cpool->idata, conn, TRUE);
Curl_conn_terminate(admin, conn, TRUE);
}
else
Curl_conn_terminate(data, conn, FALSE);
cpool_conn_close(cpool, admin, conn, FALSE);
}
int Curl_cpool_check_limits(struct Curl_easy *data,
struct connectdata *conn)
{
struct cpool *cpool = cpool_get_instance(data);
struct cshutdn *cshutdn = Curl_cshutdn_get(data);
struct Curl_easy *admin;
struct cpool_bundle *bundle;
size_t dest_limit = 0;
size_t total_limit = 0;
@ -405,17 +474,18 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
if(!dest_limit && !total_limit)
return CPOOL_LIMIT_OK;
CPOOL_LOCK(cpool, cpool->idata);
admin = Curl_get_admin(data);
CPOOL_LOCK(cpool, admin);
if(dest_limit) {
size_t live;
bundle = cpool_find_bundle(cpool, conn);
live = bundle ? Curl_llist_count(&bundle->conns) : 0;
shutdowns = Curl_cshutdn_dest_count(data, conn->destination);
shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination);
while((live + shutdowns) >= dest_limit) {
if(shutdowns) {
/* close one connection in shutdown right away, if we can */
if(!Curl_cshutdn_close_oldest(data, conn->destination))
if(!Curl_cshutdn_close_oldest(cshutdn, conn->destination))
break;
}
else if(!bundle)
@ -429,17 +499,17 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
if(!oldest_idle)
break;
/* disconnect the old conn and continue */
CURL_TRC_M(data, "Discarding connection #%" FMT_OFF_T
CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T
" from %zu to reach destination limit of %zu",
oldest_idle->connection_id,
Curl_llist_count(&bundle->conns), dest_limit);
cpool_evict_conn(cpool, data, oldest_idle);
cpool_evict_conn(cpool, admin, oldest_idle);
/* in case the bundle was destroyed in disconnect, look it up again */
bundle = cpool_find_bundle(cpool, conn);
live = bundle ? Curl_llist_count(&bundle->conns) : 0;
}
shutdowns = Curl_cshutdn_dest_count(data, conn->destination);
shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination);
}
if((live + shutdowns) >= dest_limit) {
res = CPOOL_LIMIT_DEST;
@ -448,11 +518,11 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
}
if(total_limit) {
shutdowns = Curl_cshutdn_count(data);
shutdowns = Curl_cshutdn_count(cshutdn);
while((cpool->num_conn + shutdowns) >= total_limit) {
if(shutdowns) {
/* close one connection in shutdown right away, if we can */
if(!Curl_cshutdn_close_oldest(data, NULL))
if(!Curl_cshutdn_close_oldest(cshutdn, NULL))
break;
}
else {
@ -461,13 +531,13 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
if(!oldest_idle)
break;
/* disconnect the old conn and continue */
CURL_TRC_M(data, "Discarding connection #%"
CURL_TRC_M(admin, "Discarding connection #%"
FMT_OFF_T " from %zu to reach total "
"limit of %zu",
oldest_idle->connection_id, cpool->num_conn, total_limit);
cpool_evict_conn(cpool, data, oldest_idle);
cpool_evict_conn(cpool, admin, oldest_idle);
}
shutdowns = Curl_cshutdn_count(data);
shutdowns = Curl_cshutdn_count(cshutdn);
}
if((cpool->num_conn + shutdowns) >= total_limit) {
res = CPOOL_LIMIT_TOTAL;
@ -476,7 +546,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
}
out:
CPOOL_UNLOCK(cpool, cpool->idata);
CPOOL_UNLOCK(cpool, admin);
return res;
}
@ -529,7 +599,8 @@ out:
static bool cpool_foreach(struct Curl_easy *data,
struct cpool *cpool,
void *param,
int (*func)(struct Curl_easy *data,
int (*func)(struct cpool *cpool,
struct Curl_easy *data,
struct connectdata *conn, void *param))
{
struct Curl_hash_iterator iter;
@ -553,7 +624,7 @@ static bool cpool_foreach(struct Curl_easy *data,
struct connectdata *conn = Curl_node_elem(curr);
curr = Curl_node_next(curr);
if(func(data, conn, param) == 1) {
if(func(cpool, data, conn, param) == 1) {
return TRUE;
}
}
@ -573,6 +644,7 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
unsigned int maxconnects;
struct connectdata *oldest_idle = NULL;
struct cpool *cpool = cpool_get_instance(data);
struct Curl_easy *admin;
bool kept = TRUE;
if(!data || !data->multi)
@ -590,8 +662,10 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
if(cpool && maxconnects) {
/* may be called form a callback already under lock */
bool do_lock = !CPOOL_IS_LOCKED(cpool);
admin = Curl_get_admin(data);
if(do_lock)
CPOOL_LOCK(cpool, data);
CPOOL_LOCK(cpool, admin);
if(cpool->num_conn > maxconnects) {
infof(data, "Connection pool is full, closing the oldest of %zu/%u",
cpool->num_conn, maxconnects);
@ -599,11 +673,11 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
oldest_idle = cpool_get_oldest_idle(cpool, Curl_pgrs_now(data));
kept = (oldest_idle != conn);
if(oldest_idle) {
cpool_evict_conn(cpool, data, oldest_idle);
cpool_evict_conn(cpool, admin, oldest_idle);
}
}
if(do_lock)
CPOOL_UNLOCK(cpool, data);
CPOOL_UNLOCK(cpool, admin);
}
return kept;
@ -649,74 +723,23 @@ bool Curl_cpool_find(struct Curl_easy *data,
return found;
}
void Curl_conn_terminate(struct Curl_easy *data,
struct connectdata *conn,
bool aborted)
{
struct cpool *cpool = cpool_get_instance(data);
bool do_lock;
DEBUGASSERT(cpool);
DEBUGASSERT(data && !data->conn);
if(!cpool)
return;
/* If this connection is not marked to force-close, leave it open if there
* are other users of it */
if(CONN_INUSE(conn) && !aborted) {
DEBUGASSERT(0); /* does this ever happen? */
DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers));
return;
}
/* This method may be called while we are under lock, e.g. from a
* user callback in find. */
do_lock = !CPOOL_IS_LOCKED(cpool);
if(do_lock)
CPOOL_LOCK(cpool, data);
if(conn->bits.in_cpool) {
cpool_remove_conn(cpool, conn);
DEBUGASSERT(!conn->bits.in_cpool);
}
/* treat the connection as aborted in CONNECT_ONLY situations,
* so no graceful shutdown is attempted. */
if(conn->bits.connect_only)
aborted = TRUE;
if(data->multi) {
/* Add it to the multi's cpool for shutdown handling */
infof(data, "%s connection #%" FMT_OFF_T,
aborted ? "closing" : "shutting down", conn->connection_id);
cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
}
else {
/* No multi available, terminate */
infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
Curl_cshutdn_terminate(cpool->idata, conn, !aborted);
}
if(do_lock)
CPOOL_UNLOCK(cpool, data);
}
struct cpool_reaper_ctx {
size_t reaped;
struct curltime now;
};
static int cpool_reap_dead_cb(struct Curl_easy *data,
static int cpool_reap_dead_cb(struct cpool *cpool,
struct Curl_easy *admin,
struct connectdata *conn, void *param)
{
struct cpool_reaper_ctx *reaper = param;
if(!CONN_INUSE(conn)) {
if(conn->bits.no_reuse || conn->bits.close ||
!Curl_cpool_conn_seems_healthy(conn, data, &reaper->now)) {
!Curl_cpool_conn_seems_healthy(conn, admin, &reaper->now)) {
/* terminate conn and stop the iteration */
reaper->reaped++;
Curl_conn_terminate(data, conn, FALSE);
cpool_conn_close(cpool, admin, conn, FALSE);
return 1;
}
}
@ -730,46 +753,49 @@ static int cpool_reap_dead_cb(struct Curl_easy *data,
*
* When called, this transfer has no connection attached.
*/
void Curl_cpool_prune_dead(struct Curl_easy *data)
void Curl_cpool_prune_dead(struct cpool *cpool,
struct Curl_easy *data)
{
struct cpool *cpool = cpool_get_instance(data);
struct Curl_easy *admin;
timediff_t elapsed;
if(!cpool)
return;
CPOOL_LOCK(cpool, data);
elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data), &cpool->last_cleanup);
admin = Curl_get_admin(data);
CPOOL_LOCK(cpool, admin);
elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup);
if(elapsed >= 1000L) {
struct cpool_reaper_ctx reaper;
memset(&reaper, 0, sizeof(reaper));
reaper.now = *Curl_pgrs_now(data);
while(cpool_foreach(data, cpool, &reaper, cpool_reap_dead_cb))
reaper.now = *Curl_pgrs_now(admin);
while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb))
;
cpool->last_cleanup = *Curl_pgrs_now(data);
cpool->last_cleanup = *Curl_pgrs_now(admin);
}
CPOOL_UNLOCK(cpool, data);
CPOOL_UNLOCK(cpool, admin);
}
static int conn_upkeep(struct Curl_easy *data,
static int conn_upkeep(struct cpool *cpool,
struct Curl_easy *admin,
struct connectdata *conn,
void *param)
{
(void)param;
if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->keepalive) >=
data->set.upkeep_interval_ms) {
if(curlx_ptimediff_ms(Curl_pgrs_now(admin), &conn->keepalive) >=
admin->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);
Curl_attach_connection(admin, conn, FALSE);
result = Curl_conn_keep_alive(admin, conn);
conn->keepalive = *Curl_pgrs_now(admin);
Curl_detach_connection(admin);
if(result && !CONN_INUSE(conn)) {
Curl_conn_terminate(data, conn, FALSE);
cpool_conn_close(cpool, admin, conn, FALSE);
return 1;
}
}
@ -779,14 +805,15 @@ static int conn_upkeep(struct Curl_easy *data,
CURLcode Curl_cpool_upkeep(struct Curl_easy *data)
{
struct cpool *cpool = cpool_get_instance(data);
struct Curl_easy *admin = Curl_get_admin(data);
if(!cpool)
return CURLE_OK;
CPOOL_LOCK(cpool, data);
while(cpool_foreach(data, cpool, NULL, conn_upkeep))
CPOOL_LOCK(cpool, admin);
while(cpool_foreach(admin, cpool, NULL, conn_upkeep))
;
CPOOL_UNLOCK(cpool, data);
CPOOL_UNLOCK(cpool, admin);
return CURLE_OK;
}
@ -795,10 +822,12 @@ struct cpool_find_ctx {
struct connectdata *conn;
};
static int cpool_find_conn(struct Curl_easy *data,
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;
@ -837,36 +866,37 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
cb(conn, data, cbdata);
}
static int cpool_mark_stale(struct Curl_easy *data,
static int cpool_mark_stale(struct cpool *cpool,
struct Curl_easy *admin,
struct connectdata *conn, void *param)
{
(void)data;
(void)cpool;
(void)admin;
(void)param;
conn->bits.no_reuse = TRUE;
return 0;
}
static int cpool_reap_no_reuse(struct Curl_easy *data,
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) {
Curl_conn_terminate(data, conn, FALSE);
cpool_conn_close(cpool, admin, conn, FALSE);
return 1;
}
return 0; /* continue iteration */
}
void Curl_cpool_nw_changed(struct Curl_easy *data)
void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin)
{
struct cpool *cpool = cpool_get_instance(data);
if(cpool) {
CPOOL_LOCK(cpool, data);
cpool_foreach(data, cpool, NULL, cpool_mark_stale);
while(cpool_foreach(data, cpool, NULL, cpool_reap_no_reuse))
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_UNLOCK(cpool, data);
CPOOL_UNLOCK(cpool, admin);
}
}
@ -907,24 +937,27 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn,
struct Curl_easy *data,
const struct curltime *pnow)
{
struct Curl_easy *admin;
bool healthy = TRUE;
DEBUGASSERT(!data->conn);
if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */
return FALSE;
else if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000)
if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000)
return TRUE;
else if(conn->scheme->run->connection_is_dead) {
Curl_attach_connection(data, conn);
healthy = !conn->scheme->run->connection_is_dead(data, conn);
Curl_detach_connection(data);
admin = Curl_get_admin(data);
if(conn->scheme->run->connection_is_dead) {
Curl_attach_connection(admin, conn, FALSE);
healthy = !conn->scheme->run->connection_is_dead(admin, conn);
Curl_detach_connection(admin);
}
else {
bool input_pending = FALSE;
Curl_attach_connection(data, conn);
healthy = Curl_conn_is_alive(data, conn, &input_pending);
Curl_detach_connection(data);
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)) {
/* Non-multiplexed connections without attached transfers should

View file

@ -34,7 +34,7 @@ struct Curl_multi;
struct Curl_share;
/**
* Terminate the connection, e.g. close and destroy.
* Close and destroy the connection.
* If the connection is in a cpool, remove it.
* If a `cshutdn` is available (e.g. data has a multi handle),
* pass the connection to that for controlled shutdown.
@ -42,9 +42,9 @@ struct Curl_share;
* Takes ownership of `conn`.
* `data` should not be attached to a connection.
*/
void Curl_conn_terminate(struct Curl_easy *data,
struct connectdata *conn,
bool aborted);
void Curl_conn_close(struct Curl_easy *data,
struct connectdata *conn,
bool aborted);
struct cpool {
/* the pooled connections, bundled per destination */
@ -53,22 +53,24 @@ struct cpool {
curl_off_t next_connection_id;
curl_off_t next_easy_id;
struct curltime last_cleanup;
struct Curl_easy *idata; /* internal handle for maintenance */
struct Curl_share *share; /* != NULL if pool belongs to share */
BIT(locked);
BIT(initialized);
};
/* Get connection pool instance for data or NULL if none exists */
struct cpool *Curl_cpool_get_instance(struct Curl_easy *data);
/* Init the pool, pass multi only if pool is owned by it.
* Cannot fail.
*/
void Curl_cpool_init(struct cpool *cpool,
struct Curl_easy *idata,
struct Curl_share *share,
size_t size);
/* Destroy all connections and free all members */
void Curl_cpool_destroy(struct cpool *cpool);
void Curl_cpool_destroy(struct cpool *cpool,
struct Curl_easy *admin);
/* Init the transfer to be used within its connection pool.
* Assigns `data->id`. */
@ -126,13 +128,12 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
struct connectdata *conn);
/**
* This function scans the data's connection pool for half-open/dead
* Scans the connection pool for half-open/dead
* connections, closes and removes them.
* The cleanup is done at most once per second.
*
* When called, this transfer has no connection attached.
*/
void Curl_cpool_prune_dead(struct Curl_easy *data);
void Curl_cpool_prune_dead(struct cpool *cpool,
struct Curl_easy *data);
/**
* Perform upkeep actions on connections in the transfer's pool.
@ -154,7 +155,7 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
Curl_cpool_conn_do_cb *cb, void *cbdata);
/* Close all unused connections, prevent reuse of existing ones. */
void Curl_cpool_nw_changed(struct Curl_easy *data);
void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin);
/* Return TRUE iff the given connection is considered healthy, e.g.
* usable for more transfers. */

View file

@ -38,6 +38,13 @@
#include "curlx/strparse.h"
struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data)
{
if(data && data->multi)
return &data->multi->cshutdn;
return NULL;
}
static void cshutdn_run_conn_handler(struct Curl_easy *data,
struct connectdata *conn)
{
@ -107,22 +114,22 @@ static void cshutdn_run_once(struct Curl_easy *data,
conn->bits.shutdown_filters = TRUE;
}
void Curl_cshutdn_run_once(struct Curl_easy *data,
void Curl_conn_shutdown_once(struct Curl_easy *admin,
struct connectdata *conn,
bool *done)
{
DEBUGASSERT(!data->conn);
Curl_attach_connection(data, conn);
cshutdn_run_once(data, conn, done);
CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done);
Curl_detach_connection(data);
DEBUGASSERT(!admin->conn);
DEBUGASSERT(!admin->mid);
Curl_attach_connection(admin, conn, FALSE);
cshutdn_run_once(admin, conn, done);
CURL_TRC_M(admin, "[SHUTDOWN] shutdown, done=%d", *done);
Curl_detach_connection(admin);
}
void Curl_cshutdn_terminate(struct Curl_easy *data,
struct connectdata *conn,
bool do_shutdown)
void Curl_conn_terminate(struct Curl_easy *admin,
struct connectdata *conn,
bool do_shutdown)
{
struct Curl_easy *admin = data;
bool done;
/* there must be a connection to close */
@ -130,16 +137,10 @@ void Curl_cshutdn_terminate(struct Curl_easy *data,
/* it must be removed from the connection pool */
DEBUGASSERT(!conn->bits.in_cpool);
/* the transfer must be detached from the connection */
DEBUGASSERT(data && !data->conn);
DEBUGASSERT(admin && !admin->conn);
DEBUGASSERT(!admin->mid);
/* If we can obtain an internal admin handle, use that to attach
* and terminate the connection. Some protocol will try to mess with
* `data` during shutdown and we do not want that with a `data` from
* the application. */
if(data->multi && data->multi->admin)
admin = data->multi->admin;
Curl_attach_connection(admin, conn);
Curl_attach_connection(admin, conn, FALSE);
cshutdn_run_conn_handler(admin, conn);
if(do_shutdown) {
@ -154,18 +155,17 @@ void Curl_cshutdn_terminate(struct Curl_easy *data,
Curl_conn_cf_discard_all(admin, conn, FIRSTSOCKET);
Curl_detach_connection(admin);
if(data->multi)
Curl_multi_ev_conn_done(data->multi, data, conn);
if(admin->multi)
Curl_multi_ev_conn_done(admin->multi, admin, conn);
Curl_conn_free(admin, conn);
if(data->multi) {
CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged");
Curl_multi_connchanged(data->multi);
if(admin->multi) {
CURL_TRC_M(admin, "[SHUTDOWN] trigger multi connchanged");
Curl_multi_connchanged(admin->multi);
}
}
static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn,
struct Curl_easy *data,
const char *destination)
{
struct Curl_llist_node *e;
@ -184,20 +184,19 @@ static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn,
conn = Curl_node_elem(e);
Curl_node_remove(e);
sigpipe_init(&sigpipe_ctx);
sigpipe_apply(data, &sigpipe_ctx);
Curl_cshutdn_terminate(data, conn, FALSE);
sigpipe_apply(cshutdn->multi->admin, &sigpipe_ctx);
Curl_conn_terminate(cshutdn->multi->admin, conn, FALSE);
sigpipe_restore(&sigpipe_ctx);
return TRUE;
}
return FALSE;
}
bool Curl_cshutdn_close_oldest(struct Curl_easy *data,
bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn,
const char *destination)
{
if(data && data->multi) {
struct cshutdn *csd = &data->multi->cshutdn;
return cshutdn_destroy_oldest(csd, data, destination);
if(cshutdn) {
return cshutdn_destroy_oldest(cshutdn, destination);
}
return FALSE;
}
@ -205,7 +204,6 @@ bool Curl_cshutdn_close_oldest(struct Curl_easy *data,
#define NUM_POLLS_ON_STACK 10
static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
struct Curl_easy *data,
int timeout_ms)
{
struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
@ -214,7 +212,7 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds);
result = Curl_cshutdn_add_pollfds(cshutdn, &cpfds);
if(result)
goto out;
@ -226,11 +224,11 @@ out:
}
static void cshutdn_perform(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct Curl_sigpipe_ctx *sigpipe_ctx)
{
struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
struct Curl_llist_node *enext;
struct Curl_easy *admin = cshutdn->multi->admin;
struct connectdata *conn;
timediff_t next_expire_ms = 0, ms;
bool done;
@ -238,21 +236,21 @@ static void cshutdn_perform(struct cshutdn *cshutdn,
if(!e)
return;
CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections",
CURL_TRC_M(admin, "[SHUTDOWN] perform on %zu connections",
Curl_llist_count(&cshutdn->list));
sigpipe_apply(data, sigpipe_ctx);
sigpipe_apply(admin, sigpipe_ctx);
while(e) {
enext = Curl_node_next(e);
conn = Curl_node_elem(e);
Curl_cshutdn_run_once(data, conn, &done);
Curl_conn_shutdown_once(admin, conn, &done);
if(done) {
Curl_node_remove(e);
Curl_cshutdn_terminate(data, conn, FALSE);
Curl_conn_terminate(admin, conn, FALSE);
}
else {
/* idata has one timer list, but maybe more than one connection.
* Set EXPIRE_SHUTDOWN to the smallest time left for all. */
ms = Curl_conn_shutdown_timeleft(data, conn);
ms = Curl_conn_shutdown_timeleft(admin, conn);
if(ms && (!next_expire_ms || (ms < next_expire_ms)))
next_expire_ms = ms;
}
@ -260,45 +258,42 @@ static void cshutdn_perform(struct cshutdn *cshutdn,
}
if(next_expire_ms)
Curl_expire_ex(data, next_expire_ms, EXPIRE_SHUTDOWN);
Curl_expire_ex(admin, next_expire_ms, EXPIRE_SHUTDOWN);
}
static void cshutdn_terminate_all(struct cshutdn *cshutdn,
struct Curl_easy *data,
int timeout_ms)
{
struct curltime started = *Curl_pgrs_now(data);
struct Curl_easy *admin = cshutdn->multi->admin;
struct curltime started = *Curl_pgrs_now(admin);
struct Curl_llist_node *e;
struct Curl_sigpipe_ctx sigpipe_ctx;
DEBUGASSERT(cshutdn);
DEBUGASSERT(data);
CURL_TRC_M(data, "[SHUTDOWN] shutdown all");
CURL_TRC_M(admin, "[SHUTDOWN] shutdown all");
sigpipe_init(&sigpipe_ctx);
while(Curl_llist_head(&cshutdn->list)) {
timediff_t spent_ms;
int remain_ms;
cshutdn_perform(cshutdn, data, &sigpipe_ctx);
cshutdn_perform(cshutdn, &sigpipe_ctx);
if(!Curl_llist_head(&cshutdn->list)) {
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly");
CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly");
break;
}
/* wait for activity, timeout or "nothing" */
spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &started);
spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(admin), &started);
if(spent_ms >= (timediff_t)timeout_ms) {
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s",
CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, %s",
(timeout_ms > 0) ? "timeout" : "best effort done");
break;
}
remain_ms = timeout_ms - (int)spent_ms;
if(cshutdn_wait(cshutdn, data, remain_ms)) {
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted");
if(cshutdn_wait(cshutdn, remain_ms)) {
CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, aborted");
break;
}
}
@ -308,7 +303,7 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn,
while(e) {
struct connectdata *conn = Curl_node_elem(e);
Curl_node_remove(e);
Curl_cshutdn_terminate(data, conn, FALSE);
Curl_conn_terminate(admin, conn, FALSE);
e = Curl_llist_head(&cshutdn->list);
}
DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
@ -327,12 +322,13 @@ int Curl_cshutdn_init(struct cshutdn *cshutdn,
}
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
struct Curl_easy *data)
struct Curl_easy *admin)
{
if(cshutdn->initialized && data) {
if(cshutdn->initialized && admin) {
int timeout_ms = 0;
/* for testing, run graceful shutdown */
#ifdef DEBUGBUILD
DEBUGASSERT(!admin->mid);
{
const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
if(p) {
@ -343,29 +339,28 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
}
#endif
CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
Curl_llist_count(&cshutdn->list), timeout_ms);
cshutdn_terminate_all(cshutdn, data, timeout_ms);
cshutdn_terminate_all(cshutdn, timeout_ms);
}
cshutdn->multi = NULL;
cshutdn->initialized = FALSE;
}
size_t Curl_cshutdn_count(struct Curl_easy *data)
size_t Curl_cshutdn_count(struct cshutdn *cshutdn)
{
if(data && data->multi) {
struct cshutdn *csd = &data->multi->cshutdn;
return Curl_llist_count(&csd->list);
if(cshutdn) {
return Curl_llist_count(&cshutdn->list);
}
return 0;
}
size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn,
const char *destination)
{
if(data && data->multi) {
struct cshutdn *csd = &data->multi->cshutdn;
if(cshutdn) {
size_t n = 0;
struct Curl_llist_node *e = Curl_llist_head(&csd->list);
struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
while(e) {
struct connectdata *conn = Curl_node_elem(e);
if(!strcmp(destination, conn->destination))
@ -378,17 +373,17 @@ size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
}
static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct connectdata *conn)
{
struct Curl_easy *admin = cshutdn->multi->admin;
CURLMcode mresult;
DEBUGASSERT(cshutdn);
DEBUGASSERT(cshutdn->multi->socket_cb);
Curl_attach_connection(data, conn);
mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn);
Curl_detach_connection(data);
Curl_attach_connection(admin, conn, FALSE);
mresult = Curl_multi_ev_assess_conn(cshutdn->multi, admin, conn);
Curl_detach_connection(admin);
return mresult;
}
@ -396,47 +391,46 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn,
struct connectdata *conn,
size_t conns_in_pool)
{
struct Curl_easy *data = cshutdn->multi->admin;
struct Curl_easy *admin = cshutdn->multi->admin;
size_t max_total = cshutdn->multi->max_total_connections;
/* Add the connection to our shutdown list for non-blocking shutdown
* during multi processing. */
if(max_total > 0 &&
(max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection "
CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection "
"due to connection limit of %zu", max_total);
cshutdn_destroy_oldest(cshutdn, data, NULL);
cshutdn_destroy_oldest(cshutdn, NULL);
}
if(cshutdn->multi->socket_cb) {
if(cshutdn_update_ev(cshutdn, data, conn)) {
CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%"
if(cshutdn_update_ev(cshutdn, conn)) {
CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%"
FMT_OFF_T, conn->connection_id);
Curl_cshutdn_terminate(data, conn, FALSE);
Curl_conn_terminate(admin, conn, FALSE);
return;
}
}
Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T
CURL_TRC_M(admin, "[SHUTDOWN] added #%" FMT_OFF_T
" to shutdowns, now %zu conns in shutdown",
conn->connection_id, Curl_llist_count(&cshutdn->list));
}
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct Curl_sigpipe_ctx *sigpipe_ctx)
{
cshutdn_perform(cshutdn, data, sigpipe_ctx);
cshutdn_perform(cshutdn, sigpipe_ctx);
}
/* return fd_set info about the shutdown connections */
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
struct Curl_easy *data,
fd_set *read_fd_set, fd_set *write_fd_set,
int *maxfd)
{
if(Curl_llist_head(&cshutdn->list)) {
struct Curl_easy *admin = cshutdn->multi->admin;
struct Curl_llist_node *e;
struct easy_pollset ps;
@ -447,9 +441,9 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
CURLcode result;
Curl_pollset_reset(&ps);
Curl_attach_connection(data, conn);
result = Curl_conn_adjust_pollset(data, conn, &ps);
Curl_detach_connection(data);
Curl_attach_connection(admin, conn, FALSE);
result = Curl_conn_adjust_pollset(admin, conn, &ps);
Curl_detach_connection(admin);
if(result)
continue;
@ -473,13 +467,13 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
/* return information about the shutdown connections */
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct Curl_waitfds *cwfds)
{
unsigned int need = 0;
if(Curl_llist_head(&cshutdn->list)) {
struct Curl_llist_node *e;
struct Curl_easy *admin = cshutdn->multi->admin;
struct easy_pollset ps;
struct connectdata *conn;
CURLcode result;
@ -488,9 +482,9 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
conn = Curl_node_elem(e);
Curl_pollset_reset(&ps);
Curl_attach_connection(data, conn);
result = Curl_conn_adjust_pollset(data, conn, &ps);
Curl_detach_connection(data);
Curl_attach_connection(admin, conn, FALSE);
result = Curl_conn_adjust_pollset(admin, conn, &ps);
Curl_detach_connection(admin);
if(!result)
need += Curl_waitfds_add_ps(cwfds, &ps);
@ -501,7 +495,6 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
}
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct curl_pollfds *cpfds)
{
CURLcode result = CURLE_OK;
@ -509,15 +502,16 @@ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
if(Curl_llist_head(&cshutdn->list)) {
struct Curl_llist_node *e;
struct easy_pollset ps;
struct Curl_easy *admin = cshutdn->multi->admin;
struct connectdata *conn;
Curl_pollset_init(&ps);
for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
conn = Curl_node_elem(e);
Curl_pollset_reset(&ps);
Curl_attach_connection(data, conn);
result = Curl_conn_adjust_pollset(data, conn, &ps);
Curl_detach_connection(data);
Curl_attach_connection(admin, conn, FALSE);
result = Curl_conn_adjust_pollset(admin, conn, &ps);
Curl_detach_connection(admin);
if(!result)
result = Curl_pollfds_add_ps(cpfds, &ps);

View file

@ -33,20 +33,20 @@ struct Curl_share;
struct Curl_sigpipe_ctx;
/* Run the shutdown of the connection once.
* Shortly attach/detach `data` to `conn` while doing so.
* Shortly attach/detach the admin handle to `conn` while doing so.
* `done` will be set TRUE if any error was encountered or if
* the connection was shut down completely. */
void Curl_cshutdn_run_once(struct Curl_easy *data,
struct connectdata *conn,
bool *done);
void Curl_conn_shutdown_once(struct Curl_easy *admin,
struct connectdata *conn,
bool *done);
/* Terminates the connection, e.g. closes and destroys it.
* If `do_shutdown` is TRUE, the shutdown will be run once before
* terminating it.
* Takes ownership of `conn`. */
void Curl_cshutdn_terminate(struct Curl_easy *data,
struct connectdata *conn,
bool do_shutdown);
void Curl_conn_terminate(struct Curl_easy *admin,
struct connectdata *conn,
bool do_shutdown);
/* A `cshutdown` is always owned by a multi handle to maintain
* the connections to be shut down. It registers timers and
@ -57,25 +57,28 @@ struct cshutdn {
BIT(initialized);
};
/* Get the cshutdn instance relevant for `data` or NULL if there is none */
struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data);
/* Init as part of the given multi handle. */
int Curl_cshutdn_init(struct cshutdn *cshutdn,
struct Curl_multi *multi);
/* Terminate all remaining connections and free resources. */
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
struct Curl_easy *data);
struct Curl_easy *admin);
/* Number of connections being shut down. */
size_t Curl_cshutdn_count(struct Curl_easy *data);
size_t Curl_cshutdn_count(struct cshutdn *cshutdn);
/* Number of connections to the destination being shut down. */
size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn,
const char *destination);
/* Close the oldest connection in shutdown to destination or,
* when destination is NULL for any destination.
* Return TRUE if a connection has been closed. */
bool Curl_cshutdn_close_oldest(struct Curl_easy *data,
bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn,
const char *destination);
/* Add a connection to have it shut down. Terminate the oldest
@ -86,21 +89,17 @@ void Curl_cshutdn_add(struct cshutdn *cshutdn,
/* Add sockets and POLLIN/OUT flags for connections being shut down. */
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct curl_pollfds *cpfds);
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct Curl_waitfds *cwfds);
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
struct Curl_easy *data,
fd_set *read_fd_set, fd_set *write_fd_set,
int *maxfd);
/* Run maintenance on all connections. */
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
struct Curl_easy *data,
struct Curl_sigpipe_ctx *sigpipe_ctx);
#endif /* HEADER_CURL_CSHUTDN_H */

View file

@ -38,7 +38,7 @@ static void share_destroy(struct Curl_share *share)
return;
if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) {
Curl_cpool_destroy(&share->cpool);
Curl_cpool_destroy(&share->cpool, share->admin);
}
Curl_dnscache_destroy(&share->dnscache);
@ -264,7 +264,7 @@ CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
case CURL_LOCK_DATA_CONNECT:
/* It is safe to set this option several times on a share. */
if(!share->cpool.initialized) {
Curl_cpool_init(&share->cpool, share->admin, share, 103);
Curl_cpool_init(&share->cpool, share, 103);
}
break;
@ -371,11 +371,11 @@ CURLSHcode curl_share_cleanup(CURLSH *sh)
return CURLSHE_OK;
}
CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type,
curl_lock_access accesstype)
CURLSHcode Curl_share_lock_share(struct Curl_share *share,
struct Curl_easy *data,
curl_lock_data type,
curl_lock_access accesstype)
{
struct Curl_share *share = data->share;
if(!share)
return CURLSHE_INVALID;
@ -388,10 +388,16 @@ CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type,
return CURLSHE_OK;
}
CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type)
CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type,
curl_lock_access accesstype)
{
struct Curl_share *share = data->share;
return Curl_share_lock_share(data->share, data, type, accesstype);
}
CURLSHcode Curl_share_unlock_share(struct Curl_share *share,
struct Curl_easy *data,
curl_lock_data type)
{
if(!share)
return CURLSHE_INVALID;
@ -403,6 +409,11 @@ CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type)
return CURLSHE_OK;
}
CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type)
{
return Curl_share_unlock_share(data->share, data, type);
}
CURLcode Curl_share_easy_unlink(struct Curl_easy *data)
{
struct Curl_share *share = data->share;

View file

@ -80,6 +80,14 @@ CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type,
curl_lock_access accesstype);
CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type);
CURLSHcode Curl_share_lock_share(struct Curl_share *share,
struct Curl_easy *data,
curl_lock_data type,
curl_lock_access accesstype);
CURLSHcode Curl_share_unlock_share(struct Curl_share *share,
struct Curl_easy *data,
curl_lock_data type);
/* convenience macro to check if this handle is using a shared SSL spool */
#define CURL_SHARE_ssl_scache(data) \
((data)->share && \

View file

@ -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.recent_conn_id;
curl_off_t cid = data->state.lastconnect_id;
if(data->id >= 0) {
if(cid >= 0)
return curl_msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid);

View file

@ -760,7 +760,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events)
if(data->conn) {
struct connectdata *conn = data->conn;
Curl_detach_connection(data);
Curl_conn_terminate(data, conn, TRUE);
Curl_conn_close(data, conn, TRUE);
DEBUGASSERT(!data->conn);
}
@ -996,7 +996,6 @@ CURL *curl_easy_duphandle(CURL *curl)
/* the connection pool is setup on demand */
outcurl->state.lastconnect_id = -1;
outcurl->state.recent_conn_id = -1;
outcurl->id = -1;
outcurl->mid = UINT32_MAX;
outcurl->master_mid = UINT32_MAX;
@ -1130,7 +1129,7 @@ void curl_easy_reset(CURL *curl)
data->progress.hide = TRUE;
data->state.current_speed = -1; /* init to negative == impossible */
data->state.recent_conn_id = -1; /* clear remembered connection id */
data->state.lastconnect_id = -1; /* clear remembered connection id */
/* zero out authentication data: */
memset(&data->state.authhost, 0, sizeof(struct auth));
@ -1259,7 +1258,7 @@ CURLcode Curl_easy_recv(struct Curl_easy *data,
if(!data->conn)
/* on first invoke, the transfer has been detached from the connection and
needs to be reattached */
Curl_attach_connection(data, c);
Curl_attach_connection(data, c, TRUE);
*n = 0;
return Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, n);
@ -1290,7 +1289,7 @@ CURLcode Curl_connect_only_attach(struct Curl_easy *data)
if(!data->conn)
/* on first invoke, the transfer has been detached from the connection and
needs to be reattached */
Curl_attach_connection(data, c);
Curl_attach_connection(data, c, TRUE);
return CURLE_OK;
}
@ -1316,7 +1315,7 @@ CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer,
if(!data->conn)
/* on first invoke, the transfer has been detached from the connection and
needs to be reattached */
Curl_attach_connection(data, c);
Curl_attach_connection(data, c, TRUE);
sigpipe_ignore(data, &sigpipe_ctx);
result = Curl_conn_send(data, FIRSTSOCKET, buffer, buflen, FALSE, n);

View file

@ -463,8 +463,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info,
*param_offt = data->id;
break;
case CURLINFO_CONN_ID:
*param_offt = data->conn ?
data->conn->connection_id : data->state.recent_conn_id;
*param_offt = data->state.lastconnect_id;
break;
case CURLINFO_EARLYDATA_SENT_T:
*param_offt = data->progress.earlydata_sent;

View file

@ -285,7 +285,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size,
if(Curl_cshutdn_init(&multi->cshutdn, multi))
goto error;
Curl_cpool_init(&multi->cpool, multi->admin, NULL, chashsize);
Curl_cpool_init(&multi->cpool, NULL, chashsize);
#ifdef USE_SSL
if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache))
@ -334,7 +334,7 @@ error:
Curl_multi_ev_cleanup(multi);
Curl_hash_destroy(&multi->proto_hash);
Curl_dnscache_destroy(&multi->dnscache);
Curl_cpool_destroy(&multi->cpool);
Curl_cpool_destroy(&multi->cpool, multi->admin);
Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
#ifdef USE_SSL
Curl_ssl_scache_destroy(multi->ssl_scache);
@ -654,20 +654,19 @@ static void multi_done_locked(struct connectdata *conn,
data->set.reuse_forbid, conn->bits.close, mdctx->premature,
Curl_conn_is_multiplex(conn, FIRSTSOCKET));
connclose(conn);
Curl_conn_terminate(data, conn, (bool)mdctx->premature);
Curl_conn_close(data, conn, (bool)mdctx->premature);
}
else if(!Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) {
CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s was shutdown"
" by server, not reusing", conn->connection_id,
conn->destination);
connclose(conn);
Curl_conn_terminate(data, conn, (bool)mdctx->premature);
Curl_conn_close(data, conn, (bool)mdctx->premature);
}
else {
/* the connection is no longer in use by any transfer */
if(Curl_cpool_conn_now_idle(data, conn)) {
/* connection kept in the cpool */
data->state.lastconnect_id = conn->connection_id;
infof(data, "Connection #%" FMT_OFF_T " to host %s left intact",
conn->connection_id, conn->destination);
}
@ -845,7 +844,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi,
struct connectdata *conn;
(void)Curl_getconnectinfo(data, &conn);
if(conn)
Curl_conn_terminate(data, conn, TRUE);
Curl_conn_close(data, conn, TRUE);
}
}
@ -943,17 +942,23 @@ void Curl_detach_connection(struct Curl_easy *data)
/*
* Curl_attach_connection() attaches this transfer to this connection.
*
* This is the only function that should assign data->conn
* This is the only function that should assign data->conn.
* `matched == TRUE` means the transfer's properties match this
* connection and it is not a temporary attach for maintenance.
*/
void Curl_attach_connection(struct Curl_easy *data,
struct connectdata *conn)
struct connectdata *conn,
bool matched)
{
DEBUGASSERT(data);
DEBUGASSERT(!data->conn);
DEBUGASSERT(conn);
DEBUGASSERT(conn->attached_xfers < UINT32_MAX);
data->conn = conn;
data->state.recent_conn_id = conn->connection_id;
if(matched)
data->state.lastconnect_id = conn->connection_id;
else
DEBUGASSERT(!data->mid); /* admin handle */
conn->attached_xfers++;
/* all attached transfers must be from the same multi */
if(!conn->attached_multi)
@ -1288,8 +1293,8 @@ CURLMcode curl_multi_fdset(CURLM *m,
} while(Curl_uint32_bset_next(&multi->process, mid, &mid));
}
Curl_cshutdn_setfds(&multi->cshutdn, multi->admin,
read_fd_set, write_fd_set, &this_max_fd);
Curl_cshutdn_setfds(&multi->cshutdn, read_fd_set, write_fd_set,
&this_max_fd);
*max_fd = this_max_fd;
Curl_pollset_cleanup(&ps);
@ -1337,7 +1342,7 @@ CURLMcode curl_multi_waitfds(CURLM *m,
} while(Curl_uint32_bset_next(&multi->process, mid, &mid));
}
need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds);
need += Curl_cshutdn_add_waitfds(&multi->cshutdn, &cwfds);
if(need != cwfds.n && ufds)
mresult = CURLM_OUT_OF_MEMORY;
@ -1575,7 +1580,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
} while(Curl_uint32_bset_next(&multi->process, mid, &mid));
}
if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) {
if(Curl_cshutdn_add_pollfds(&multi->cshutdn, &cpfds)) {
mresult = CURLM_OUT_OF_MEMORY;
goto out;
}
@ -1774,7 +1779,7 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
/* take this handle to the perform state right away */
multistate(data, MSTATE_PERFORMING);
Curl_attach_connection(data, conn);
Curl_attach_connection(data, conn, TRUE);
CURL_REQ_SET_RECV(data);
}
return mresult;
@ -2391,7 +2396,7 @@ static CURLcode is_finished(struct Curl_multi *multi,
We do not have to do this in every case block above where a
failure is detected */
Curl_detach_connection(data);
Curl_conn_terminate(data, conn, dead_connection);
Curl_conn_close(data, conn, dead_connection);
}
}
else if(data->mstate == MSTATE_CONNECT) {
@ -2711,7 +2716,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
struct Curl_easy *data,
struct Curl_sigpipe_ctx *sigpipe_ctx)
{
CURLMcode mresult;
CURLMcode mresult = CURLM_OK;
CURLcode result = CURLE_OK;
if(multi->dead) {
@ -2738,8 +2743,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
#ifdef USE_RESOLV_THREADED
Curl_async_thrdd_multi_process(multi);
#endif
Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx);
return CURLM_OK;
Curl_cshutdn_perform(&multi->cshutdn, sigpipe_ctx);
goto out;
}
sigpipe_apply(data, sigpipe_ctx);
@ -2758,8 +2763,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
data->mstate < MSTATE_COMPLETED) {
/* Make sure we set the connection's current owner */
DEBUGASSERT(data->conn);
if(!data->conn)
return CURLM_INTERNAL_ERROR;
if(!data->conn) {
mresult = CURLM_INTERNAL_ERROR;
goto out;
}
}
/* Wait for the connect state as only then is the start time stored, but
@ -2841,7 +2848,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
default:
return CURLM_INTERNAL_ERROR;
mresult = CURLM_INTERNAL_ERROR;
goto out;
}
if(data->mstate >= MSTATE_CONNECT &&
@ -2865,11 +2873,13 @@ statemachine_end:
if(MSTATE_COMPLETED == data->mstate) {
handle_completed(multi, data, result);
return CURLM_OK;
mresult = CURLM_OK;
goto out;
}
} while((mresult == CURLM_CALL_MULTI_PERFORM) ||
multi_ischanged(multi, FALSE));
out:
data->result = result;
return mresult;
}
@ -3014,7 +3024,7 @@ CURLMcode curl_multi_cleanup(CURLM *m)
#ifdef USE_RESOLV_THREADED
Curl_async_thrdd_multi_destroy(multi, !multi->quick_exit);
#endif
Curl_cpool_destroy(&multi->cpool);
Curl_cpool_destroy(&multi->cpool, multi->admin);
Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
if(multi->admin) {
CURL_TRC_M(multi->admin, "multi_cleanup, closing admin handle, done");
@ -3393,7 +3403,7 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...)
Curl_dnscache_clear(multi->admin);
}
if(val & CURLMNWC_CLEAR_CONNS) {
Curl_cpool_nw_changed(multi->admin);
Curl_cpool_nw_changed(&multi->cpool, multi->admin);
}
break;
}

View file

@ -34,7 +34,8 @@ void Curl_expire_clear(struct Curl_easy *data);
void Curl_expire_done(struct Curl_easy *data, expire_id id);
CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT;
void Curl_attach_connection(struct Curl_easy *data,
struct connectdata *conn);
struct connectdata *conn,
bool matched);
void Curl_detach_connection(struct Curl_easy *data);
bool Curl_multiplex_wanted(const struct Curl_multi *multi);
CURLcode Curl_preconnect(struct Curl_easy *data);

View file

@ -471,7 +471,6 @@ 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.recent_conn_id = -1;
/* and not assigned an id yet */
data->id = -1;
data->mid = UINT32_MAX;
@ -914,7 +913,7 @@ static bool url_allow_sspi_empty_creds(struct Curl_creds *conn_creds,
* To avoid TOCTOU attacks, do not reuse on empty credentials
* UNLESS this connection is the one used by this transfer before. */
if(!Curl_creds_has_user(conn_creds) &&
(data->state.recent_conn_id != conn->connection_id))
(data->state.lastconnect_id != conn->connection_id))
return FALSE;
#else
(void)conn_creds;
@ -1113,7 +1112,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
!Curl_cpool_conn_seems_healthy(conn, m->data, &m->now)) {
infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating",
conn->connection_id);
Curl_conn_terminate(m->data, conn, FALSE);
Curl_conn_close(m->data, conn, FALSE);
return FALSE;
}
@ -1128,7 +1127,7 @@ static bool url_match_result(void *userdata)
if(match->found) {
/* Attach it now while still under lock, so the connection does
* no longer appear idle and can be reaped. */
Curl_attach_connection(match->data, match->found);
Curl_attach_connection(match->data, match->found, TRUE);
return TRUE;
}
else if(match->seen_single_use_conn && !match->seen_multiplex_conn) {
@ -1159,11 +1158,11 @@ static bool url_attach_existing(struct Curl_easy *data,
struct connectdata *needle,
bool *waitpipe)
{
struct cpool *cpool = Curl_cpool_get_instance(data);
struct url_conn_match match;
bool success;
DEBUGASSERT(!data->conn);
Curl_cpool_prune_dead(data);
memset(&match, 0, sizeof(match));
match.data = data;
@ -1171,6 +1170,8 @@ static bool url_attach_existing(struct Curl_easy *data,
match.now = *Curl_pgrs_now(data);
match.may_multiplex = xfer_may_multiplex(data, needle);
Curl_cpool_prune_dead(cpool, data);
#ifdef USE_NTLM
match.want_ntlm_http =
(data->state.authhost.want & CURLAUTH_NTLM) &&
@ -2303,9 +2304,9 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data)
goto out;
/* Setup a "faked" transfer that will do nothing */
Curl_attach_connection(data, needle);
result = Curl_cpool_add(data, needle);
Curl_attach_connection(data, needle, TRUE);
needle = NULL;
result = Curl_cpool_add(data, data->conn);
if(!result) {
/* Setup whatever necessary for a resumed transfer */
result = setup_range(data);
@ -2411,7 +2412,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data)
/* Add needle to conn pool, which assigns the connection id.
* Attach regardless of result, for correct handling. */
result = Curl_cpool_add(data, needle);
Curl_attach_connection(data, needle);
Curl_attach_connection(data, needle, TRUE);
needle = NULL;
if(result)
goto out;
@ -2523,7 +2524,7 @@ out:
/* We are not allowed to return failure with memory left allocated in the
connectdata struct, free those here */
Curl_detach_connection(data);
Curl_conn_terminate(data, conn, TRUE);
Curl_conn_close(data, conn, TRUE);
}
return result;
@ -2595,6 +2596,31 @@ void *Curl_conn_meta_get(struct connectdata *conn, const char *key)
return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1);
}
struct Curl_easy *Curl_get_admin(struct Curl_easy *data)
{
struct Curl_easy *admin;
if(!data->mid) /* already an admin handle */
admin = data;
else if(data->multi)
admin = data->multi->admin;
else if(data->multi_easy)
admin = data->multi_easy->admin;
else {
DEBUGASSERT(0); /* we do not want this. does it happen? */
admin = data;
}
if(admin != data) {
admin->set.conn_max_idle_ms = data->set.conn_max_idle_ms;
admin->set.conn_max_age_ms = data->set.conn_max_age_ms;
admin->set.upkeep_interval_ms = data->set.upkeep_interval_ms;
admin->set.timeout = data->set.timeout;
admin->set.server_response_timeout = data->set.server_response_timeout;
admin->set.no_signal = data->set.no_signal;
}
return admin;
}
CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2)
{
if(r1 && (r1 != CURLE_AGAIN))

View file

@ -67,6 +67,12 @@ CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key,
void Curl_conn_meta_remove(struct connectdata *conn, const char *key);
void *Curl_conn_meta_get(struct connectdata *conn, const char *key);
/* Get an admin handle for internal operations from the given
* easy handle, if possible. The admin handle inherits certain
* properties from `data`. If no admin handle is available (not multi
* or share attached), the easy handle itself is returned. */
struct Curl_easy *Curl_get_admin(struct Curl_easy *data);
#define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */
#define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless
specified */

View file

@ -540,9 +540,7 @@ struct UrlState {
/* buffers to store authentication data in, as parsed from input options */
struct curltime keeps_speed; /* for the progress meter really */
curl_off_t lastconnect_id; /* The last connection, -1 if undefined */
curl_off_t recent_conn_id; /* The most recent connection used, might no
* longer exist */
curl_off_t lastconnect_id; /* The last assigned connection or -1 */
struct dynbuf headerb; /* buffer to store headers in */
#ifndef CURL_DISABLE_HSTS
struct curl_slist *hstslist; /* list of HSTS files set by

View file

@ -63,6 +63,8 @@ run 1: foobar and so on fun!
[- Mutex unlock SHARE
-] Mutex lock SHARE
[- Mutex unlock SHARE
-] Mutex lock CONNECT
[- Mutex unlock CONNECT
</datacheck>
</reply>