Coverage Report

Created: 2026-03-11 07:11

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