Coverage Report

Created: 2026-09-14 07:05

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