Coverage Report

Created: 2026-10-22 06:52

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
4.17k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
41
42
#define CPOOL_LOCK(c, d)                                                \
43
34.2k
  do {                                                                  \
44
34.2k
    if(c) {                                                             \
45
34.2k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
46
34.2k
        Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT,  \
47
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
48
34.2k
      DEBUGASSERT(!(c)->locked);                                        \
49
34.2k
      (c)->locked = TRUE;                                               \
50
34.2k
    }                                                                   \
51
34.2k
  } while(0)
52
53
#define CPOOL_UNLOCK(c, d)                                              \
54
34.2k
  do {                                                                  \
55
34.2k
    if(c) {                                                             \
56
34.2k
      DEBUGASSERT((c)->locked);                                         \
57
34.2k
      (c)->locked = FALSE;                                              \
58
34.2k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
59
34.2k
        Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \
60
34.2k
    }                                                                   \
61
34.2k
  } 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
25.0k
{
71
25.0k
  if(*pbundle) {
72
12.5k
    struct cpool_bundle *bundle = *pbundle;
73
74
12.5k
    DEBUGASSERT(bundle->refcount);
75
12.5k
    *pbundle = NULL;
76
12.5k
    if(bundle->refcount)
77
12.5k
      bundle->refcount--;
78
12.5k
    if(!bundle->refcount) {
79
4.17k
      Curl_uint32_spbset_destroy(&bundle->ids);
80
4.17k
      curlx_free(bundle);
81
4.17k
    }
82
12.5k
  }
83
25.0k
}
84
85
static void cpool_bundle_link(struct cpool_bundle **pdest,
86
                              struct cpool_bundle *src)
87
16.6k
{
88
16.6k
  if(*pdest != src) {
89
8.34k
    cpool_bundle_unlink(pdest);
90
8.34k
    *pdest = src;
91
8.34k
    if(src) {
92
8.34k
      DEBUGASSERT(src->refcount < UINT32_MAX);
93
8.34k
      src->refcount++;
94
8.34k
    }
95
8.34k
  }
96
16.6k
}
97
98
static struct cpool_bundle *cpool_create_bundle(void)
99
4.17k
{
100
  /* created with refcount == 1 */
101
4.17k
  struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle));
102
4.17k
  if(!bundle)
103
0
    return NULL;
104
105
4.17k
  Curl_uint32_spbset_init(&bundle->ids);
106
4.17k
  bundle->refcount = 1;
107
4.17k
  return bundle;
108
4.17k
}
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
4.17k
{
116
4.17k
  if(!Curl_uint32_spbset_add(&bundle->ids, pool_id))
117
0
    return FALSE;
118
4.17k
  if(Curl_uint32_spbset_count(&bundle->ids) == 1) {
119
    /* first one added, register bundle */
120
4.17k
    struct cpool_bundle *new_ref = NULL;
121
4.17k
    cpool_bundle_link(&new_ref, bundle);
122
4.17k
    if(!Curl_hash_add(&cpool->dest2bundle,
123
4.17k
                      destination, strlen(destination), new_ref)) {
124
0
      cpool_bundle_unlink(&new_ref);
125
0
      return FALSE;
126
0
    }
127
4.17k
  }
128
4.17k
  return TRUE;
129
4.17k
}
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
4.17k
{
137
4.17k
  Curl_uint32_spbset_remove(&bundle->ids, pool_id);
138
4.17k
  if(!Curl_uint32_spbset_count(&bundle->ids)) {
139
    /* Now empty, unregister bundle */
140
4.17k
    Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination));
141
4.17k
  }
142
4.17k
}
143
144
static void cpool_bundle_hash_dtor(void *entry)
145
4.17k
{
146
4.17k
  struct cpool_bundle *bundle = entry;
147
4.17k
  cpool_bundle_unlink(&bundle);
148
4.17k
}
149
150
static struct cpool_bundle *cpool_get_bundle(struct cpool *cpool,
151
                                             const char *destination)
152
12.4k
{
153
12.4k
  struct cpool_bundle *bundle = NULL;
154
12.4k
  cpool_bundle_link(&bundle, Curl_hash_pick(
155
12.4k
    &cpool->dest2bundle, destination, strlen(destination)));
156
12.4k
  return bundle;
157
12.4k
}
158
159
static void cpool_remove_conn(struct cpool *cpool,
160
                              struct connectdata *conn)
161
4.17k
{
162
4.17k
  uint32_t cpid = conn->cpid;
163
164
4.17k
  DEBUGASSERT(cpool);
165
4.17k
  if(cpid != UINT32_MAX) {
166
4.17k
    struct cpool_bundle *bundle;
167
168
4.17k
#ifdef DEBUGBUILD
169
4.17k
    {
170
4.17k
      struct connectdata *pooled_conn =
171
4.17k
        Curl_uint32_tbl_get(&cpool->conns, cpid);
172
4.17k
      DEBUGASSERT(conn == pooled_conn);
173
4.17k
    }
174
4.17k
#endif
175
4.17k
    Curl_uint32_tbl_remove(&cpool->conns, cpid);
176
4.17k
    Curl_uint32_bset_remove(&cpool->idles, cpid);
177
4.17k
    conn->cpid = UINT32_MAX;
178
179
4.17k
    bundle = cpool_get_bundle(cpool, conn->destination);
180
4.17k
    DEBUGASSERT(bundle);
181
4.17k
    if(bundle)
182
4.17k
      cpool_bundle_remove_id(cpool, bundle, conn->destination, cpid);
183
4.17k
    cpool_bundle_unlink(&bundle);
184
4.17k
  }
185
4.17k
}
186
187
static void cpool_discard_conn(struct cpool *cpool,
188
                               struct Curl_easy *data,
189
                               struct connectdata *conn,
190
                               bool aborted)
191
4.17k
{
192
4.17k
  struct Curl_easy *admin;
193
4.17k
  bool done = FALSE;
194
195
4.17k
  DEBUGASSERT(data);
196
4.17k
  DEBUGASSERT(!data->conn);
197
4.17k
  DEBUGASSERT(cpool);
198
4.17k
  DEBUGASSERT(conn->cpid == UINT32_MAX);
199
200
4.17k
  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
4.17k
  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
4.17k
  if(conn->bits.connect_only)
214
19
    aborted = TRUE;
215
4.17k
  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
4.17k
  if(aborted)
223
2.42k
    done = TRUE;
224
4.17k
  if(!done) {
225
    /* Attempt to shutdown the connection right away. */
226
1.74k
    Curl_cshutdn_try_once(admin, conn, &done);
227
1.74k
  }
228
229
4.17k
  if(done || !data->multi)
230
4.17k
    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
4.17k
}
245
246
void Curl_cpool_init(struct cpool *cpool,
247
                     struct Curl_share *share,
248
                     size_t size)
249
10.8k
{
250
10.8k
  Curl_uint32_tbl_init(&cpool->conns);
251
10.8k
  Curl_uint32_bset_init(&cpool->idles);
252
10.8k
  Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES,
253
10.8k
                 cpool_bundle_hash_dtor);
254
10.8k
  cpool->share = share;
255
10.8k
  cpool->initialized = TRUE;
256
10.8k
  cpool->in_shutdown = FALSE;
257
10.8k
  cpool->locked = FALSE;
258
10.8k
}
259
260
void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin)
261
10.8k
{
262
10.8k
  if(cpool && cpool->initialized && admin) {
263
10.8k
    void *entry;
264
10.8k
    uint32_t cpid;
265
266
10.8k
    CURL_TRC_M(admin, "%s[CPOOL] destroy, %u connections",
267
10.8k
               cpool->share ? "[SHARE] " : "",
268
10.8k
               Curl_uint32_tbl_count(&cpool->conns));
269
    /* Move all connections to the shutdown list */
270
10.8k
    CPOOL_LOCK(cpool, admin);
271
10.8k
    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
10.8k
    CPOOL_UNLOCK(cpool, admin);
284
10.8k
    Curl_hash_destroy(&cpool->dest2bundle);
285
10.8k
    Curl_uint32_bset_destroy(&cpool->idles);
286
10.8k
    Curl_uint32_tbl_destroy(&cpool->conns);
287
10.8k
  }
288
10.8k
}
289
290
static struct cpool *cpool_get_instance(struct Curl_easy *data)
291
31.7k
{
292
  /* admin handles do not necessarily find the correct pool */
293
31.7k
  DEBUGASSERT(data->mid);
294
31.7k
  if(CURL_SHARE_KEEP_CONNECT(data->share))
295
0
    return &data->share->cpool;
296
31.7k
  else if(data->multi_easy)
297
0
    return &data->multi_easy->cpool;
298
31.7k
  else if(data->multi)
299
31.7k
    return &data->multi->cpool;
300
0
  return NULL;
301
31.7k
}
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
10.8k
{
310
10.8k
  struct cpool *cpool = cpool_get_instance(data);
311
312
10.8k
  if(cpool) {
313
10.8k
    CPOOL_LOCK(cpool, data);
314
    /* the identifier inside the connection cache */
315
10.8k
    data->id = cpool->next_easy_id++;
316
10.8k
    if(cpool->next_easy_id == CURL_OFF_T_MAX)
317
0
      cpool->next_easy_id = 0;
318
10.8k
    data->state.last_conn_id = -1;
319
10.8k
    data->state.last_cpid = UINT32_MAX;
320
321
10.8k
    CPOOL_UNLOCK(cpool, data);
322
10.8k
  }
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
10.8k
}
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
4.17k
{
402
4.17k
  struct Curl_easy *admin;
403
4.17k
  bool do_lock;
404
405
4.17k
  DEBUGASSERT(cpool);
406
4.17k
  DEBUGASSERT(data && !data->conn);
407
4.17k
  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
4.17k
  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
4.17k
  admin = Curl_get_admin(data);
421
4.17k
  do_lock = !CPOOL_IS_LOCKED(cpool);
422
4.17k
  if(do_lock)
423
11
    CPOOL_LOCK(cpool, admin);
424
425
4.17k
  if(conn->cpid != UINT32_MAX) {
426
4.17k
    cpool_remove_conn(cpool, conn);
427
4.17k
    DEBUGASSERT(conn->cpid == UINT32_MAX);
428
4.17k
  }
429
430
  /* treat the connection as aborted in CONNECT_ONLY situations,
431
   * so no graceful shutdown is attempted. */
432
4.17k
  if(conn->bits.connect_only)
433
19
    aborted = TRUE;
434
435
4.17k
  if(data->multi) {
436
    /* Add it to the multi's cpool for shutdown handling */
437
4.17k
    infof(data, "%s connection #%" FMT_OFF_T,
438
4.17k
          aborted ? "closing" : "shutting down", conn->connection_id);
439
4.17k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
440
4.17k
  }
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
4.17k
  if(do_lock)
448
11
    CPOOL_UNLOCK(cpool, admin);
449
4.17k
}
450
451
void Curl_conn_close(struct Curl_easy *data,
452
                     struct connectdata *conn,
453
                     bool aborted)
454
4.17k
{
455
4.17k
  struct cpool *cpool = cpool_get_instance(data);
456
4.17k
  cpool_conn_close(cpool, data, conn, aborted);
457
4.17k
}
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
4.17k
#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
4.17k
{
486
4.17k
  struct cpool *cpool = cpool_get_instance(data);
487
4.17k
  struct cshutdn *cshutdn = data->multi ? &data->multi->cshutdn : NULL;
488
4.17k
  struct Curl_easy *admin = NULL;
489
490
4.17k
  if(!cpool)
491
0
    return CPOOL_LIMIT_OK;
492
493
4.17k
  if(!max_total && !max_host)
494
4.17k
    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
4.17k
{
578
4.17k
  CURLcode result = CURLE_OK;
579
4.17k
  struct cpool_bundle *bundle = NULL;
580
4.17k
  struct cpool *cpool = cpool_get_instance(data);
581
4.17k
  uint32_t capacity, cpid;
582
4.17k
  DEBUGASSERT(conn);
583
584
4.17k
  DEBUGASSERT(cpool);
585
4.17k
  if(!cpool)
586
0
    return CURLE_FAILED_INIT;
587
588
4.17k
  DEBUGASSERT(conn->cpid == UINT32_MAX);
589
4.17k
  conn->created = *pnow;
590
4.17k
  conn->shutdown.start_ms[FIRSTSOCKET] =
591
4.17k
    conn->shutdown.start_ms[SECONDARYSOCKET] = -1;
592
593
4.17k
  CPOOL_LOCK(cpool, data);
594
  /* Find the bundle, should it exist, and check the limits */
595
4.17k
  bundle = cpool_get_bundle(cpool, conn->destination);
596
597
4.17k
  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
4.17k
  default:
608
4.17k
    break;
609
4.17k
  }
610
611
  /* We add the connection */
612
4.17k
  capacity = Curl_uint32_tbl_capacity(&cpool->conns);
613
4.17k
  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
4.17k
  if(Curl_uint32_tbl_count(&cpool->conns) >= capacity) {
619
4.17k
    uint32_t growth, ncapacity;
620
621
4.17k
    growth = CURLMAX(capacity, 16);
622
4.17k
    if((UINT32_MAX - growth) >= capacity)
623
4.17k
      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
4.17k
    if(Curl_uint32_bset_resize(&cpool->idles, ncapacity) ||
629
4.17k
       Curl_uint32_tbl_resize(&cpool->conns, ncapacity)) {
630
0
      result = CURLE_OUT_OF_MEMORY;
631
0
      goto out;
632
0
    }
633
4.17k
  }
634
635
4.17k
  if(!bundle) {
636
4.17k
    bundle = cpool_create_bundle();
637
4.17k
    if(!bundle) {
638
0
      result = CURLE_OUT_OF_MEMORY;
639
0
      goto out;
640
0
    }
641
4.17k
  }
642
643
4.17k
  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
4.17k
  conn->cpid = cpid;
649
650
4.17k
  if(!cpool_bundle_add_id(cpool, bundle, conn->destination, cpid)) {
651
0
    Curl_uint32_tbl_remove(&cpool->conns, cpid);
652
0
    conn->cpid = UINT32_MAX;
653
0
    result = CURLE_OUT_OF_MEMORY;
654
0
    goto out;
655
0
  }
656
657
4.17k
  conn->connection_id = cpool->next_connection_id++;
658
4.17k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
659
4.17k
             "The cache now contains %u members",
660
4.17k
             conn->connection_id, Curl_uint32_tbl_count(&cpool->conns));
661
4.17k
out:
662
4.17k
  cpool_bundle_unlink(&bundle);
663
4.17k
  CPOOL_UNLOCK(cpool, data);
664
665
4.17k
  return result;
666
4.17k
}
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
4.14k
{
687
4.14k
  void *entry;
688
4.14k
  uint32_t cpid;
689
690
4.14k
  if(!cpool)
691
0
    return FALSE;
692
693
4.14k
  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
4.14k
  return FALSE;
701
4.14k
}
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
11
{
714
11
  struct connectdata *oldest_idle = NULL;
715
11
  struct Curl_easy *admin;
716
11
  uint32_t maxconnects;
717
11
  bool kept = TRUE;
718
11
  timediff_t min_age_ms = 0;
719
720
11
  if(!data || !data->multi)
721
0
    return kept;
722
723
11
  Curl_uint32_bset_add(&cpool->idles, conn->cpid);
724
725
11
  if(!data->multi->maxconnects) {
726
    /* Attached transfers is a weak indicator of business. */
727
11
    uint32_t attached = Curl_multi_xfers_attached(data->multi);
728
11
    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
11
    min_age_ms = 1000;
732
11
  }
733
0
  else {
734
0
    maxconnects = data->multi->maxconnects;
735
0
  }
736
737
11
  if(cpool && maxconnects && (maxconnects < UINT32_MAX)) {
738
11
    uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns);
739
11
    admin = Curl_get_admin(data);
740
11
    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
11
  }
750
751
11
  return kept;
752
11
}
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
4.14k
{
787
4.14k
  struct cpool *cpool = cpool_get_instance(data);
788
4.14k
  struct cpool_bundle *bundle = NULL;
789
4.14k
  bool found = FALSE;
790
791
4.14k
  DEBUGASSERT(cpool);
792
4.14k
  DEBUGASSERT(conn_cb);
793
4.14k
  if(!cpool)
794
0
    return FALSE;
795
796
4.14k
  CPOOL_LOCK(cpool, data);
797
798
4.14k
  if(prune_dead)
799
4.14k
    cpool_prune_dead(cpool, data, pnow);
800
801
4.14k
  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
4.14k
  bundle = cpool_get_bundle(cpool, destination);
819
4.14k
  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
4.14k
out:
840
4.14k
  if(done_cb) {
841
4.14k
    found = done_cb(userdata);
842
4.14k
  }
843
4.14k
  cpool_bundle_unlink(&bundle);
844
4.14k
  CPOOL_UNLOCK(cpool, data);
845
4.14k
  return found;
846
4.14k
}
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
4.14k
{
879
4.14k
  timediff_t elapsed_ms = curlx_ptimediff_ms(pnow, &cpool->last_cleanup);
880
881
4.14k
  if(elapsed_ms >= 1000L) {
882
4.14k
    struct Curl_easy *admin = Curl_get_admin(data);
883
4.14k
    struct cpool_reaper_ctx reaper;
884
885
4.14k
    memset(&reaper, 0, sizeof(reaper));
886
4.14k
    reaper.pnow = pnow;
887
4.14k
    cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb);
888
4.14k
    cpool->last_cleanup = *pnow;
889
4.14k
  }
890
4.14k
}
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
20
{
933
20
  struct cpool *cpool = cpool_get_instance(data);
934
20
  if(cpool && (data->state.last_cpid != UINT32_MAX)) {
935
19
    struct connectdata *conn;
936
937
19
    CPOOL_LOCK(cpool, data);
938
19
    conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid);
939
19
    CPOOL_UNLOCK(cpool, data);
940
19
    if(conn && (data->state.last_conn_id == conn->connection_id))
941
11
      return conn;
942
8
    data->state.last_cpid = UINT32_MAX;
943
8
    data->state.last_conn_id = -1;
944
8
  }
945
9
  return NULL;
946
20
}
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
4.17k
{
953
4.17k
  struct cpool *cpool = cpool_get_instance(data);
954
955
4.17k
  CPOOL_LOCK(cpool, data);
956
957
  /* remember times, connection had been used just before */
958
4.17k
  conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms =
959
4.17k
    curlx_ptimediff_ms(pnow, &conn->created);
960
961
4.17k
  switch(cb(data, conn, cbdata, pnow)) {
962
0
  case CPOOL_DO_KEEP:
963
0
    break;
964
11
  case CPOOL_DO_IDLE:
965
    /* the connection is no longer in use by any transfer */
966
11
    if(cpool_conn_now_idle(cpool, data, conn, pnow)) {
967
      /* connection kept in the cpool */
968
11
      infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact",
969
11
            conn->connection_id, conn->origin->user_hostname,
970
11
            conn->origin->port);
971
11
    }
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
11
    break;
977
1.74k
  case CPOOL_DO_CLOSE:
978
1.74k
    Curl_conn_close(data, conn, FALSE);
979
1.74k
    break;
980
2.41k
  case CPOOL_DO_TERMINATE:
981
2.41k
    Curl_conn_close(data, conn, TRUE);
982
2.41k
    break;
983
4.17k
  }
984
985
4.17k
  CPOOL_UNLOCK(cpool, data);
986
4.17k
}
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
1.93k
{
1093
1.93k
  (void)data;
1094
1.93k
  conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created);
1095
1.93k
}
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
}