Coverage Report

Created: 2026-09-04 07:15

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