Coverage Report

Created: 2026-07-30 07:26

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