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
5.19k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
40
41
#define CPOOL_LOCK(c, d)                                                \
42
42.2k
  do {                                                                  \
43
42.2k
    if(c) {                                                             \
44
42.2k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
45
42.2k
        Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT,  \
46
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
47
42.2k
      DEBUGASSERT(!(c)->locked);                                        \
48
42.2k
      (c)->locked = TRUE;                                               \
49
42.2k
    }                                                                   \
50
42.2k
  } while(0)
51
52
#define CPOOL_UNLOCK(c, d)                                              \
53
42.2k
  do {                                                                  \
54
42.2k
    if(c) {                                                             \
55
42.2k
      DEBUGASSERT((c)->locked);                                         \
56
42.2k
      (c)->locked = FALSE;                                              \
57
42.2k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
58
42.2k
        Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \
59
42.2k
    }                                                                   \
60
42.2k
  } 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
5.18k
{
71
5.18k
  struct cpool_bundle *bundle;
72
5.18k
  size_t dest_len = strlen(dest) + 1;
73
74
5.18k
  bundle = curlx_calloc(1, sizeof(*bundle) + dest_len - 1);
75
5.18k
  if(!bundle)
76
0
    return NULL;
77
5.18k
  Curl_llist_init(&bundle->conns, NULL);
78
5.18k
  bundle->dest_len = dest_len;
79
5.18k
  memcpy(bundle->dest, dest, bundle->dest_len);
80
5.18k
  return bundle;
81
5.18k
}
82
83
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
84
5.18k
{
85
5.18k
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
86
5.18k
  curlx_free(bundle);
87
5.18k
}
88
89
/* Add a connection to a bundle */
90
static void cpool_bundle_add(struct cpool_bundle *bundle,
91
                             struct connectdata *conn)
92
5.18k
{
93
5.18k
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
94
5.18k
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
95
5.18k
  conn->bits.in_cpool = TRUE;
96
5.18k
}
97
98
/* Remove a connection from a bundle */
99
static void cpool_bundle_remove(struct cpool_bundle *bundle,
100
                                struct connectdata *conn)
101
5.18k
{
102
5.18k
  (void)bundle;
103
5.18k
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
104
5.18k
  Curl_node_remove(&conn->cpool_node);
105
5.18k
  conn->bits.in_cpool = FALSE;
106
5.18k
}
107
108
static void cpool_bundle_free_entry(void *freethis)
109
5.18k
{
110
5.18k
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
111
5.18k
}
112
113
void Curl_cpool_init(struct cpool *cpool,
114
                     struct Curl_share *share,
115
                     size_t size)
116
10.7k
{
117
10.7k
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
118
10.7k
                 curlx_str_key_compare, cpool_bundle_free_entry);
119
120
10.7k
  cpool->share = share;
121
10.7k
  cpool->initialized = TRUE;
122
10.7k
}
123
124
/* Return the "first" connection in the pool or NULL. */
125
static struct connectdata *cpool_get_first(struct cpool *cpool)
126
10.7k
{
127
10.7k
  struct Curl_hash_iterator iter;
128
10.7k
  struct Curl_hash_element *he;
129
10.7k
  struct cpool_bundle *bundle;
130
10.7k
  struct Curl_llist_node *conn_node;
131
132
10.7k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
133
10.7k
  for(he = Curl_hash_next_element(&iter); he;
134
10.7k
      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
10.7k
  return NULL;
141
10.7k
}
142
143
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
144
                                              struct connectdata *conn)
145
10.3k
{
146
10.3k
  return Curl_hash_pick(&cpool->dest2bundle,
147
10.3k
                        conn->destination, strlen(conn->destination) + 1);
148
10.3k
}
149
150
static void cpool_remove_bundle(struct cpool *cpool,
151
                                struct cpool_bundle *bundle)
152
5.18k
{
153
5.18k
  if(!cpool)
154
0
    return;
155
5.18k
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
156
5.18k
}
157
158
static void cpool_remove_conn(struct cpool *cpool,
159
                              struct connectdata *conn)
160
5.18k
{
161
5.18k
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
162
5.18k
  DEBUGASSERT(cpool);
163
5.18k
  if(list) {
164
    /* The connection is certainly in the pool, but where? */
165
5.18k
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
166
5.18k
    if(bundle && (list == &bundle->conns)) {
167
5.18k
      cpool_bundle_remove(bundle, conn);
168
5.18k
      if(!Curl_llist_count(&bundle->conns))
169
5.18k
        cpool_remove_bundle(cpool, bundle);
170
5.18k
      conn->bits.in_cpool = FALSE;
171
5.18k
      cpool->num_conn--;
172
5.18k
    }
173
0
    else {
174
      /* Should have been in the bundle list */
175
0
      DEBUGASSERT(NULL);
176
0
    }
177
5.18k
  }
178
5.18k
}
179
180
static void cpool_discard_conn(struct cpool *cpool,
181
                               struct Curl_easy *data,
182
                               struct connectdata *conn,
183
                               bool aborted)
184
5.18k
{
185
5.18k
  struct cshutdn *cshutdn;
186
5.18k
  struct Curl_easy *admin;
187
5.18k
  bool done = FALSE;
188
189
5.18k
  DEBUGASSERT(data);
190
5.18k
  DEBUGASSERT(!data->conn);
191
5.18k
  DEBUGASSERT(cpool);
192
5.18k
  DEBUGASSERT(!conn->bits.in_cpool);
193
194
5.18k
  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
5.18k
  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
5.18k
  if(conn->bits.connect_only)
209
15
    aborted = TRUE;
210
5.18k
  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
5.18k
  if(aborted)
218
4.23k
    done = TRUE;
219
5.18k
  if(!done) {
220
    /* Attempt to shutdown the connection right away. */
221
955
    Curl_conn_shutdown_once(admin, conn, &done);
222
955
  }
223
224
5.18k
  cshutdn = Curl_cshutdn_get(data);
225
5.18k
  if(done || !cshutdn)
226
5.18k
    Curl_conn_terminate(admin, conn, FALSE);
227
0
  else
228
0
    Curl_cshutdn_add(cshutdn, conn, cpool->num_conn);
229
5.18k
}
230
231
void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin)
232
10.7k
{
233
10.7k
  if(cpool && cpool->initialized && admin) {
234
10.7k
    struct connectdata *conn;
235
10.7k
    struct Curl_sigpipe_ctx pipe_ctx;
236
237
10.7k
    CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections",
238
10.7k
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
239
    /* Move all connections to the shutdown list */
240
10.7k
    sigpipe_init(&pipe_ctx);
241
10.7k
    CPOOL_LOCK(cpool, admin);
242
10.7k
    conn = cpool_get_first(cpool);
243
10.7k
    if(conn)
244
0
      sigpipe_apply(admin, &pipe_ctx);
245
10.7k
    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
10.7k
    CPOOL_UNLOCK(cpool, admin);
251
10.7k
    sigpipe_restore(&pipe_ctx);
252
10.7k
    Curl_hash_destroy(&cpool->dest2bundle);
253
10.7k
  }
254
10.7k
}
255
256
static struct cpool *cpool_get_instance(struct Curl_easy *data)
257
41.8k
{
258
  /* admin handles do not necessarily find the correct pool */
259
41.8k
  DEBUGASSERT(data->mid);
260
41.8k
  if(CURL_SHARE_KEEP_CONNECT(data->share))
261
0
    return &data->share->cpool;
262
41.8k
  else if(data->multi_easy)
263
0
    return &data->multi_easy->cpool;
264
41.8k
  else if(data->multi)
265
41.8k
    return &data->multi->cpool;
266
0
  return NULL;
267
41.8k
}
268
269
struct cpool *Curl_cpool_get_instance(struct Curl_easy *data)
270
5.16k
{
271
5.16k
  return cpool_get_instance(data);
272
5.16k
}
273
274
void Curl_cpool_xfer_init(struct Curl_easy *data)
275
10.7k
{
276
10.7k
  struct cpool *cpool = cpool_get_instance(data);
277
278
10.7k
  if(cpool) {
279
10.7k
    CPOOL_LOCK(cpool, data);
280
    /* the identifier inside the connection cache */
281
10.7k
    data->id = cpool->next_easy_id++;
282
10.7k
    if(cpool->next_easy_id == CURL_OFF_T_MAX)
283
0
      cpool->next_easy_id = 0;
284
10.7k
    data->state.lastconnect_id = -1;
285
286
10.7k
    CPOOL_UNLOCK(cpool, data);
287
10.7k
  }
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
10.7k
}
295
296
static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool,
297
                                             struct connectdata *conn)
298
5.18k
{
299
5.18k
  struct cpool_bundle *bundle;
300
301
5.18k
  bundle = cpool_bundle_create(conn->destination);
302
5.18k
  if(!bundle)
303
0
    return NULL;
304
305
5.18k
  if(!Curl_hash_add(&cpool->dest2bundle,
306
5.18k
                    bundle->dest, bundle->dest_len, bundle)) {
307
0
    cpool_bundle_destroy(bundle);
308
0
    return NULL;
309
0
  }
310
5.18k
  return bundle;
311
5.18k
}
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
5.18k
{
380
5.18k
  struct Curl_easy *admin;
381
5.18k
  bool do_lock;
382
383
5.18k
  DEBUGASSERT(cpool);
384
5.18k
  DEBUGASSERT(data && !data->conn);
385
5.18k
  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
5.18k
  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
5.18k
  admin = Curl_get_admin(data);
399
5.18k
  do_lock = !CPOOL_IS_LOCKED(cpool);
400
5.18k
  if(do_lock)
401
7
    CPOOL_LOCK(cpool, admin);
402
403
5.18k
  if(conn->bits.in_cpool) {
404
5.18k
    cpool_remove_conn(cpool, conn);
405
5.18k
    DEBUGASSERT(!conn->bits.in_cpool);
406
5.18k
  }
407
408
  /* treat the connection as aborted in CONNECT_ONLY situations,
409
   * so no graceful shutdown is attempted. */
410
5.18k
  if(conn->bits.connect_only)
411
15
    aborted = TRUE;
412
413
5.18k
  if(data->multi) {
414
    /* Add it to the multi's cpool for shutdown handling */
415
5.18k
    infof(data, "%s connection #%" FMT_OFF_T,
416
5.18k
          aborted ? "closing" : "shutting down", conn->connection_id);
417
5.18k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
418
5.18k
  }
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
5.18k
  if(do_lock)
426
7
    CPOOL_UNLOCK(cpool, admin);
427
5.18k
}
428
429
void Curl_conn_close(struct Curl_easy *data,
430
                     struct connectdata *conn,
431
                     bool aborted)
432
5.18k
{
433
5.18k
  struct cpool *cpool = cpool_get_instance(data);
434
5.18k
  cpool_conn_close(cpool, data, conn, aborted);
435
5.18k
}
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
5.18k
{
457
5.18k
  struct cpool *cpool = cpool_get_instance(data);
458
5.18k
  struct cshutdn *cshutdn = Curl_cshutdn_get(data);
459
5.18k
  struct Curl_easy *admin;
460
5.18k
  struct cpool_bundle *bundle;
461
5.18k
  size_t dest_limit = 0;
462
5.18k
  size_t total_limit = 0;
463
5.18k
  size_t shutdowns;
464
5.18k
  int res = CPOOL_LIMIT_OK;
465
466
5.18k
  if(!cpool)
467
0
    return CPOOL_LIMIT_OK;
468
469
  /* multi determines the limits, no matter who owns the pool */
470
5.18k
  if(data->multi) {
471
5.18k
    dest_limit = data->multi->max_host_connections;
472
5.18k
    total_limit = data->multi->max_total_connections;
473
5.18k
  }
474
475
5.18k
  if(!dest_limit && !total_limit)
476
5.18k
    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
5.18k
{
556
5.18k
  CURLcode result = CURLE_OK;
557
5.18k
  struct cpool_bundle *bundle = NULL;
558
5.18k
  struct cpool *cpool = cpool_get_instance(data);
559
5.18k
  DEBUGASSERT(conn);
560
561
5.18k
  DEBUGASSERT(cpool);
562
5.18k
  if(!cpool)
563
0
    return CURLE_FAILED_INIT;
564
565
5.18k
  CPOOL_LOCK(cpool, data);
566
5.18k
  bundle = cpool_find_bundle(cpool, conn);
567
5.18k
  if(!bundle) {
568
5.18k
    bundle = cpool_add_bundle(cpool, conn);
569
5.18k
    if(!bundle) {
570
0
      result = CURLE_OUT_OF_MEMORY;
571
0
      goto out;
572
0
    }
573
5.18k
  }
574
575
5.18k
  cpool_bundle_add(bundle, conn);
576
5.18k
  conn->connection_id = cpool->next_connection_id++;
577
5.18k
  cpool->num_conn++;
578
5.18k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
579
5.18k
             "The cache now contains %zu members",
580
5.18k
             conn->connection_id, cpool->num_conn);
581
5.18k
out:
582
5.18k
  CPOOL_UNLOCK(cpool, data);
583
584
5.18k
  return result;
585
5.18k
}
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
5.18k
{
606
5.18k
  struct Curl_hash_iterator iter;
607
5.18k
  struct Curl_hash_element *he;
608
609
5.18k
  if(!cpool)
610
0
    return FALSE;
611
612
5.18k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
613
614
5.18k
  he = Curl_hash_next_element(&iter);
615
5.18k
  while(he) {
616
7
    struct Curl_llist_node *curr;
617
7
    struct cpool_bundle *bundle = he->ptr;
618
7
    he = Curl_hash_next_element(&iter);
619
620
7
    curr = Curl_llist_head(&bundle->conns);
621
7
    while(curr) {
622
      /* Yes, we need to update curr before calling func(), because func()
623
         might decide to remove the connection */
624
7
      struct connectdata *conn = Curl_node_elem(curr);
625
7
      curr = Curl_node_next(curr);
626
627
7
      if(func(cpool, data, conn, param) == 1) {
628
7
        return TRUE;
629
7
      }
630
7
    }
631
7
  }
632
5.17k
  return FALSE;
633
5.18k
}
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
7
{
644
7
  unsigned int maxconnects;
645
7
  struct connectdata *oldest_idle = NULL;
646
7
  struct cpool *cpool = cpool_get_instance(data);
647
7
  struct Curl_easy *admin;
648
7
  bool kept = TRUE;
649
7
  timediff_t min_age_ms = 0;
650
651
7
  if(!data || !data->multi)
652
0
    return kept;
653
654
7
  if(!data->multi->maxconnects) {
655
    /* Attached transfers is a weak indicator of business. */
656
7
    uint32_t attached = Curl_multi_xfers_attached(data->multi);
657
7
    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
7
    min_age_ms = 1000;
661
7
  }
662
0
  else {
663
0
    maxconnects = data->multi->maxconnects;
664
0
  }
665
666
  /* remember times, connection had been used just before */
667
7
  conn->lastchecked = conn->lastupkeep = conn->lastused = *Curl_pgrs_now(data);
668
7
  if(cpool && maxconnects) {
669
    /* may be called form a callback already under lock */
670
7
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
671
672
7
    admin = Curl_get_admin(data);
673
7
    if(do_lock)
674
0
      CPOOL_LOCK(cpool, admin);
675
7
    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
7
    if(do_lock)
686
0
      CPOOL_UNLOCK(cpool, admin);
687
7
  }
688
689
7
  return kept;
690
7
}
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
5.16k
{
698
5.16k
  struct cpool *cpool = cpool_get_instance(data);
699
5.16k
  struct cpool_bundle *bundle;
700
5.16k
  bool found = FALSE;
701
702
5.16k
  DEBUGASSERT(cpool);
703
5.16k
  DEBUGASSERT(conn_cb);
704
5.16k
  if(!cpool)
705
0
    return FALSE;
706
707
5.16k
  CPOOL_LOCK(cpool, data);
708
5.16k
  bundle = Curl_hash_pick(&cpool->dest2bundle,
709
5.16k
                          CURL_UNCONST(destination),
710
5.16k
                          strlen(destination) + 1);
711
5.16k
  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
5.16k
  if(done_cb) {
726
5.16k
    found = done_cb(userdata);
727
5.16k
  }
728
5.16k
  CPOOL_UNLOCK(cpool, data);
729
5.16k
  return found;
730
5.16k
}
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
5.16k
{
765
5.16k
  struct Curl_easy *admin;
766
5.16k
  timediff_t elapsed;
767
768
5.16k
  if(!cpool)
769
0
    return;
770
771
5.16k
  admin = Curl_get_admin(data);
772
5.16k
  CPOOL_LOCK(cpool, admin);
773
5.16k
  elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup);
774
775
5.16k
  if(elapsed >= 1000L) {
776
5.16k
    struct cpool_reaper_ctx reaper;
777
778
5.16k
    memset(&reaper, 0, sizeof(reaper));
779
5.16k
    reaper.now = *Curl_pgrs_now(admin);
780
5.16k
    while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb))
781
0
      ;
782
5.16k
    cpool->last_cleanup = *Curl_pgrs_now(admin);
783
5.16k
  }
784
5.16k
  CPOOL_UNLOCK(cpool, admin);
785
5.16k
}
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
7
{
837
7
  struct cpool_find_ctx *fctx = param;
838
7
  (void)cpool;
839
7
  (void)data;
840
7
  if(conn->connection_id == fctx->id) {
841
7
    fctx->conn = conn;
842
7
    return 1;
843
7
  }
844
0
  return 0;
845
7
}
846
847
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
848
                                        curl_off_t conn_id)
849
15
{
850
15
  struct cpool *cpool = cpool_get_instance(data);
851
15
  struct cpool_find_ctx fctx;
852
853
15
  if(!cpool)
854
0
    return NULL;
855
15
  fctx.id = conn_id;
856
15
  fctx.conn = NULL;
857
15
  CPOOL_LOCK(cpool, data);
858
15
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
859
15
  CPOOL_UNLOCK(cpool, data);
860
15
  return fctx.conn;
861
15
}
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
5.18k
{
867
5.18k
  struct cpool *cpool = cpool_get_instance(data);
868
5.18k
  if(cpool) {
869
5.18k
    CPOOL_LOCK(cpool, data);
870
5.18k
    cb(conn, data, cbdata);
871
5.18k
    CPOOL_UNLOCK(cpool, data);
872
5.18k
  }
873
0
  else
874
0
    cb(conn, data, cbdata);
875
5.18k
}
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