Coverage Report

Created: 2026-09-01 06:59

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