Coverage Report

Created: 2026-09-14 07:12

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
38
39
763k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
40
41
#define CPOOL_LOCK(c, d)                                                \
42
3.98M
  do {                                                                  \
43
3.98M
    if(c) {                                                             \
44
3.98M
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
45
3.98M
        Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT,  \
46
651
                        CURL_LOCK_ACCESS_SINGLE);                       \
47
3.98M
      DEBUGASSERT(!(c)->locked);                                        \
48
3.98M
      (c)->locked = TRUE;                                               \
49
3.98M
    }                                                                   \
50
3.98M
  } while(0)
51
52
#define CPOOL_UNLOCK(c, d)                                              \
53
3.98M
  do {                                                                  \
54
3.98M
    if(c) {                                                             \
55
3.98M
      DEBUGASSERT((c)->locked);                                         \
56
3.98M
      (c)->locked = FALSE;                                              \
57
3.98M
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
58
3.98M
        Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \
59
3.98M
    }                                                                   \
60
3.98M
  } while(0)
61
62
/* A list of connections to the same destination. */
63
struct cpool_bundle {
64
  struct Curl_llist conns; /* connections in the bundle */
65
};
66
67
static struct cpool_bundle *cpool_bundle_create(void)
68
497k
{
69
497k
  struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle));
70
497k
  if(bundle)
71
497k
    Curl_llist_init(&bundle->conns, NULL);
72
497k
  return bundle;
73
497k
}
74
75
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
76
497k
{
77
497k
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
78
497k
  curlx_free(bundle);
79
497k
}
80
81
/* Add a connection to a bundle */
82
static void cpool_bundle_add(struct cpool_bundle *bundle,
83
                             struct connectdata *conn)
84
529k
{
85
529k
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
86
529k
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
87
529k
  conn->bits.in_cpool = TRUE;
88
529k
}
89
90
/* Remove a connection from a bundle */
91
static void cpool_bundle_remove(struct cpool_bundle *bundle,
92
                                struct connectdata *conn)
93
529k
{
94
529k
  (void)bundle;
95
529k
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
96
529k
  Curl_node_remove(&conn->cpool_node);
97
529k
  conn->bits.in_cpool = FALSE;
98
529k
}
99
100
static void cpool_bundle_free_entry(void *freethis)
101
497k
{
102
497k
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
103
497k
}
104
105
void Curl_cpool_init(struct cpool *cpool,
106
                     struct Curl_share *share,
107
                     size_t size)
108
508k
{
109
508k
  Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES,
110
508k
                 cpool_bundle_free_entry);
111
112
508k
  cpool->share = share;
113
508k
  cpool->initialized = TRUE;
114
508k
}
115
116
/* Return the "first" connection in the pool or NULL. */
117
static struct connectdata *cpool_get_first(struct cpool *cpool)
118
555k
{
119
555k
  struct Curl_hash_iterator iter;
120
555k
  struct Curl_hash_element *he;
121
555k
  struct cpool_bundle *bundle;
122
555k
  struct Curl_llist_node *conn_node;
123
124
555k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
125
555k
  for(he = Curl_hash_next_element(&iter); he;
126
555k
      he = Curl_hash_next_element(&iter)) {
127
47.1k
    bundle = he->ptr;
128
47.1k
    conn_node = Curl_llist_head(&bundle->conns);
129
47.1k
    if(conn_node)
130
47.1k
      return Curl_node_elem(conn_node);
131
47.1k
  }
132
508k
  return NULL;
133
555k
}
134
135
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
136
                                              const char *destination)
137
1.07M
{
138
1.07M
  return Curl_hash_pick(
139
1.07M
    &cpool->dest2bundle, destination, strlen(destination) + 1);
140
1.07M
}
141
142
static void cpool_remove_bundle(struct cpool *cpool,
143
                                const char *destination)
144
497k
{
145
497k
  if(!cpool)
146
0
    return;
147
497k
  Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination) + 1);
148
497k
}
149
150
static void cpool_remove_conn(struct cpool *cpool,
151
                              struct connectdata *conn)
152
529k
{
153
529k
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
154
529k
  DEBUGASSERT(cpool);
155
529k
  if(list) {
156
    /* The connection is certainly in the pool, but where? */
157
529k
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn->destination);
158
529k
    if(bundle && (list == &bundle->conns)) {
159
529k
      cpool_bundle_remove(bundle, conn);
160
529k
      if(!Curl_llist_count(&bundle->conns))
161
497k
        cpool_remove_bundle(cpool, conn->destination);
162
529k
      conn->bits.in_cpool = FALSE;
163
529k
      cpool->num_conn--;
164
529k
    }
165
0
    else {
166
      /* Should have been in the bundle list */
167
0
      DEBUGASSERT(NULL);
168
0
    }
169
529k
  }
170
529k
}
171
172
static void cpool_discard_conn(struct cpool *cpool,
173
                               struct Curl_easy *data,
174
                               struct connectdata *conn,
175
                               bool aborted)
176
529k
{
177
529k
  struct Curl_easy *admin;
178
529k
  bool done = FALSE;
179
180
529k
  DEBUGASSERT(data);
181
529k
  DEBUGASSERT(!data->conn);
182
529k
  DEBUGASSERT(cpool);
183
529k
  DEBUGASSERT(!conn->bits.in_cpool);
184
185
529k
  admin = Curl_get_admin(data);
186
  /*
187
   * If this connection is not marked to force-close, leave it open if there
188
   * are other users of it
189
   */
190
529k
  if(CONN_INUSE(conn) && !aborted) {
191
0
    CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T
192
0
               " still in use by %u transfers", conn->connection_id,
193
0
               conn->attached_xfers);
194
0
    return;
195
0
  }
196
197
  /* treat the connection as aborted in CONNECT_ONLY situations, we do
198
   * not know what the APP did with it. */
199
529k
  if(conn->bits.connect_only)
200
26.5k
    aborted = TRUE;
201
529k
  conn->bits.aborted = aborted;
202
203
  /* We do not shutdown dead connections. The term 'dead' can be misleading
204
   * here, as we also mark errored connections/transfers as 'dead'.
205
   * If we do a shutdown for an aborted transfer, the server might think
206
   * it was successful otherwise (for example an ftps: upload). This is
207
   * not what we want. */
208
529k
  if(aborted)
209
285k
    done = TRUE;
210
529k
  if(!done) {
211
    /* Attempt to shutdown the connection right away. */
212
244k
    Curl_cshutdn_try_once(admin, conn, &done);
213
244k
  }
214
215
529k
  if(done || !data->multi)
216
516k
    Curl_cshutdn_terminate(admin, conn, FALSE);
217
13.3k
  else {
218
13.3k
    struct Curl_multi *multi = data->multi;
219
13.3k
    size_t max_shutdowns = multi->max_total_connections;
220
13.3k
    if(cpool->num_conn < max_shutdowns)
221
84
      max_shutdowns -= cpool->num_conn;
222
13.2k
    else if(max_shutdowns)
223
0
      max_shutdowns = 1;
224
13.2k
    else /* no connection limit set, let's restrict growth nevertheless */
225
13.2k
      max_shutdowns = CURLMAX(cpool->num_conn / 4, 128);
226
13.3k
    Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns);
227
13.3k
  }
228
529k
}
229
230
void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin)
231
508k
{
232
508k
  if(cpool && cpool->initialized && admin) {
233
508k
    struct connectdata *conn;
234
508k
    struct Curl_sigpipe_ctx pipe_ctx;
235
236
508k
    CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections",
237
508k
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
238
    /* Move all connections to the shutdown list */
239
508k
    sigpipe_init(&pipe_ctx);
240
508k
    CPOOL_LOCK(cpool, admin);
241
508k
    conn = cpool_get_first(cpool);
242
508k
    if(conn)
243
45.5k
      sigpipe_apply(admin, &pipe_ctx);
244
555k
    while(conn) {
245
47.1k
      cpool_remove_conn(cpool, conn);
246
47.1k
      cpool_discard_conn(cpool, admin, conn, FALSE);
247
47.1k
      conn = cpool_get_first(cpool);
248
47.1k
    }
249
508k
    CPOOL_UNLOCK(cpool, admin);
250
508k
    sigpipe_restore(&pipe_ctx);
251
508k
    Curl_hash_destroy(&cpool->dest2bundle);
252
508k
  }
253
508k
}
254
255
static struct cpool *cpool_get_instance(struct Curl_easy *data)
256
4.96M
{
257
  /* admin handles do not necessarily find the correct pool */
258
4.96M
  DEBUGASSERT(data->mid);
259
4.96M
  if(CURL_SHARE_KEEP_CONNECT(data->share))
260
795
    return &data->share->cpool;
261
4.96M
  else if(data->multi_easy)
262
13.9k
    return &data->multi_easy->cpool;
263
4.95M
  else if(data->multi)
264
4.73M
    return &data->multi->cpool;
265
221k
  return NULL;
266
4.96M
}
267
268
struct cpool *Curl_cpool_get_instance(struct Curl_easy *data)
269
757k
{
270
757k
  return cpool_get_instance(data);
271
757k
}
272
273
void Curl_cpool_xfer_init(struct Curl_easy *data)
274
556k
{
275
556k
  struct cpool *cpool = cpool_get_instance(data);
276
277
556k
  if(cpool) {
278
556k
    CPOOL_LOCK(cpool, data);
279
    /* the identifier inside the connection cache */
280
556k
    data->id = cpool->next_easy_id++;
281
556k
    if(cpool->next_easy_id == CURL_OFF_T_MAX)
282
0
      cpool->next_easy_id = 0;
283
556k
    data->state.lastconnect_id = -1;
284
285
556k
    CPOOL_UNLOCK(cpool, data);
286
556k
  }
287
0
  else {
288
    /* We should not get here, but in a non-debug build, do something */
289
0
    DEBUGASSERT(0);
290
0
    data->id = 0;
291
0
    data->state.lastconnect_id = -1;
292
0
  }
293
556k
}
294
295
static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool,
296
                                             const char *destination)
297
497k
{
298
497k
  struct cpool_bundle *bundle;
299
300
497k
  bundle = cpool_bundle_create();
301
497k
  if(!bundle)
302
0
    return NULL;
303
304
497k
  if(!Curl_hash_add(&cpool->dest2bundle,
305
497k
                    destination, strlen(destination) + 1, bundle)) {
306
0
    cpool_bundle_destroy(bundle);
307
0
    return NULL;
308
0
  }
309
497k
  return bundle;
310
497k
}
311
312
static struct connectdata *cpool_bundle_get_oldest_idle(
313
  struct cpool_bundle *bundle,
314
  const struct curltime *pnow)
315
5.50k
{
316
5.50k
  struct Curl_llist_node *curr;
317
5.50k
  struct connectdata *oldest_idle = NULL;
318
5.50k
  timediff_t unused_ms;
319
5.50k
  timediff_t oldest_ms = -1;
320
5.50k
  struct connectdata *conn;
321
322
5.50k
  curr = Curl_llist_head(&bundle->conns);
323
11.4k
  while(curr) {
324
5.99k
    conn = Curl_node_elem(curr);
325
326
    /* CONNECT_ONLY sockets remain in use by the application. */
327
5.99k
    if(!CONN_INUSE(conn) && !conn->bits.close && !conn->bits.connect_only) {
328
      /* Set higher score for the age passed since the connection was used */
329
54
      unused_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
330
54
      if(unused_ms > oldest_ms) {
331
32
        oldest_ms = unused_ms;
332
32
        oldest_idle = conn;
333
32
      }
334
54
    }
335
5.99k
    curr = Curl_node_next(curr);
336
5.99k
  }
337
5.50k
  return oldest_idle;
338
5.50k
}
339
340
static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool,
341
                                                 const struct curltime *pnow,
342
                                                 timediff_t min_age_ms)
343
8.10k
{
344
8.10k
  struct Curl_hash_iterator iter;
345
8.10k
  struct Curl_llist_node *curr;
346
8.10k
  struct Curl_hash_element *he;
347
8.10k
  struct cpool_bundle *bundle;
348
8.10k
  struct connectdata *oldest_idle = NULL;
349
8.10k
  timediff_t oldest_idle_ms = -1;
350
8.10k
  timediff_t idle_ms;
351
352
8.10k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
353
354
16.6k
  for(he = Curl_hash_next_element(&iter); he;
355
8.51k
      he = Curl_hash_next_element(&iter)) {
356
8.51k
    struct connectdata *conn;
357
8.51k
    bundle = he->ptr;
358
359
19.8k
    for(curr = Curl_llist_head(&bundle->conns); curr;
360
11.3k
        curr = Curl_node_next(curr)) {
361
11.3k
      conn = Curl_node_elem(curr);
362
11.3k
      if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only)
363
7.80k
        continue;
364
3.53k
      idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
365
3.53k
      if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) {
366
59
        oldest_idle_ms = idle_ms;
367
59
        oldest_idle = conn;
368
59
      }
369
3.53k
    }
370
8.51k
  }
371
8.10k
  return oldest_idle;
372
8.10k
}
373
374
static void cpool_conn_close(struct cpool *cpool,
375
                             struct Curl_easy *data,
376
                             struct connectdata *conn,
377
                             bool aborted)
378
482k
{
379
482k
  struct Curl_easy *admin;
380
482k
  bool do_lock;
381
382
482k
  DEBUGASSERT(cpool);
383
482k
  DEBUGASSERT(data && !data->conn);
384
482k
  if(!cpool)
385
0
    return;
386
387
  /* If this connection is not marked to force-close, leave it open if there
388
   * are other users of it */
389
482k
  if(CONN_INUSE(conn) && !aborted) {
390
0
    DEBUGASSERT(0); /* does this ever happen? */
391
0
    DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers));
392
0
    return;
393
0
  }
394
395
  /* This method may be called while we are under lock, e.g. from a
396
   * user callback in find. */
397
482k
  admin = Curl_get_admin(data);
398
482k
  do_lock = !CPOOL_IS_LOCKED(cpool);
399
482k
  if(do_lock)
400
6.00k
    CPOOL_LOCK(cpool, admin);
401
402
482k
  if(conn->bits.in_cpool) {
403
482k
    cpool_remove_conn(cpool, conn);
404
482k
    DEBUGASSERT(!conn->bits.in_cpool);
405
482k
  }
406
407
  /* treat the connection as aborted in CONNECT_ONLY situations,
408
   * so no graceful shutdown is attempted. */
409
482k
  if(conn->bits.connect_only)
410
25.0k
    aborted = TRUE;
411
412
482k
  if(data->multi) {
413
    /* Add it to the multi's cpool for shutdown handling */
414
482k
    infof(data, "%s connection #%" FMT_OFF_T,
415
482k
          aborted ? "closing" : "shutting down", conn->connection_id);
416
482k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
417
482k
  }
418
0
  else {
419
    /* No multi available, terminate */
420
0
    infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
421
0
    Curl_cshutdn_terminate(admin, conn, !aborted);
422
0
  }
423
424
482k
  if(do_lock)
425
6.00k
    CPOOL_UNLOCK(cpool, admin);
426
482k
}
427
428
void Curl_conn_close(struct Curl_easy *data,
429
                     struct connectdata *conn,
430
                     bool aborted)
431
482k
{
432
482k
  struct cpool *cpool = cpool_get_instance(data);
433
482k
  cpool_conn_close(cpool, data, conn, aborted);
434
482k
}
435
436
/* Evict an idle connection to make room in the pool. A pool owned by
437
 * a share has no multi that could perform a controlled shutdown of the
438
 * connection; terminate it right away. Otherwise, hand it to the
439
 * transfer's multi for shutdown. Expects the pool to be locked. */
440
static void cpool_evict_conn(struct cpool *cpool,
441
                             struct Curl_easy *admin,
442
                             struct connectdata *conn)
443
91
{
444
91
  if(cpool->share) {
445
0
    cpool_remove_conn(cpool, conn);
446
0
    Curl_cshutdn_terminate(admin, conn, TRUE);
447
0
  }
448
91
  else
449
91
    cpool_conn_close(cpool, admin, conn, FALSE);
450
91
}
451
452
int Curl_cpool_check_limits(struct Curl_easy *data,
453
                            struct connectdata *conn,
454
                            const struct curltime *pnow)
455
540k
{
456
540k
  struct cpool *cpool = cpool_get_instance(data);
457
540k
  struct Curl_multi *multi = data->multi;
458
540k
  struct Curl_easy *admin;
459
540k
  struct cpool_bundle *bundle;
460
540k
  size_t dest_limit = 0;
461
540k
  size_t total_limit = 0;
462
540k
  size_t shutdowns;
463
540k
  int res = CPOOL_LIMIT_OK;
464
465
540k
  if(!cpool)
466
0
    return CPOOL_LIMIT_OK;
467
468
  /* multi determines the limits, no matter who owns the pool */
469
540k
  if(multi) {
470
540k
    dest_limit = multi->max_host_connections;
471
540k
    total_limit = multi->max_total_connections;
472
540k
  }
473
474
540k
  if(!dest_limit && !total_limit)
475
512k
    return CPOOL_LIMIT_OK;
476
477
27.8k
  admin = Curl_get_admin(data);
478
27.8k
  CPOOL_LOCK(cpool, admin);
479
27.8k
  if(dest_limit) {
480
17.5k
    size_t live;
481
482
17.5k
    bundle = cpool_find_bundle(cpool, conn->destination);
483
17.5k
    live = bundle ? Curl_llist_count(&bundle->conns) : 0;
484
17.5k
    shutdowns =
485
17.5k
      multi ? Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0;
486
17.5k
    while((live + shutdowns) >= dest_limit) {
487
5.52k
      if(shutdowns) {
488
        /* close one connection in shutdown right away, if we can */
489
25
        if(!Curl_cshutdn_close_oldest(&multi->cshutdn, multi->admin,
490
25
                                      conn->destination))
491
0
          break;
492
25
      }
493
5.50k
      else if(!bundle)
494
0
        break;
495
5.50k
      else {
496
5.50k
        struct connectdata *oldest_idle = NULL;
497
        /* The bundle is full. Extract the oldest connection that may
498
         * be removed now, if there is one. */
499
5.50k
        oldest_idle = cpool_bundle_get_oldest_idle(bundle, pnow);
500
5.50k
        if(!oldest_idle)
501
5.47k
          break;
502
        /* disconnect the old conn and continue */
503
32
        CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T
504
32
                   " from %zu to reach destination limit of %zu",
505
32
                   oldest_idle->connection_id,
506
32
                   Curl_llist_count(&bundle->conns), dest_limit);
507
32
        cpool_evict_conn(cpool, admin, oldest_idle);
508
509
        /* in case the bundle was destroyed in disconnect, look it up again */
510
32
        bundle = cpool_find_bundle(cpool, conn->destination);
511
32
        live = bundle ? Curl_llist_count(&bundle->conns) : 0;
512
32
      }
513
57
      shutdowns = multi ?
514
57
        Curl_cshutdn_dest_count(&multi->cshutdn, conn->destination) : 0;
515
57
    }
516
17.5k
    if((live + shutdowns) >= dest_limit) {
517
5.47k
      res = CPOOL_LIMIT_DEST;
518
5.47k
      goto out;
519
5.47k
    }
520
17.5k
  }
521
522
22.3k
  if(total_limit) {
523
17.5k
    shutdowns = multi ? Curl_cshutdn_count(&multi->cshutdn) : 0;
524
17.5k
    while((cpool->num_conn + shutdowns) >= total_limit) {
525
6.88k
      if(shutdowns) {
526
        /* close one connection in shutdown right away, if we can */
527
14
        if(!Curl_cshutdn_close_oldest(&multi->cshutdn, multi->admin, NULL))
528
0
          break;
529
14
      }
530
6.86k
      else {
531
6.86k
        struct connectdata *oldest_idle =
532
6.86k
          cpool_get_oldest_idle(cpool, pnow, 0);
533
6.86k
        if(!oldest_idle)
534
6.83k
          break;
535
        /* disconnect the old conn and continue */
536
27
        CURL_TRC_M(admin, "Discarding connection #%"
537
27
                   FMT_OFF_T " from %zu to reach total "
538
27
                   "limit of %zu",
539
27
                   oldest_idle->connection_id, cpool->num_conn, total_limit);
540
27
        cpool_evict_conn(cpool, admin, oldest_idle);
541
27
      }
542
41
      shutdowns = multi ? Curl_cshutdn_count(&multi->cshutdn) : 0;
543
41
    }
544
17.5k
    if((cpool->num_conn + shutdowns) >= total_limit) {
545
6.83k
      res = CPOOL_LIMIT_TOTAL;
546
6.83k
      goto out;
547
6.83k
    }
548
17.5k
  }
549
550
27.8k
out:
551
27.8k
  CPOOL_UNLOCK(cpool, admin);
552
27.8k
  return res;
553
27.8k
}
554
555
CURLcode Curl_cpool_add(struct Curl_easy *data,
556
                        struct connectdata *conn,
557
                        const struct curltime *pnow)
558
529k
{
559
529k
  CURLcode result = CURLE_OK;
560
529k
  struct cpool_bundle *bundle = NULL;
561
529k
  struct cpool *cpool = cpool_get_instance(data);
562
529k
  DEBUGASSERT(conn);
563
564
529k
  DEBUGASSERT(cpool);
565
529k
  if(!cpool)
566
0
    return CURLE_FAILED_INIT;
567
568
529k
  conn->created = *pnow;
569
529k
  conn->shutdown.start_ms[FIRSTSOCKET] =
570
529k
    conn->shutdown.start_ms[SECONDARYSOCKET] = -1;
571
572
529k
  CPOOL_LOCK(cpool, data);
573
529k
  bundle = cpool_find_bundle(cpool, conn->destination);
574
529k
  if(!bundle) {
575
497k
    bundle = cpool_add_bundle(cpool, conn->destination);
576
497k
    if(!bundle) {
577
0
      result = CURLE_OUT_OF_MEMORY;
578
0
      goto out;
579
0
    }
580
497k
  }
581
582
529k
  cpool_bundle_add(bundle, conn);
583
529k
  conn->connection_id = cpool->next_connection_id++;
584
529k
  cpool->num_conn++;
585
529k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
586
529k
             "The cache now contains %zu members",
587
529k
             conn->connection_id, cpool->num_conn);
588
529k
out:
589
529k
  CPOOL_UNLOCK(cpool, data);
590
591
529k
  return result;
592
529k
}
593
594
/* This function iterates the entire connection pool and calls the function
595
   func() with the connection pointer as the first argument and the supplied
596
   'param' argument as the other.
597
598
   The cpool lock is still held when the callback is called. It needs it,
599
   so that it can safely continue traversing the lists once the callback
600
   returns.
601
602
   Returns TRUE if the loop was aborted due to the callback's return code.
603
604
   Return 0 from func() to continue the loop, return 1 to abort it.
605
 */
606
static bool cpool_foreach(struct Curl_easy *data,
607
                          struct cpool *cpool,
608
                          void *param,
609
                          int (*func)(struct cpool *cpool,
610
                                      struct Curl_easy *data,
611
                                      struct connectdata *conn, void *param))
612
451k
{
613
451k
  struct Curl_hash_iterator iter;
614
451k
  struct Curl_hash_element *he;
615
616
451k
  if(!cpool)
617
0
    return FALSE;
618
619
451k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
620
621
451k
  he = Curl_hash_next_element(&iter);
622
460k
  while(he) {
623
47.8k
    struct Curl_llist_node *curr;
624
47.8k
    struct cpool_bundle *bundle = he->ptr;
625
47.8k
    he = Curl_hash_next_element(&iter);
626
627
47.8k
    curr = Curl_llist_head(&bundle->conns);
628
58.2k
    while(curr) {
629
      /* Yes, we need to update curr before calling func(), because func()
630
         might decide to remove the connection */
631
48.6k
      struct connectdata *conn = Curl_node_elem(curr);
632
48.6k
      curr = Curl_node_next(curr);
633
634
48.6k
      if(func(cpool, data, conn, param) == 1) {
635
38.3k
        return TRUE;
636
38.3k
      }
637
48.6k
    }
638
47.8k
  }
639
412k
  return FALSE;
640
451k
}
641
642
/*
643
 * A connection (already in the pool) has become idle. Do any
644
 * cleanups in regard to the pool's limits.
645
 *
646
 * Return TRUE if idle connection kept in pool, FALSE if closed.
647
 */
648
bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
649
                              struct connectdata *conn,
650
                              const struct curltime *pnow)
651
280k
{
652
280k
  struct cpool *cpool = cpool_get_instance(data);
653
280k
  struct connectdata *oldest_idle = NULL;
654
280k
  struct Curl_easy *admin;
655
280k
  unsigned int maxconnects;
656
280k
  bool kept = TRUE;
657
280k
  timediff_t min_age_ms = 0;
658
659
280k
  if(!data || !data->multi)
660
0
    return kept;
661
662
280k
  if(!data->multi->maxconnects) {
663
    /* Attached transfers is a weak indicator of business. */
664
277k
    uint32_t attached = Curl_multi_xfers_attached(data->multi);
665
277k
    maxconnects = (attached <= UINT_MAX / 2) ? attached * 2 : UINT_MAX;
666
    /* We are guessing. So, only evict a "seemingly superfluous" connection
667
     * when has not been used for this long, */
668
277k
    min_age_ms = 1000;
669
277k
  }
670
2.88k
  else {
671
2.88k
    maxconnects = data->multi->maxconnects;
672
2.88k
  }
673
674
  /* remember times, connection had been used just before */
675
280k
  conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms =
676
280k
    curlx_ptimediff_ms(pnow, &conn->created);
677
280k
  if(cpool && maxconnects) {
678
    /* may be called form a callback already under lock */
679
280k
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
680
681
280k
    admin = Curl_get_admin(data);
682
280k
    if(do_lock)
683
0
      CPOOL_LOCK(cpool, admin);
684
280k
    if(cpool->num_conn > maxconnects) {
685
1.23k
      infof(data, "Connection pool is full, closing the oldest of %zu/%u",
686
1.23k
            cpool->num_conn, maxconnects);
687
688
1.23k
      oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms);
689
1.23k
      kept = (oldest_idle != conn);
690
1.23k
      if(oldest_idle) {
691
32
        cpool_evict_conn(cpool, admin, oldest_idle);
692
32
      }
693
1.23k
    }
694
280k
    if(do_lock)
695
0
      CPOOL_UNLOCK(cpool, admin);
696
280k
  }
697
698
280k
  return kept;
699
280k
}
700
701
bool Curl_cpool_find(struct Curl_easy *data,
702
                     const char *destination,
703
                     Curl_cpool_conn_match_cb *conn_cb,
704
                     Curl_cpool_done_match_cb *done_cb,
705
                     void *userdata)
706
757k
{
707
757k
  struct cpool *cpool = cpool_get_instance(data);
708
757k
  struct cpool_bundle *bundle;
709
757k
  struct Curl_llist_node *curr;
710
757k
  struct connectdata *conn;
711
757k
  bool found = FALSE;
712
713
757k
  DEBUGASSERT(cpool);
714
757k
  DEBUGASSERT(conn_cb);
715
757k
  if(!cpool)
716
0
    return FALSE;
717
718
757k
  CPOOL_LOCK(cpool, data);
719
757k
  bundle = Curl_hash_pick(&cpool->dest2bundle,
720
757k
                          destination, strlen(destination) + 1);
721
757k
  if(bundle) {
722
287k
    if(data->state.lastconnect_id >= 0) {
723
      /* Try to find the previously used connection in this bundle
724
       * and if it still matches, use that one. This assures that
725
       * an authentication involving several requests is using
726
       * the same connection again. */
727
235k
      curr = Curl_llist_head(&bundle->conns);
728
245k
      while(curr) {
729
240k
        conn = Curl_node_elem(curr);
730
240k
        curr = Curl_node_next(curr);
731
240k
        if(data->state.lastconnect_id == conn->connection_id) {
732
231k
          if(conn_cb(conn, userdata))
733
221k
            found = TRUE;
734
231k
          break;
735
231k
        }
736
240k
      }
737
235k
    }
738
739
287k
    if(!found) {
740
66.3k
      curr = Curl_llist_head(&bundle->conns);
741
121k
      while(curr) {
742
78.2k
        conn = Curl_node_elem(curr);
743
78.2k
        curr = Curl_node_next(curr);
744
        /* Already tried a matching connection above. No need to
745
         * invoke callback again for this one. */
746
78.2k
        if((data->state.lastconnect_id != conn->connection_id) &&
747
68.3k
           conn_cb(conn, userdata)) {
748
23.5k
          found = TRUE;
749
23.5k
          break;
750
23.5k
        }
751
78.2k
      }
752
66.3k
    }
753
287k
  }
754
755
757k
  if(done_cb) {
756
757k
    found = done_cb(userdata);
757
757k
  }
758
757k
  CPOOL_UNLOCK(cpool, data);
759
757k
  return found;
760
757k
}
761
762
struct cpool_reaper_ctx {
763
  size_t reaped;
764
  struct curltime now;
765
};
766
767
static int cpool_reap_dead_cb(struct cpool *cpool,
768
                              struct Curl_easy *admin,
769
                              struct connectdata *conn, void *param)
770
419
{
771
419
  struct cpool_reaper_ctx *reaper = param;
772
773
419
  if(!CONN_INUSE(conn)) {
774
378
    if(conn->bits.no_reuse || conn->bits.close ||
775
378
       !Curl_cpool_conn_seems_healthy(conn, admin, &reaper->now)) {
776
      /* terminate conn and stop the iteration */
777
0
      reaper->reaped++;
778
0
      cpool_conn_close(cpool, admin, conn, FALSE);
779
0
      return 1;
780
0
    }
781
378
  }
782
419
  return 0; /* continue iteration */
783
419
}
784
785
/*
786
 * This function scans the data's connection pool for half-open/dead
787
 * connections, closes and removes them.
788
 * The cleanup is done at most once per second.
789
 *
790
 * When called, this transfer has no connection attached.
791
 */
792
void Curl_cpool_prune_dead(struct cpool *cpool,
793
                           struct Curl_easy *data)
794
757k
{
795
757k
  struct Curl_easy *admin;
796
757k
  timediff_t elapsed;
797
798
757k
  if(!cpool)
799
0
    return;
800
801
757k
  admin = Curl_get_admin(data);
802
757k
  CPOOL_LOCK(cpool, admin);
803
757k
  elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup);
804
805
757k
  if(elapsed >= 1000L) {
806
385k
    struct cpool_reaper_ctx reaper;
807
808
385k
    memset(&reaper, 0, sizeof(reaper));
809
385k
    reaper.now = *Curl_pgrs_now(admin);
810
385k
    while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb))
811
0
      ;
812
385k
    cpool->last_cleanup = *Curl_pgrs_now(admin);
813
385k
  }
814
757k
  CPOOL_UNLOCK(cpool, admin);
815
757k
}
816
817
static int conn_upkeep(struct cpool *cpool,
818
                       struct Curl_easy *admin,
819
                       struct connectdata *conn,
820
                       void *param)
821
8.45k
{
822
8.45k
  const struct curltime *pnow = Curl_pgrs_now(admin);
823
824
8.45k
  (void)param;
825
8.45k
  if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastupkeep_ms) >=
826
8.45k
     admin->set.upkeep_interval_ms) {
827
8.30k
    CURLcode result;
828
829
8.30k
    conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created);
830
    /* briefly attach for action */
831
8.30k
    Curl_attach_connection(admin, conn, FALSE);
832
8.30k
    result = Curl_conn_keep_alive(admin, conn);
833
8.30k
    Curl_detach_connection(admin);
834
835
8.30k
    if(result && !CONN_INUSE(conn)) {
836
0
      cpool_conn_close(cpool, admin, conn, FALSE);
837
0
      return 1;
838
0
    }
839
8.30k
  }
840
8.45k
  return 0; /* continue iteration */
841
8.45k
}
842
843
CURLcode Curl_cpool_upkeep(struct Curl_easy *data)
844
11.9k
{
845
11.9k
  struct cpool *cpool = cpool_get_instance(data);
846
11.9k
  struct Curl_easy *admin = Curl_get_admin(data);
847
848
11.9k
  if(!cpool)
849
0
    return CURLE_OK;
850
851
11.9k
  CPOOL_LOCK(cpool, admin);
852
11.9k
  while(cpool_foreach(admin, cpool, NULL, conn_upkeep))
853
0
    ;
854
11.9k
  CPOOL_UNLOCK(cpool, admin);
855
11.9k
  return CURLE_OK;
856
11.9k
}
857
858
struct cpool_find_ctx {
859
  curl_off_t id;
860
  struct connectdata *conn;
861
};
862
863
static int cpool_find_conn(struct cpool *cpool,
864
                           struct Curl_easy *data,
865
                           struct connectdata *conn, void *param)
866
39.7k
{
867
39.7k
  struct cpool_find_ctx *fctx = param;
868
39.7k
  (void)cpool;
869
39.7k
  (void)data;
870
39.7k
  if(conn->connection_id == fctx->id) {
871
38.3k
    fctx->conn = conn;
872
38.3k
    return 1;
873
38.3k
  }
874
1.45k
  return 0;
875
39.7k
}
876
877
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
878
                                        curl_off_t conn_id)
879
275k
{
880
275k
  struct cpool *cpool = cpool_get_instance(data);
881
275k
  struct cpool_find_ctx fctx;
882
883
275k
  if(!cpool)
884
221k
    return NULL;
885
53.9k
  fctx.id = conn_id;
886
53.9k
  fctx.conn = NULL;
887
53.9k
  CPOOL_LOCK(cpool, data);
888
53.9k
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
889
53.9k
  CPOOL_UNLOCK(cpool, data);
890
53.9k
  return fctx.conn;
891
53.9k
}
892
893
void Curl_cpool_do_locked(struct Curl_easy *data,
894
                          struct connectdata *conn,
895
                          Curl_cpool_conn_do_cb *cb, void *cbdata)
896
774k
{
897
774k
  struct cpool *cpool = cpool_get_instance(data);
898
774k
  if(cpool) {
899
774k
    CPOOL_LOCK(cpool, data);
900
774k
    cb(conn, data, cbdata);
901
774k
    CPOOL_UNLOCK(cpool, data);
902
774k
  }
903
0
  else
904
0
    cb(conn, data, cbdata);
905
774k
}
906
907
static int cpool_mark_stale(struct cpool *cpool,
908
                            struct Curl_easy *admin,
909
                            struct connectdata *conn, void *param)
910
0
{
911
0
  (void)cpool;
912
0
  (void)admin;
913
0
  (void)param;
914
0
  conn->bits.no_reuse = TRUE;
915
0
  return 0;
916
0
}
917
918
static int cpool_reap_no_reuse(struct cpool *cpool,
919
                               struct Curl_easy *admin,
920
                               struct connectdata *conn, void *param)
921
0
{
922
0
  (void)param;
923
0
  if(!CONN_INUSE(conn) && conn->bits.no_reuse) {
924
0
    cpool_conn_close(cpool, admin, conn, FALSE);
925
0
    return 1;
926
0
  }
927
0
  return 0; /* continue iteration */
928
0
}
929
930
void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin)
931
0
{
932
0
  if(cpool && admin) {
933
0
    CPOOL_LOCK(cpool, admin);
934
0
    cpool_foreach(admin, cpool, NULL, cpool_mark_stale);
935
0
    while(cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse))
936
0
      ;
937
0
    CPOOL_UNLOCK(cpool, admin);
938
0
  }
939
0
}
940
941
/* A connection has to have been idle for less than 'conn_max_idle_ms'
942
   (the success rate is too low after this), or created less than
943
   'conn_max_age_ms' ago, to be subject for reuse. */
944
static bool cpool_conn_maxage(struct Curl_easy *data,
945
                              struct connectdata *conn,
946
                              const struct curltime *pnow)
947
227k
{
948
227k
  timediff_t age_ms;
949
950
227k
  if(data->set.conn_max_idle_ms) {
951
226k
    age_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms;
952
226k
    if(age_ms > data->set.conn_max_idle_ms) {
953
0
      infof(data, "Too old connection (%" FMT_TIMEDIFF_T
954
0
            " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it",
955
0
            age_ms, data->set.conn_max_idle_ms);
956
0
      return TRUE;
957
0
    }
958
226k
  }
959
960
227k
  if(data->set.conn_max_age_ms) {
961
226k
    age_ms = curlx_ptimediff_ms(pnow, &conn->created);
962
226k
    if(age_ms > data->set.conn_max_age_ms) {
963
0
      infof(data,
964
0
            "Too old connection (created %" FMT_TIMEDIFF_T
965
0
            " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it",
966
0
            age_ms, data->set.conn_max_age_ms);
967
0
      return TRUE;
968
0
    }
969
226k
  }
970
971
227k
  return FALSE;
972
227k
}
973
974
bool Curl_cpool_conn_seems_healthy(struct connectdata *conn,
975
                                   struct Curl_easy *data,
976
                                   const struct curltime *pnow)
977
227k
{
978
227k
  struct Curl_easy *admin;
979
227k
  bool healthy = TRUE;
980
981
227k
  DEBUGASSERT(!data->conn);
982
227k
  if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */
983
0
    return FALSE;
984
227k
  if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000)
985
227k
    return TRUE;
986
987
0
  admin = Curl_get_admin(data);
988
0
  if(conn->scheme->run->connection_is_dead) {
989
0
    Curl_attach_connection(admin, conn, FALSE);
990
0
    healthy = !conn->scheme->run->connection_is_dead(admin, conn);
991
0
    Curl_detach_connection(admin);
992
0
  }
993
0
  else {
994
0
    bool input_pending = FALSE;
995
996
0
    Curl_attach_connection(admin, conn, FALSE);
997
0
    healthy = Curl_conn_is_alive(admin, conn, &input_pending);
998
0
    Curl_detach_connection(admin);
999
0
    if(healthy && input_pending &&
1000
0
       !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) {
1001
      /* Non-multiplexed connections without attached transfers should
1002
       * not have input pending. The input might be a TLS Notify Close,
1003
       * for all we know. */
1004
0
      DEBUGF(infof(data, "connection has no transfer but input, not healthy"));
1005
0
      healthy = FALSE;
1006
0
    }
1007
0
  }
1008
1009
0
  if(healthy)
1010
0
    conn->lastchecked_ms = curlx_ptimediff_ms(pnow, &conn->created);
1011
0
  return healthy;
1012
227k
}
1013
1014
void Curl_cpool_conn_was_used(struct Curl_easy *data,
1015
                              struct connectdata *conn,
1016
                              const struct curltime *pnow)
1017
423k
{
1018
423k
  (void)data;
1019
423k
  conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created);
1020
423k
}
1021
1022
timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data,
1023
                                  struct connectdata *conn,
1024
                                  const struct curltime *pnow)
1025
242k
{
1026
242k
  (void)data;
1027
242k
  return curlx_ptimediff_ms(pnow, &conn->created);
1028
242k
}
1029
1030
#if 0
1031
/* Useful for debugging the connection pool */
1032
void Curl_cpool_print(struct cpool *cpool)
1033
{
1034
  struct Curl_hash_iterator iter;
1035
  struct Curl_llist_node *curr;
1036
  struct Curl_hash_element *he;
1037
1038
  if(!cpool)
1039
    return;
1040
1041
  curl_mfprintf(stderr, "=Bundle cache=\n");
1042
1043
  Curl_hash_start_iterate(cpool->dest2bundle, &iter);
1044
1045
  he = Curl_hash_next_element(&iter);
1046
  while(he) {
1047
    struct cpool_bundle *bundle;
1048
    struct connectdata *conn;
1049
1050
    bundle = he->ptr;
1051
1052
    curl_mfprintf(stderr, "%s -", he->key);
1053
    curr = Curl_llist_head(bundle->conns);
1054
    while(curr) {
1055
      conn = Curl_node_elem(curr);
1056
1057
      curl_mfprintf(stderr, " [%p %d]", (void *)conn, conn->refcount);
1058
      curr = Curl_node_next(curr);
1059
    }
1060
    curl_mfprintf(stderr, "\n");
1061
1062
    he = Curl_hash_next_element(&iter);
1063
  }
1064
}
1065
#endif