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