Coverage Report

Created: 2026-10-22 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/conncache.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
9
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
10
 *
11
 * This software is licensed as described in the file COPYING, which
12
 * you should have received as part of this distribution. The terms
13
 * are also available at https://curl.se/docs/copyright.html.
14
 *
15
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
16
 * copies of the Software, and permit persons to whom the Software is
17
 * furnished to do so, under the terms of the COPYING file.
18
 *
19
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20
 * KIND, either express or implied.
21
 *
22
 * SPDX-License-Identifier: curl
23
 *
24
 ***************************************************************************/
25
#include "curl_setup.h"
26
27
#include "urldata.h"
28
#include "url.h"
29
#include "cfilters.h"
30
#include "progress.h"
31
#include "multiif.h"
32
#include "curl_trc.h"
33
#include "cshutdn.h"
34
#include "conncache.h"
35
#include "curl_share.h"
36
#include "sigpipe.h"
37
#include "uint-spbset.h"
38
39
40
10.0k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
41
42
#define CPOOL_LOCK(c, d)                                                \
43
66.6k
  do {                                                                  \
44
66.6k
    if(c) {                                                             \
45
66.6k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
46
66.6k
        Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT,  \
47
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
48
66.6k
      DEBUGASSERT(!(c)->locked);                                        \
49
66.6k
      (c)->locked = TRUE;                                               \
50
66.6k
    }                                                                   \
51
66.6k
  } while(0)
52
53
#define CPOOL_UNLOCK(c, d)                                              \
54
66.6k
  do {                                                                  \
55
66.6k
    if(c) {                                                             \
56
66.6k
      DEBUGASSERT((c)->locked);                                         \
57
66.6k
      (c)->locked = FALSE;                                              \
58
66.6k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
59
66.6k
        Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \
60
66.6k
    }                                                                   \
61
66.6k
  } while(0)
62
63
/* A set of connection pool ids to the same destination. */
64
struct cpool_bundle {
65
  struct uint32_spbset ids; /* bitset of connection `pool_id`s  */
66
  uint32_t refcount;
67
};
68
69
static void cpool_bundle_unlink(struct cpool_bundle **pbundle)
70
59.9k
{
71
59.9k
  if(*pbundle) {
72
30.2k
    struct cpool_bundle *bundle = *pbundle;
73
74
30.2k
    DEBUGASSERT(bundle->refcount);
75
30.2k
    *pbundle = NULL;
76
30.2k
    if(bundle->refcount)
77
30.2k
      bundle->refcount--;
78
30.2k
    if(!bundle->refcount) {
79
10.0k
      Curl_uint32_spbset_destroy(&bundle->ids);
80
10.0k
      curlx_free(bundle);
81
10.0k
    }
82
30.2k
  }
83
59.9k
}
84
85
static void cpool_bundle_link(struct cpool_bundle **pdest,
86
                              struct cpool_bundle *src)
87
39.7k
{
88
39.7k
  if(*pdest != src) {
89
20.1k
    cpool_bundle_unlink(pdest);
90
20.1k
    *pdest = src;
91
20.1k
    if(src) {
92
20.1k
      DEBUGASSERT(src->refcount < UINT32_MAX);
93
20.1k
      src->refcount++;
94
20.1k
    }
95
20.1k
  }
96
39.7k
}
97
98
static struct cpool_bundle *cpool_create_bundle(void)
99
10.0k
{
100
  /* created with refcount == 1 */
101
10.0k
  struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle));
102
10.0k
  if(!bundle)
103
1
    return NULL;
104
105
10.0k
  Curl_uint32_spbset_init(&bundle->ids);
106
10.0k
  bundle->refcount = 1;
107
10.0k
  return bundle;
108
10.0k
}
109
110
/* Add a connection to a bundle */
111
static bool cpool_bundle_add_id(struct cpool *cpool,
112
                                struct cpool_bundle *bundle,
113
                                const char *destination,
114
                                uint32_t pool_id)
115
10.0k
{
116
10.0k
  if(!Curl_uint32_spbset_add(&bundle->ids, pool_id))
117
0
    return FALSE;
118
10.0k
  if(Curl_uint32_spbset_count(&bundle->ids) == 1) {
119
    /* first one added, register bundle */
120
10.0k
    struct cpool_bundle *new_ref = NULL;
121
10.0k
    cpool_bundle_link(&new_ref, bundle);
122
10.0k
    if(!Curl_hash_add(&cpool->dest2bundle,
123
10.0k
                      destination, strlen(destination), new_ref)) {
124
6
      cpool_bundle_unlink(&new_ref);
125
6
      return FALSE;
126
6
    }
127
10.0k
  }
128
10.0k
  return TRUE;
129
10.0k
}
130
131
/* Remove a connection from a bundle */
132
static void cpool_bundle_remove_id(struct cpool *cpool,
133
                                   struct cpool_bundle *bundle,
134
                                   const char *destination,
135
                                   uint32_t pool_id)
136
10.0k
{
137
10.0k
  Curl_uint32_spbset_remove(&bundle->ids, pool_id);
138
10.0k
  if(!Curl_uint32_spbset_count(&bundle->ids)) {
139
    /* Now empty, unregister bundle */
140
10.0k
    Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination));
141
10.0k
  }
142
10.0k
}
143
144
static void cpool_bundle_hash_dtor(void *entry)
145
10.0k
{
146
10.0k
  struct cpool_bundle *bundle = entry;
147
10.0k
  cpool_bundle_unlink(&bundle);
148
10.0k
}
149
150
static struct cpool_bundle *cpool_get_bundle(struct cpool *cpool,
151
                                             const char *destination)
152
29.6k
{
153
29.6k
  struct cpool_bundle *bundle = NULL;
154
29.6k
  cpool_bundle_link(&bundle, Curl_hash_pick(
155
29.6k
    &cpool->dest2bundle, destination, strlen(destination)));
156
29.6k
  return bundle;
157
29.6k
}
158
159
static void cpool_remove_conn(struct cpool *cpool,
160
                              struct connectdata *conn)
161
10.0k
{
162
10.0k
  uint32_t cpid = conn->cpid;
163
164
10.0k
  DEBUGASSERT(cpool);
165
10.0k
  if(cpid != UINT32_MAX) {
166
10.0k
    struct cpool_bundle *bundle;
167
168
10.0k
#ifdef DEBUGBUILD
169
10.0k
    {
170
10.0k
      struct connectdata *pooled_conn =
171
10.0k
        Curl_uint32_tbl_get(&cpool->conns, cpid);
172
10.0k
      DEBUGASSERT(conn == pooled_conn);
173
10.0k
    }
174
10.0k
#endif
175
10.0k
    Curl_uint32_tbl_remove(&cpool->conns, cpid);
176
10.0k
    Curl_uint32_bset_remove(&cpool->idles, cpid);
177
10.0k
    conn->cpid = UINT32_MAX;
178
179
10.0k
    bundle = cpool_get_bundle(cpool, conn->destination);
180
10.0k
    DEBUGASSERT(bundle);
181
10.0k
    if(bundle)
182
10.0k
      cpool_bundle_remove_id(cpool, bundle, conn->destination, cpid);
183
10.0k
    cpool_bundle_unlink(&bundle);
184
10.0k
  }
185
10.0k
}
186
187
static void cpool_discard_conn(struct cpool *cpool,
188
                               struct Curl_easy *data,
189
                               struct connectdata *conn,
190
                               bool aborted)
191
10.0k
{
192
10.0k
  struct Curl_easy *admin;
193
10.0k
  bool done = FALSE;
194
195
10.0k
  DEBUGASSERT(data);
196
10.0k
  DEBUGASSERT(!data->conn);
197
10.0k
  DEBUGASSERT(cpool);
198
10.0k
  DEBUGASSERT(conn->cpid == UINT32_MAX);
199
200
10.0k
  admin = Curl_get_admin(data);
201
  /* We MUST NOT discard a connection that is still in use and would
202
   * trigger Use-After-Free. */
203
10.0k
  if(CONN_INUSE(conn)) {
204
0
    CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T
205
0
               " still in use by %u transfers", conn->connection_id,
206
0
               conn->attached_xfers);
207
0
    DEBUGASSERT(0);
208
0
    return;
209
0
  }
210
211
  /* treat the connection as aborted in CONNECT_ONLY situations, we do
212
   * not know what the APP did with it. */
213
10.0k
  if(conn->bits.connect_only)
214
600
    aborted = TRUE;
215
10.0k
  conn->bits.aborted = aborted;
216
217
  /* We do not shutdown dead connections. The term 'dead' can be misleading
218
   * here, as we also mark errored connections/transfers as 'dead'.
219
   * If we do a shutdown for an aborted transfer, the server might think
220
   * it was successful otherwise (for example an ftps: upload). This is
221
   * not what we want. */
222
10.0k
  if(aborted)
223
9.30k
    done = TRUE;
224
10.0k
  if(!done) {
225
    /* Attempt to shutdown the connection right away. */
226
785
    Curl_cshutdn_try_once(admin, conn, &done);
227
785
  }
228
229
10.0k
  if(done || !data->multi)
230
10.0k
    Curl_cshutdn_terminate(admin, conn, FALSE);
231
0
  else {
232
0
    struct Curl_multi *multi = data->multi;
233
0
    size_t max_shutdowns = multi->max_total_connections;
234
0
    uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
235
236
0
    if(num_conns < max_shutdowns)
237
0
      max_shutdowns -= num_conns;
238
0
    else if(max_shutdowns)
239
0
      max_shutdowns = 1;
240
0
    else /* no connection limit set, let's restrict growth nevertheless */
241
0
      max_shutdowns = CURLMAX(num_conns / 4, 128);
242
0
    Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns);
243
0
  }
244
10.0k
}
245
246
void Curl_cpool_init(struct cpool *cpool,
247
                     struct Curl_share *share,
248
                     size_t size)
249
12.6k
{
250
12.6k
  Curl_uint32_tbl_init(&cpool->conns);
251
12.6k
  Curl_uint32_bset_init(&cpool->idles);
252
12.6k
  Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES,
253
12.6k
                 cpool_bundle_hash_dtor);
254
12.6k
  cpool->share = share;
255
12.6k
  cpool->initialized = TRUE;
256
12.6k
  cpool->in_shutdown = FALSE;
257
12.6k
  cpool->locked = FALSE;
258
12.6k
}
259
260
void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin)
261
12.7k
{
262
12.7k
  if(cpool && cpool->initialized && admin) {
263
12.6k
    void *entry;
264
12.6k
    uint32_t cpid;
265
266
12.6k
    CURL_TRC_M(admin, "%s[CPOOL] destroy, %u connections",
267
12.6k
               cpool->share ? "[SHARE] " : "",
268
12.6k
               Curl_uint32_tbl_count(&cpool->conns));
269
    /* Move all connections to the shutdown list */
270
12.6k
    CPOOL_LOCK(cpool, admin);
271
12.6k
    if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) {
272
0
      struct Curl_sigpipe_ctx pipe_ctx;
273
274
0
      sigpipe_init(&pipe_ctx);
275
0
      sigpipe_apply(admin, &pipe_ctx);
276
0
      do {
277
0
        struct connectdata *conn = entry;
278
0
        cpool_remove_conn(cpool, conn);
279
0
        cpool_discard_conn(cpool, admin, conn, FALSE);
280
0
      } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry));
281
0
      sigpipe_restore(&pipe_ctx);
282
0
    }
283
12.6k
    CPOOL_UNLOCK(cpool, admin);
284
12.6k
    Curl_hash_destroy(&cpool->dest2bundle);
285
12.6k
    Curl_uint32_bset_destroy(&cpool->idles);
286
12.6k
    Curl_uint32_tbl_destroy(&cpool->conns);
287
12.6k
  }
288
12.7k
}
289
290
static struct cpool *cpool_get_instance(struct Curl_easy *data)
291
86.5k
{
292
  /* admin handles do not necessarily find the correct pool */
293
86.5k
  DEBUGASSERT(data->mid);
294
86.5k
  if(CURL_SHARE_KEEP_CONNECT(data->share))
295
0
    return &data->share->cpool;
296
86.5k
  else if(data->multi_easy)
297
0
    return &data->multi_easy->cpool;
298
86.5k
  else if(data->multi)
299
73.7k
    return &data->multi->cpool;
300
12.7k
  return NULL;
301
86.5k
}
302
303
struct cpool *Curl_cpool_get_instance(struct Curl_easy *data)
304
0
{
305
0
  return cpool_get_instance(data);
306
0
}
307
308
void Curl_cpool_xfer_init(struct Curl_easy *data)
309
12.6k
{
310
12.6k
  struct cpool *cpool = cpool_get_instance(data);
311
312
12.6k
  if(cpool) {
313
12.6k
    CPOOL_LOCK(cpool, data);
314
    /* the identifier inside the connection cache */
315
12.6k
    data->id = cpool->next_easy_id++;
316
12.6k
    if(cpool->next_easy_id == CURL_OFF_T_MAX)
317
0
      cpool->next_easy_id = 0;
318
12.6k
    data->state.last_conn_id = -1;
319
12.6k
    data->state.last_cpid = UINT32_MAX;
320
321
12.6k
    CPOOL_UNLOCK(cpool, data);
322
12.6k
  }
323
0
  else {
324
    /* We should not get here, but in a non-debug build, do something */
325
0
    DEBUGASSERT(0);
326
0
    data->id = 0;
327
0
    data->state.last_conn_id = -1;
328
0
    data->state.last_cpid = UINT32_MAX;
329
0
  }
330
12.6k
}
331
332
static bool cpool_conn_can_be_closed(struct connectdata *conn)
333
0
{
334
  /* CONNECT_ONLY sockets remain in use by the application. */
335
0
  return (conn && !CONN_INUSE(conn) && !conn->bits.connect_only);
336
0
}
337
338
static struct connectdata *cpool_bundle_get_oldest_idle(
339
  struct cpool *cpool,
340
  struct cpool_bundle *bundle,
341
  const struct curltime *pnow)
342
0
{
343
0
  uint32_t cpid, oldest_id = UINT32_MAX;
344
0
  timediff_t unused_ms;
345
0
  timediff_t oldest_ms = -1;
346
347
0
  if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) {
348
0
    do {
349
0
      struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid);
350
0
      if(cpool_conn_can_be_closed(conn)) {
351
        /* Set higher score for the age passed since the connection was used */
352
0
        unused_ms =
353
0
          curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
354
0
        if(unused_ms > oldest_ms) {
355
0
          oldest_ms = unused_ms;
356
0
          oldest_id = cpid;
357
0
        }
358
0
      }
359
0
      else if(!conn) {
360
0
        DEBUGASSERT(0);
361
0
        Curl_uint32_spbset_remove(&bundle->ids, cpid);
362
0
      }
363
0
    } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid));
364
0
  }
365
0
  return Curl_uint32_tbl_get(&cpool->conns, oldest_id);
366
0
}
367
368
static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool,
369
                                                 const struct curltime *pnow,
370
                                                 timediff_t min_age_ms)
371
0
{
372
0
  uint32_t cpid, oldest_id = UINT32_MAX;
373
0
  timediff_t oldest_idle_ms = -1;
374
0
  timediff_t idle_ms;
375
376
  /* Iterate over all connections that once were idle. They may
377
   * no longer be, in which case we remove them from the set again. */
378
0
  if(Curl_uint32_bset_first(&cpool->idles, &cpid)) {
379
0
    do {
380
0
      struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid);
381
0
      if(cpool_conn_can_be_closed(conn)) {
382
0
        idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
383
0
        if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) {
384
0
          oldest_idle_ms = idle_ms;
385
0
          oldest_id = cpid;
386
0
        }
387
0
      }
388
0
      else {
389
0
        DEBUGASSERT(conn);
390
0
        Curl_uint32_bset_remove(&cpool->idles, cpid);
391
0
      }
392
0
    } while(Curl_uint32_bset_next(&cpool->idles, cpid, &cpid));
393
0
  }
394
0
  return Curl_uint32_tbl_get(&cpool->conns, oldest_id);
395
0
}
396
397
static void cpool_conn_close(struct cpool *cpool,
398
                             struct Curl_easy *data,
399
                             struct connectdata *conn,
400
                             bool aborted)
401
10.0k
{
402
10.0k
  struct Curl_easy *admin;
403
10.0k
  bool do_lock;
404
405
10.0k
  DEBUGASSERT(cpool);
406
10.0k
  DEBUGASSERT(data && !data->conn);
407
10.0k
  if(!cpool)
408
0
    return;
409
410
  /* If this connection is not marked to force-close, leave it open if there
411
   * are other users of it */
412
10.0k
  if(CONN_INUSE(conn) && !aborted) {
413
0
    DEBUGASSERT(0); /* does this ever happen? */
414
0
    DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers));
415
0
    return;
416
0
  }
417
418
  /* This method may be called while we are under lock, e.g. from a
419
   * user callback in find. */
420
10.0k
  admin = Curl_get_admin(data);
421
10.0k
  do_lock = !CPOOL_IS_LOCKED(cpool);
422
10.0k
  if(do_lock)
423
593
    CPOOL_LOCK(cpool, admin);
424
425
10.0k
  if(conn->cpid != UINT32_MAX) {
426
10.0k
    cpool_remove_conn(cpool, conn);
427
10.0k
    DEBUGASSERT(conn->cpid == UINT32_MAX);
428
10.0k
  }
429
430
  /* treat the connection as aborted in CONNECT_ONLY situations,
431
   * so no graceful shutdown is attempted. */
432
10.0k
  if(conn->bits.connect_only)
433
600
    aborted = TRUE;
434
435
10.0k
  if(data->multi) {
436
    /* Add it to the multi's cpool for shutdown handling */
437
10.0k
    infof(data, "%s connection #%" FMT_OFF_T,
438
10.0k
          aborted ? "closing" : "shutting down", conn->connection_id);
439
10.0k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
440
10.0k
  }
441
0
  else {
442
    /* No multi available, terminate */
443
0
    infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
444
0
    Curl_cshutdn_terminate(admin, conn, !aborted);
445
0
  }
446
447
10.0k
  if(do_lock)
448
593
    CPOOL_UNLOCK(cpool, admin);
449
10.0k
}
450
451
void Curl_conn_close(struct Curl_easy *data,
452
                     struct connectdata *conn,
453
                     bool aborted)
454
10.0k
{
455
10.0k
  struct cpool *cpool = cpool_get_instance(data);
456
10.0k
  cpool_conn_close(cpool, data, conn, aborted);
457
10.0k
}
458
459
/* Evict an idle connection to make room in the pool. A pool owned by
460
 * a share has no multi that could perform a controlled shutdown of the
461
 * connection; terminate it right away. Otherwise, hand it to the
462
 * transfer's multi for shutdown. Expects the pool to be locked. */
463
static void cpool_evict_conn(struct cpool *cpool,
464
                             struct Curl_easy *admin,
465
                             struct connectdata *conn)
466
0
{
467
0
  if(cpool->share) {
468
0
    cpool_remove_conn(cpool, conn);
469
0
    Curl_cshutdn_terminate(admin, conn, TRUE);
470
0
  }
471
0
  else
472
0
    cpool_conn_close(cpool, admin, conn, FALSE);
473
0
}
474
475
10.0k
#define CPOOL_LIMIT_OK     0
476
0
#define CPOOL_LIMIT_DEST   1
477
0
#define CPOOL_LIMIT_TOTAL  2
478
479
static int cpool_check_limits(struct Curl_easy *data,
480
                              uint32_t max_total,
481
                              struct connectdata *to_add,
482
                              uint32_t max_host,
483
                              struct cpool_bundle *bundle,
484
                              const struct curltime *pnow)
485
10.0k
{
486
10.0k
  struct cpool *cpool = cpool_get_instance(data);
487
10.0k
  struct cshutdn *cshutdn = data->multi ? &data->multi->cshutdn : NULL;
488
10.0k
  struct Curl_easy *admin = NULL;
489
490
10.0k
  if(!cpool)
491
0
    return CPOOL_LIMIT_OK;
492
493
10.0k
  if(!max_total && !max_host)
494
10.0k
    return CPOOL_LIMIT_OK;
495
496
0
  if(max_host) {
497
0
    size_t live = 0, shutdowns = 0;
498
    /* if we are at or above `dest_limit`, try to get rid of connections
499
     * in shutdown and, if that does not lower it, evict idle connections
500
     * from the pool. */
501
0
    admin = Curl_get_admin(data);
502
0
    if(bundle) {
503
0
      live = Curl_uint32_spbset_count(&bundle->ids);
504
0
      if(live >= max_host) {
505
0
        size_t over = live - max_host + 1;
506
0
        for(; over; --over) {
507
0
          struct connectdata *oldest_idle =
508
0
            cpool_bundle_get_oldest_idle(cpool, bundle, pnow);
509
0
          if(!oldest_idle)
510
0
            break;
511
          /* disconnect the old conn and continue */
512
0
          CURL_TRC_M(admin, "Shutting down connection #%" FMT_OFF_T
513
0
                     " to '%s' due to destination limit of %u",
514
0
                     oldest_idle->connection_id, oldest_idle->destination,
515
0
                     max_host);
516
0
          cpool_evict_conn(cpool, admin, oldest_idle);
517
0
        }
518
0
        live = Curl_uint32_spbset_count(&bundle->ids);
519
0
      }
520
0
    }
521
0
    if(cshutdn) {
522
0
      shutdowns = Curl_cshutdn_dest_count(cshutdn, to_add->destination);
523
0
      if(shutdowns && ((live + shutdowns) >= max_host)) {
524
0
        size_t over = live + shutdowns - max_host + 1;
525
0
        shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin,
526
0
                                               to_add->destination, over);
527
0
      }
528
0
    }
529
    /* We may not have gotten rid of as many connections as we wanted to */
530
0
    if((live + shutdowns) >= max_host)
531
0
      return CPOOL_LIMIT_DEST;
532
0
  }
533
534
  /* Internal transfers are outside the total limit */
535
0
  if(max_total && !data->state.internal) {
536
0
    size_t shutdowns = 0;
537
0
    uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
538
539
0
    if(!admin)
540
0
      admin = Curl_get_admin(data);
541
0
    if(num_conns >= max_total) {
542
0
      size_t over = num_conns - max_total + 1;
543
0
      for(; over; --over) {
544
0
        struct connectdata *oldest_idle =
545
0
          cpool_get_oldest_idle(cpool, pnow, 0);
546
0
        if(!oldest_idle)
547
0
          break;
548
        /* disconnect the old conn and continue */
549
0
        CURL_TRC_M(admin, "Shutting down idle connection #%"
550
0
                   FMT_OFF_T " to '%s' due to total limit of %u",
551
0
                   oldest_idle->connection_id, oldest_idle->destination,
552
0
                   max_total);
553
0
        cpool_evict_conn(cpool, admin, oldest_idle);
554
0
      }
555
0
    }
556
557
0
    num_conns = Curl_uint32_tbl_count(&cpool->conns);
558
0
    if(cshutdn) {
559
0
      shutdowns = Curl_cshutdn_count(cshutdn);
560
0
      if(shutdowns && (num_conns + shutdowns) >= max_total) {
561
0
        size_t over = num_conns + shutdowns - max_total + 1;
562
0
        shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, NULL, over);
563
0
      }
564
0
    }
565
0
    if((num_conns + shutdowns) >= max_total)
566
0
      return CPOOL_LIMIT_TOTAL;
567
0
  }
568
569
0
  return CPOOL_LIMIT_OK;
570
0
}
571
572
CURLcode Curl_cpool_add(struct Curl_easy *data,
573
                        struct connectdata *conn,
574
                        uint32_t max_total,
575
                        uint32_t max_host,
576
                        const struct curltime *pnow)
577
10.0k
{
578
10.0k
  CURLcode result = CURLE_OK;
579
10.0k
  struct cpool_bundle *bundle = NULL;
580
10.0k
  struct cpool *cpool = cpool_get_instance(data);
581
10.0k
  uint32_t capacity, cpid;
582
10.0k
  DEBUGASSERT(conn);
583
584
10.0k
  DEBUGASSERT(cpool);
585
10.0k
  if(!cpool)
586
0
    return CURLE_FAILED_INIT;
587
588
10.0k
  DEBUGASSERT(conn->cpid == UINT32_MAX);
589
10.0k
  conn->created = *pnow;
590
10.0k
  conn->shutdown.start_ms[FIRSTSOCKET] =
591
10.0k
    conn->shutdown.start_ms[SECONDARYSOCKET] = -1;
592
593
10.0k
  CPOOL_LOCK(cpool, data);
594
  /* Find the bundle, should it exist, and check the limits */
595
10.0k
  bundle = cpool_get_bundle(cpool, conn->destination);
596
597
10.0k
  switch(cpool_check_limits(data, max_total, conn, max_host, bundle, pnow)) {
598
0
  case CPOOL_LIMIT_DEST:
599
0
    infof(data, "No more connections allowed to host");
600
0
    result = CURLE_NO_CONNECTION_AVAILABLE;
601
0
    goto out;
602
0
  case CPOOL_LIMIT_TOTAL:
603
0
    infof(data, "No connections available, total of %u reached.",
604
0
          data->multi->max_total_connections);
605
0
    result = CURLE_NO_CONNECTION_AVAILABLE;
606
0
    goto out;
607
10.0k
  default:
608
10.0k
    break;
609
10.0k
  }
610
611
  /* We add the connection */
612
10.0k
  capacity = Curl_uint32_tbl_capacity(&cpool->conns);
613
10.0k
  if(capacity == UINT32_MAX) {
614
0
    infof(data, "No connections available, pool at max capacity");
615
0
    result = CURLE_NO_CONNECTION_AVAILABLE;
616
0
    goto out;
617
0
  }
618
10.0k
  if(Curl_uint32_tbl_count(&cpool->conns) >= capacity) {
619
10.0k
    uint32_t growth, ncapacity;
620
621
10.0k
    growth = CURLMAX(capacity, 16);
622
10.0k
    if((UINT32_MAX - growth) >= capacity)
623
10.0k
      ncapacity = capacity + growth;
624
0
    else
625
0
      ncapacity = UINT32_MAX;
626
    /* Grow bitset first, so table resize failure does not leave a too
627
     * short bitset should that one fail. */
628
10.0k
    if(Curl_uint32_bset_resize(&cpool->idles, ncapacity) ||
629
10.0k
       Curl_uint32_tbl_resize(&cpool->conns, ncapacity)) {
630
5
      result = CURLE_OUT_OF_MEMORY;
631
5
      goto out;
632
5
    }
633
10.0k
  }
634
635
10.0k
  if(!bundle) {
636
10.0k
    bundle = cpool_create_bundle();
637
10.0k
    if(!bundle) {
638
1
      result = CURLE_OUT_OF_MEMORY;
639
1
      goto out;
640
1
    }
641
10.0k
  }
642
643
10.0k
  if(!Curl_uint32_tbl_add(&cpool->conns, conn, &cpid)) {
644
0
    DEBUGASSERT(0);
645
0
    result = CURLE_OUT_OF_MEMORY;
646
0
    goto out;
647
0
  }
648
10.0k
  conn->cpid = cpid;
649
650
10.0k
  if(!cpool_bundle_add_id(cpool, bundle, conn->destination, cpid)) {
651
6
    Curl_uint32_tbl_remove(&cpool->conns, cpid);
652
6
    conn->cpid = UINT32_MAX;
653
6
    result = CURLE_OUT_OF_MEMORY;
654
6
    goto out;
655
6
  }
656
657
10.0k
  conn->connection_id = cpool->next_connection_id++;
658
10.0k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
659
10.0k
             "The cache now contains %u members",
660
10.0k
             conn->connection_id, Curl_uint32_tbl_count(&cpool->conns));
661
10.0k
out:
662
10.0k
  cpool_bundle_unlink(&bundle);
663
10.0k
  CPOOL_UNLOCK(cpool, data);
664
665
10.0k
  return result;
666
10.0k
}
667
668
/* This function iterates the entire connection pool and calls the function
669
   func() with the connection pointer as the first argument and the supplied
670
   'param' argument as the other.
671
672
   The cpool lock is still held when the callback is called. It needs it,
673
   so that it can safely continue traversing the lists once the callback
674
   returns.
675
676
   Returns TRUE if the loop was aborted due to the callback's return code.
677
678
   Return 0 from func() to continue the loop, return 1 to abort it.
679
 */
680
static bool cpool_foreach(struct Curl_easy *data,
681
                          struct cpool *cpool,
682
                          void *param,
683
                          int (*func)(struct cpool *cpool,
684
                                      struct Curl_easy *data,
685
                                      struct connectdata *conn, void *param))
686
9.49k
{
687
9.49k
  void *entry;
688
9.49k
  uint32_t cpid;
689
690
9.49k
  if(!cpool)
691
0
    return FALSE;
692
693
9.49k
  if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) {
694
0
    do {
695
0
      struct connectdata *conn = entry;
696
0
      if(conn && func(cpool, data, conn, param) == 1)
697
0
        return TRUE;
698
0
    } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry));
699
0
  }
700
9.49k
  return FALSE;
701
9.49k
}
702
703
/*
704
 * A connection (already in the pool) has become idle. Do any
705
 * cleanups in regard to the pool's limits.
706
 *
707
 * Return TRUE if idle connection kept in pool, FALSE if closed.
708
 */
709
static bool cpool_conn_now_idle(struct cpool *cpool,
710
                                struct Curl_easy *data,
711
                                struct connectdata *conn,
712
                                const struct curltime *pnow)
713
572
{
714
572
  struct connectdata *oldest_idle = NULL;
715
572
  struct Curl_easy *admin;
716
572
  uint32_t maxconnects;
717
572
  bool kept = TRUE;
718
572
  timediff_t min_age_ms = 0;
719
720
572
  if(!data || !data->multi)
721
0
    return kept;
722
723
572
  Curl_uint32_bset_add(&cpool->idles, conn->cpid);
724
725
572
  if(!data->multi->maxconnects) {
726
    /* Attached transfers is a weak indicator of business. */
727
572
    uint32_t attached = Curl_multi_xfers_attached(data->multi);
728
572
    maxconnects = (attached <= UINT32_MAX / 2) ? attached * 2 : UINT32_MAX;
729
    /* We are guessing. So, only evict a "seemingly superfluous" connection
730
     * when has not been used for this long, */
731
572
    min_age_ms = 1000;
732
572
  }
733
0
  else {
734
0
    maxconnects = data->multi->maxconnects;
735
0
  }
736
737
572
  if(cpool && maxconnects && (maxconnects < UINT32_MAX)) {
738
572
    uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
739
572
    admin = Curl_get_admin(data);
740
572
    if(num_conns > maxconnects) {
741
0
      infof(data, "Connection pool is full, closing the oldest of %u/%u",
742
0
            num_conns, maxconnects);
743
0
      oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms);
744
0
      kept = (oldest_idle != conn);
745
0
      if(oldest_idle) {
746
0
        cpool_evict_conn(cpool, admin, oldest_idle);
747
0
      }
748
0
    }
749
572
  }
750
751
572
  return kept;
752
572
}
753
754
static void cpool_prune_dead(struct cpool *cpool,
755
                             struct Curl_easy *data,
756
                             const struct curltime *pnow);
757
758
static bool cpool_find_act(struct cpool *cpool,
759
                           struct Curl_easy *data,
760
                           struct connectdata *conn,
761
                           cpool_match_result match)
762
0
{
763
0
  switch(match) {
764
0
  case CPOOL_MATCH_FOUND:
765
0
    return TRUE;
766
0
  case CPOOL_MATCH_TOO_OLD:
767
0
    if(!cpool_conn_can_be_closed(conn))
768
0
      return FALSE;
769
0
    FALLTHROUGH();
770
0
  case CPOOL_MATCH_CLOSE:
771
0
    cpool_conn_close(cpool, data, conn, FALSE);
772
0
    break;
773
0
  default:
774
0
    break;
775
0
  }
776
0
  return FALSE;
777
0
}
778
779
bool Curl_cpool_find(struct Curl_easy *data,
780
                     const char *destination,
781
                     bool prune_dead,
782
                     const struct curltime *pnow,
783
                     Curl_cpool_conn_match_cb *conn_cb,
784
                     Curl_cpool_done_match_cb *done_cb,
785
                     void *userdata)
786
9.49k
{
787
9.49k
  struct cpool *cpool = cpool_get_instance(data);
788
9.49k
  struct cpool_bundle *bundle = NULL;
789
9.49k
  bool found = FALSE;
790
791
9.49k
  DEBUGASSERT(cpool);
792
9.49k
  DEBUGASSERT(conn_cb);
793
9.49k
  if(!cpool)
794
0
    return FALSE;
795
796
9.49k
  CPOOL_LOCK(cpool, data);
797
798
9.49k
  if(prune_dead)
799
9.49k
    cpool_prune_dead(cpool, data, pnow);
800
801
9.49k
  if(data->state.last_cpid != UINT32_MAX) {
802
0
    struct connectdata *conn =
803
0
      Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid);
804
0
    if(conn && (data->state.last_conn_id == conn->connection_id)) {
805
      /* Indeed the last connection used by `data` still exists.
806
       * Check that first, so that connection use gets "sticky". */
807
0
      found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata));
808
0
      if(found)
809
0
        goto out;
810
0
    }
811
0
    else {
812
      /* No longer found here. */
813
0
      data->state.last_conn_id = -1;
814
0
      data->state.last_cpid = UINT32_MAX;
815
0
    }
816
0
  }
817
818
9.49k
  bundle = cpool_get_bundle(cpool, destination);
819
9.49k
  if(bundle) {
820
0
    uint32_t cpid;
821
0
    if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) {
822
0
      do {
823
0
        struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid);
824
0
        if(conn) {
825
0
          if(data->state.last_conn_id != conn->connection_id) {
826
0
            found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata));
827
0
            if(found)
828
0
              goto out;
829
0
          }
830
0
        }
831
0
        else {
832
0
          DEBUGASSERT(0);
833
0
          Curl_uint32_spbset_remove(&bundle->ids, cpid);
834
0
        }
835
0
      } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid));
836
0
    }
837
0
  }
838
839
9.49k
out:
840
9.49k
  if(done_cb) {
841
9.49k
    found = done_cb(userdata);
842
9.49k
  }
843
9.49k
  cpool_bundle_unlink(&bundle);
844
9.49k
  CPOOL_UNLOCK(cpool, data);
845
9.49k
  return found;
846
9.49k
}
847
848
struct cpool_reaper_ctx {
849
  size_t reaped;
850
  const struct curltime *pnow;
851
};
852
853
static int cpool_reap_dead_cb(struct cpool *cpool,
854
                              struct Curl_easy *admin,
855
                              struct connectdata *conn, void *param)
856
0
{
857
0
  struct cpool_reaper_ctx *reaper = param;
858
859
0
  if(cpool_conn_can_be_closed(conn)) {
860
0
    if(conn->bits.no_reuse || conn->bits.close ||
861
0
       !Curl_cpool_conn_seems_healthy(conn, admin, reaper->pnow)) {
862
      /* terminate conn and stop the iteration */
863
0
      reaper->reaped++;
864
0
      cpool_conn_close(cpool, admin, conn, FALSE);
865
0
    }
866
0
  }
867
0
  return 0; /* continue iteration */
868
0
}
869
870
/*
871
 * This function scans the data's connection pool for half-open/dead
872
 * connections, closes and removes them.
873
 * The cleanup is done at most once per second.
874
 */
875
static void cpool_prune_dead(struct cpool *cpool,
876
                             struct Curl_easy *data,
877
                             const struct curltime *pnow)
878
9.49k
{
879
9.49k
  timediff_t elapsed_ms = curlx_ptimediff_ms(pnow, &cpool->last_cleanup);
880
881
9.49k
  if(elapsed_ms >= 1000L) {
882
9.49k
    struct Curl_easy *admin = Curl_get_admin(data);
883
9.49k
    struct cpool_reaper_ctx reaper;
884
885
9.49k
    memset(&reaper, 0, sizeof(reaper));
886
9.49k
    reaper.pnow = pnow;
887
9.49k
    cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb);
888
9.49k
    cpool->last_cleanup = *pnow;
889
9.49k
  }
890
9.49k
}
891
892
static int conn_upkeep(struct cpool *cpool,
893
                       struct Curl_easy *admin,
894
                       struct connectdata *conn,
895
                       void *param)
896
0
{
897
0
  const struct curltime *pnow = Curl_pgrs_now(admin);
898
899
0
  (void)param;
900
0
  if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastupkeep_ms) >=
901
0
     admin->set.upkeep_interval_ms) {
902
0
    CURLcode result;
903
904
0
    conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created);
905
    /* briefly attach for action */
906
0
    Curl_attach_connection(admin, conn, FALSE);
907
0
    result = Curl_conn_keep_alive(admin, conn);
908
0
    Curl_detach_connection(admin);
909
910
0
    if(result && cpool_conn_can_be_closed(conn)) {
911
0
      cpool_conn_close(cpool, admin, conn, FALSE);
912
0
    }
913
0
  }
914
0
  return 0; /* continue iteration */
915
0
}
916
917
CURLcode Curl_cpool_upkeep(struct Curl_easy *data)
918
0
{
919
0
  struct cpool *cpool = cpool_get_instance(data);
920
0
  struct Curl_easy *admin = Curl_get_admin(data);
921
922
0
  if(!cpool)
923
0
    return CURLE_OK;
924
925
0
  CPOOL_LOCK(cpool, admin);
926
0
  cpool_foreach(admin, cpool, NULL, conn_upkeep);
927
0
  CPOOL_UNLOCK(cpool, admin);
928
0
  return CURLE_OK;
929
0
}
930
931
struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data)
932
24.0k
{
933
24.0k
  struct cpool *cpool = cpool_get_instance(data);
934
24.0k
  if(cpool && (data->state.last_cpid != UINT32_MAX)) {
935
11.0k
    struct connectdata *conn;
936
937
11.0k
    CPOOL_LOCK(cpool, data);
938
11.0k
    conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid);
939
11.0k
    CPOOL_UNLOCK(cpool, data);
940
11.0k
    if(conn && (data->state.last_conn_id == conn->connection_id))
941
11.0k
      return conn;
942
28
    data->state.last_cpid = UINT32_MAX;
943
28
    data->state.last_conn_id = -1;
944
28
  }
945
13.0k
  return NULL;
946
24.0k
}
947
948
void Curl_cpool_return(struct Curl_easy *data,
949
                       struct connectdata *conn,
950
                       Curl_cpool_return_cb *cb, void *cbdata,
951
                       const struct curltime *pnow)
952
10.0k
{
953
10.0k
  struct cpool *cpool = cpool_get_instance(data);
954
955
10.0k
  CPOOL_LOCK(cpool, data);
956
957
  /* remember times, connection had been used just before */
958
10.0k
  conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms =
959
10.0k
    curlx_ptimediff_ms(pnow, &conn->created);
960
961
10.0k
  switch(cb(data, conn, cbdata, pnow)) {
962
0
  case CPOOL_DO_KEEP:
963
0
    break;
964
572
  case CPOOL_DO_IDLE:
965
    /* the connection is no longer in use by any transfer */
966
572
    if(cpool_conn_now_idle(cpool, data, conn, pnow)) {
967
      /* connection kept in the cpool */
968
572
      infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact",
969
572
            conn->connection_id, conn->origin->user_hostname,
970
572
            conn->origin->port);
971
572
    }
972
0
    else { /* connection was removed from the cpool and destroyed. */
973
0
      data->state.last_conn_id = -1;
974
0
      data->state.last_cpid = UINT32_MAX;
975
0
    }
976
572
    break;
977
788
  case CPOOL_DO_CLOSE:
978
788
    Curl_conn_close(data, conn, FALSE);
979
788
    break;
980
8.70k
  case CPOOL_DO_TERMINATE:
981
8.70k
    Curl_conn_close(data, conn, TRUE);
982
8.70k
    break;
983
10.0k
  }
984
985
10.0k
  CPOOL_UNLOCK(cpool, data);
986
10.0k
}
987
988
static int cpool_reap_no_reuse(struct cpool *cpool,
989
                               struct Curl_easy *admin,
990
                               struct connectdata *conn, void *param)
991
0
{
992
0
  (void)param;
993
0
  conn->bits.no_reuse = TRUE;
994
0
  if(cpool_conn_can_be_closed(conn))
995
0
    cpool_conn_close(cpool, admin, conn, FALSE);
996
0
  return 0; /* continue iteration */
997
0
}
998
999
void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin)
1000
0
{
1001
0
  if(cpool && admin) {
1002
0
    CPOOL_LOCK(cpool, admin);
1003
0
    cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse);
1004
0
    CPOOL_UNLOCK(cpool, admin);
1005
0
  }
1006
0
}
1007
1008
/* A connection has to have been idle for less than 'conn_max_idle_ms'
1009
   (the success rate is too low after this), or created less than
1010
   'conn_max_age_ms' ago, to be subject for reuse. */
1011
static bool cpool_conn_too_old(struct Curl_easy *data,
1012
                               struct connectdata *conn,
1013
                               const struct curltime *pnow)
1014
0
{
1015
0
  timediff_t age_ms;
1016
1017
0
  if(data->set.conn_max_idle_ms) {
1018
0
    age_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
1019
0
    if(age_ms > data->set.conn_max_idle_ms) {
1020
0
      infof(data, "Too old connection (%" FMT_TIMEDIFF_T
1021
0
            " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it",
1022
0
            age_ms, data->set.conn_max_idle_ms);
1023
0
      return TRUE;
1024
0
    }
1025
0
  }
1026
1027
0
  if(data->set.conn_max_age_ms) {
1028
0
    age_ms = curlx_ptimediff_ms(pnow, &conn->created);
1029
0
    if(age_ms > data->set.conn_max_age_ms) {
1030
0
      infof(data,
1031
0
            "Too old connection (created %" FMT_TIMEDIFF_T
1032
0
            " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it",
1033
0
            age_ms, data->set.conn_max_age_ms);
1034
0
      return TRUE;
1035
0
    }
1036
0
  }
1037
1038
0
  return FALSE;
1039
0
}
1040
1041
bool Curl_cpool_conn_seems_healthy(struct connectdata *conn,
1042
                                   struct Curl_easy *data,
1043
                                   const struct curltime *pnow)
1044
0
{
1045
0
  struct Curl_easy *admin;
1046
0
  bool healthy = TRUE;
1047
0
  bool poll_input;
1048
1049
0
  DEBUGASSERT(!data->conn);
1050
0
  if(cpool_conn_can_be_closed(conn) && cpool_conn_too_old(data, conn, pnow))
1051
0
    return FALSE;
1052
1053
  /* An idle, non-multiplexed connection must not be reused when it has
1054
   * input pending (RFC 9112, section 9.2), so that is polled on every
1055
   * reuse. The once a second limit only applies to the other checks. */
1056
0
  poll_input = cpool_conn_can_be_closed(conn) &&
1057
0
    !Curl_conn_is_multiplex(conn, FIRSTSOCKET) &&
1058
0
    !conn->scheme->run->connection_is_dead;
1059
0
  if(!poll_input &&
1060
0
     (curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000)
1061
0
    return TRUE;
1062
1063
0
  admin = Curl_get_admin(data);
1064
0
  if(conn->scheme->run->connection_is_dead) {
1065
0
    Curl_attach_connection(admin, conn, FALSE);
1066
0
    healthy = !conn->scheme->run->connection_is_dead(admin, conn);
1067
0
    Curl_detach_connection(admin);
1068
0
  }
1069
0
  else {
1070
0
    bool input_pending = FALSE;
1071
1072
0
    Curl_attach_connection(admin, conn, FALSE);
1073
0
    healthy = Curl_conn_is_alive(admin, conn, &input_pending);
1074
0
    Curl_detach_connection(admin);
1075
0
    if(healthy && input_pending && poll_input) {
1076
      /* Non-multiplexed connections without attached transfers should
1077
       * not have input pending. The input might be a TLS Notify Close,
1078
       * for all we know. */
1079
0
      DEBUGF(infof(data, "connection has no transfer but input, not healthy"));
1080
0
      healthy = FALSE;
1081
0
    }
1082
0
  }
1083
1084
0
  if(healthy)
1085
0
    conn->lastchecked_ms = curlx_ptimediff_ms(pnow, &conn->created);
1086
0
  return healthy;
1087
0
}
1088
1089
void Curl_cpool_conn_was_used(struct Curl_easy *data,
1090
                              struct connectdata *conn,
1091
                              const struct curltime *pnow)
1092
9.90k
{
1093
9.90k
  (void)data;
1094
9.90k
  conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created);
1095
9.90k
}
1096
1097
timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data,
1098
                                  struct connectdata *conn,
1099
                                  const struct curltime *pnow)
1100
0
{
1101
0
  (void)data;
1102
0
  return curlx_ptimediff_ms(pnow, &conn->created);
1103
0
}