Coverage Report

Created: 2026-10-28 06:38

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