Coverage Report

Created: 2026-09-01 06:58

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