Coverage Report

Created: 2026-01-25 06:18

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