Coverage Report

Created: 2026-01-17 06:25

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