Coverage Report

Created: 2026-04-12 06:59

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
126k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
40
41
#define CPOOL_LOCK(c, d)                                                \
42
836k
  do {                                                                  \
43
836k
    if(c) {                                                             \
44
836k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
45
836k
        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
46
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
47
836k
      DEBUGASSERT(!(c)->locked);                                        \
48
836k
      (c)->locked = TRUE;                                               \
49
836k
    }                                                                   \
50
836k
  } while(0)
51
52
#define CPOOL_UNLOCK(c, d)                                              \
53
836k
  do {                                                                  \
54
836k
    if(c) {                                                             \
55
836k
      DEBUGASSERT((c)->locked);                                         \
56
836k
      (c)->locked = FALSE;                                              \
57
836k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
58
836k
        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
59
836k
    }                                                                   \
60
836k
  } 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
113k
{
71
113k
  struct cpool_bundle *bundle;
72
113k
  size_t dest_len = strlen(dest) + 1;
73
74
113k
  bundle = curlx_calloc(1, sizeof(*bundle) + dest_len - 1);
75
113k
  if(!bundle)
76
0
    return NULL;
77
113k
  Curl_llist_init(&bundle->conns, NULL);
78
113k
  bundle->dest_len = dest_len;
79
113k
  memcpy(bundle->dest, dest, bundle->dest_len);
80
113k
  return bundle;
81
113k
}
82
83
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
84
113k
{
85
113k
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
86
113k
  curlx_free(bundle);
87
113k
}
88
89
/* Add a connection to a bundle */
90
static void cpool_bundle_add(struct cpool_bundle *bundle,
91
                             struct connectdata *conn)
92
113k
{
93
113k
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
94
113k
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
95
113k
  conn->bits.in_cpool = TRUE;
96
113k
}
97
98
/* Remove a connection from a bundle */
99
static void cpool_bundle_remove(struct cpool_bundle *bundle,
100
                                struct connectdata *conn)
101
113k
{
102
113k
  (void)bundle;
103
113k
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
104
113k
  Curl_node_remove(&conn->cpool_node);
105
113k
  conn->bits.in_cpool = FALSE;
106
113k
}
107
108
static void cpool_bundle_free_entry(void *freethis)
109
113k
{
110
113k
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
111
113k
}
112
113
void Curl_cpool_init(struct cpool *cpool,
114
                     struct Curl_easy *idata,
115
                     struct Curl_share *share,
116
                     size_t size)
117
169k
{
118
169k
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
119
169k
                 curlx_str_key_compare, cpool_bundle_free_entry);
120
121
169k
  DEBUGASSERT(idata);
122
123
169k
  cpool->idata = idata;
124
169k
  cpool->share = share;
125
169k
  cpool->initialised = TRUE;
126
169k
}
127
128
/* Return the "first" connection in the pool or NULL. */
129
static struct connectdata *cpool_get_first(struct cpool *cpool)
130
175k
{
131
175k
  struct Curl_hash_iterator iter;
132
175k
  struct Curl_hash_element *he;
133
175k
  struct cpool_bundle *bundle;
134
175k
  struct Curl_llist_node *conn_node;
135
136
175k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
137
175k
  for(he = Curl_hash_next_element(&iter); he;
138
175k
      he = Curl_hash_next_element(&iter)) {
139
6.14k
    bundle = he->ptr;
140
6.14k
    conn_node = Curl_llist_head(&bundle->conns);
141
6.14k
    if(conn_node)
142
6.14k
      return Curl_node_elem(conn_node);
143
6.14k
  }
144
169k
  return NULL;
145
175k
}
146
147
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
148
                                              struct connectdata *conn)
149
226k
{
150
226k
  return Curl_hash_pick(&cpool->dest2bundle,
151
226k
                        conn->destination, strlen(conn->destination) + 1);
152
226k
}
153
154
static void cpool_remove_bundle(struct cpool *cpool,
155
                                struct cpool_bundle *bundle)
156
113k
{
157
113k
  if(!cpool)
158
0
    return;
159
113k
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
160
113k
}
161
162
static void cpool_remove_conn(struct cpool *cpool,
163
                              struct connectdata *conn)
164
113k
{
165
113k
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
166
113k
  DEBUGASSERT(cpool);
167
113k
  if(list) {
168
    /* The connection is certainly in the pool, but where? */
169
113k
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
170
113k
    if(bundle && (list == &bundle->conns)) {
171
113k
      cpool_bundle_remove(bundle, conn);
172
113k
      if(!Curl_llist_count(&bundle->conns))
173
113k
        cpool_remove_bundle(cpool, bundle);
174
113k
      conn->bits.in_cpool = FALSE;
175
113k
      cpool->num_conn--;
176
113k
    }
177
0
    else {
178
      /* Should have been in the bundle list */
179
0
      DEBUGASSERT(NULL);
180
0
    }
181
113k
  }
182
113k
}
183
184
static void cpool_discard_conn(struct cpool *cpool,
185
                               struct Curl_easy *data,
186
                               struct connectdata *conn,
187
                               bool aborted)
188
113k
{
189
113k
  bool done = FALSE;
190
191
113k
  DEBUGASSERT(data);
192
113k
  DEBUGASSERT(!data->conn);
193
113k
  DEBUGASSERT(cpool);
194
113k
  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
113k
  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
113k
  if(conn->bits.connect_only)
210
14.5k
    aborted = TRUE;
211
113k
  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
113k
  if(aborted)
219
91.1k
    done = TRUE;
220
113k
  if(!done) {
221
    /* Attempt to shutdown the connection right away. */
222
22.2k
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
223
22.2k
  }
224
225
113k
  if(done || !data->multi)
226
113k
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
227
0
  else
228
0
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
229
113k
}
230
231
void Curl_cpool_destroy(struct cpool *cpool)
232
169k
{
233
169k
  if(cpool && cpool->initialised && cpool->idata) {
234
169k
    struct connectdata *conn;
235
169k
    struct Curl_sigpipe_ctx pipe_ctx;
236
237
169k
    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
238
169k
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
239
    /* Move all connections to the shutdown list */
240
169k
    sigpipe_init(&pipe_ctx);
241
169k
    CPOOL_LOCK(cpool, cpool->idata);
242
169k
    conn = cpool_get_first(cpool);
243
169k
    if(conn)
244
6.10k
      sigpipe_apply(cpool->idata, &pipe_ctx);
245
175k
    while(conn) {
246
6.14k
      cpool_remove_conn(cpool, conn);
247
6.14k
      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
248
6.14k
      conn = cpool_get_first(cpool);
249
6.14k
    }
250
169k
    CPOOL_UNLOCK(cpool, cpool->idata);
251
169k
    sigpipe_restore(&pipe_ctx);
252
169k
    Curl_hash_destroy(&cpool->dest2bundle);
253
169k
  }
254
169k
}
255
256
static struct cpool *cpool_get_instance(struct Curl_easy *data)
257
901k
{
258
901k
  if(data) {
259
901k
    if(CURL_SHARE_KEEP_CONNECT(data->share))
260
0
      return &data->share->cpool;
261
901k
    else if(data->multi_easy)
262
0
      return &data->multi_easy->cpool;
263
901k
    else if(data->multi)
264
901k
      return &data->multi->cpool;
265
901k
  }
266
0
  return NULL;
267
901k
}
268
269
void Curl_cpool_xfer_init(struct Curl_easy *data)
270
178k
{
271
178k
  struct cpool *cpool = cpool_get_instance(data);
272
273
178k
  DEBUGASSERT(cpool);
274
178k
  if(cpool) {
275
178k
    CPOOL_LOCK(cpool, data);
276
    /* the identifier inside the connection cache */
277
178k
    data->id = cpool->next_easy_id++;
278
178k
    if(cpool->next_easy_id <= 0)
279
0
      cpool->next_easy_id = 0;
280
178k
    data->state.lastconnect_id = -1;
281
282
178k
    CPOOL_UNLOCK(cpool, data);
283
178k
  }
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
178k
}
290
291
static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool,
292
                                             struct connectdata *conn)
293
113k
{
294
113k
  struct cpool_bundle *bundle;
295
296
113k
  bundle = cpool_bundle_create(conn->destination);
297
113k
  if(!bundle)
298
0
    return NULL;
299
300
113k
  if(!Curl_hash_add(&cpool->dest2bundle,
301
113k
                    bundle->dest, bundle->dest_len, bundle)) {
302
0
    cpool_bundle_destroy(bundle);
303
0
    return NULL;
304
0
  }
305
113k
  return bundle;
306
113k
}
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->bits.connect_only)
358
0
        continue;
359
      /* Set higher score for the age passed since the connection was used */
360
0
      score = curlx_ptimediff_ms(pnow, &conn->lastused);
361
0
      if(score > highscore) {
362
0
        highscore = score;
363
0
        oldest_idle = conn;
364
0
      }
365
0
    }
366
0
  }
367
0
  return oldest_idle;
368
0
}
369
370
int Curl_cpool_check_limits(struct Curl_easy *data,
371
                            struct connectdata *conn)
372
112k
{
373
112k
  struct cpool *cpool = cpool_get_instance(data);
374
112k
  struct cpool_bundle *bundle;
375
112k
  size_t dest_limit = 0;
376
112k
  size_t total_limit = 0;
377
112k
  size_t shutdowns;
378
112k
  int res = CPOOL_LIMIT_OK;
379
380
112k
  if(!cpool)
381
0
    return CPOOL_LIMIT_OK;
382
383
112k
  if(cpool->idata->multi) {
384
112k
    dest_limit = cpool->idata->multi->max_host_connections;
385
112k
    total_limit = cpool->idata->multi->max_total_connections;
386
112k
  }
387
388
112k
  if(!dest_limit && !total_limit)
389
112k
    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
      res = 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
      res = 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 res;
464
0
}
465
466
CURLcode Curl_cpool_add(struct Curl_easy *data,
467
                        struct connectdata *conn)
468
113k
{
469
113k
  CURLcode result = CURLE_OK;
470
113k
  struct cpool_bundle *bundle = NULL;
471
113k
  struct cpool *cpool = cpool_get_instance(data);
472
113k
  DEBUGASSERT(conn);
473
474
113k
  DEBUGASSERT(cpool);
475
113k
  if(!cpool)
476
0
    return CURLE_FAILED_INIT;
477
478
113k
  CPOOL_LOCK(cpool, data);
479
113k
  bundle = cpool_find_bundle(cpool, conn);
480
113k
  if(!bundle) {
481
113k
    bundle = cpool_add_bundle(cpool, conn);
482
113k
    if(!bundle) {
483
0
      result = CURLE_OUT_OF_MEMORY;
484
0
      goto out;
485
0
    }
486
113k
  }
487
488
113k
  cpool_bundle_add(bundle, conn);
489
113k
  conn->connection_id = cpool->next_connection_id++;
490
113k
  cpool->num_conn++;
491
113k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
492
113k
             "The cache now contains %zu members",
493
113k
             conn->connection_id, cpool->num_conn);
494
113k
out:
495
113k
  CPOOL_UNLOCK(cpool, data);
496
497
113k
  return result;
498
113k
}
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
118k
{
518
118k
  struct Curl_hash_iterator iter;
519
118k
  struct Curl_hash_element *he;
520
521
118k
  if(!cpool)
522
0
    return FALSE;
523
524
118k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
525
526
118k
  he = Curl_hash_next_element(&iter);
527
118k
  while(he) {
528
9.53k
    struct Curl_llist_node *curr;
529
9.53k
    struct cpool_bundle *bundle = he->ptr;
530
9.53k
    he = Curl_hash_next_element(&iter);
531
532
9.53k
    curr = Curl_llist_head(&bundle->conns);
533
9.83k
    while(curr) {
534
      /* Yes, we need to update curr before calling func(), because func()
535
         might decide to remove the connection */
536
9.67k
      struct connectdata *conn = Curl_node_elem(curr);
537
9.67k
      curr = Curl_node_next(curr);
538
539
9.67k
      if(func(data, conn, param) == 1) {
540
9.37k
        return TRUE;
541
9.37k
      }
542
9.67k
    }
543
9.53k
  }
544
108k
  return FALSE;
545
118k
}
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
19.4k
{
556
19.4k
  unsigned int maxconnects;
557
19.4k
  struct connectdata *oldest_idle = NULL;
558
19.4k
  struct cpool *cpool = cpool_get_instance(data);
559
19.4k
  bool kept = TRUE;
560
561
19.4k
  if(!data || !data->multi)
562
0
    return kept;
563
564
19.4k
  if(!data->multi->maxconnects) {
565
19.4k
    unsigned int running = Curl_multi_xfers_running(data->multi);
566
19.4k
    maxconnects = (running <= UINT_MAX / 4) ? running * 4 : UINT_MAX;
567
19.4k
  }
568
0
  else {
569
0
    maxconnects = data->multi->maxconnects;
570
0
  }
571
572
19.4k
  conn->lastused = *Curl_pgrs_now(data); /* it was used up until now */
573
19.4k
  if(cpool && maxconnects) {
574
    /* may be called form a callback already under lock */
575
19.4k
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
576
19.4k
    if(do_lock)
577
0
      CPOOL_LOCK(cpool, data);
578
19.4k
    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
19.4k
    if(do_lock)
589
0
      CPOOL_UNLOCK(cpool, data);
590
19.4k
  }
591
592
19.4k
  return kept;
593
19.4k
}
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
109k
{
601
109k
  struct cpool *cpool = cpool_get_instance(data);
602
109k
  struct cpool_bundle *bundle;
603
109k
  bool found = FALSE;
604
605
109k
  DEBUGASSERT(cpool);
606
109k
  DEBUGASSERT(conn_cb);
607
109k
  if(!cpool)
608
0
    return FALSE;
609
610
109k
  CPOOL_LOCK(cpool, data);
611
109k
  bundle = Curl_hash_pick(&cpool->dest2bundle,
612
109k
                          CURL_UNCONST(destination),
613
109k
                          strlen(destination) + 1);
614
109k
  if(bundle) {
615
12.7k
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
616
16.3k
    while(curr) {
617
12.7k
      struct connectdata *conn = Curl_node_elem(curr);
618
      /* Get next node now. callback might discard current */
619
12.7k
      curr = Curl_node_next(curr);
620
621
12.7k
      if(conn_cb(conn, userdata)) {
622
9.23k
        found = TRUE;
623
9.23k
        break;
624
9.23k
      }
625
12.7k
    }
626
12.7k
  }
627
628
109k
  if(done_cb) {
629
109k
    found = done_cb(userdata);
630
109k
  }
631
109k
  CPOOL_UNLOCK(cpool, data);
632
109k
  return found;
633
109k
}
634
635
void Curl_conn_terminate(struct Curl_easy *data,
636
                         struct connectdata *conn,
637
                         bool aborted)
638
107k
{
639
107k
  struct cpool *cpool = cpool_get_instance(data);
640
107k
  bool do_lock;
641
642
107k
  DEBUGASSERT(cpool);
643
107k
  DEBUGASSERT(data && !data->conn);
644
107k
  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
107k
  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
107k
  do_lock = !CPOOL_IS_LOCKED(cpool);
658
107k
  if(do_lock)
659
3.41k
    CPOOL_LOCK(cpool, data);
660
661
107k
  if(conn->bits.in_cpool) {
662
107k
    cpool_remove_conn(cpool, conn);
663
107k
    DEBUGASSERT(!conn->bits.in_cpool);
664
107k
  }
665
666
  /* treat the connection as aborted in CONNECT_ONLY situations,
667
   * so no graceful shutdown is attempted. */
668
107k
  if(conn->bits.connect_only)
669
14.4k
    aborted = TRUE;
670
671
107k
  if(data->multi) {
672
    /* Add it to the multi's cpool for shutdown handling */
673
107k
    infof(data, "%s connection #%" FMT_OFF_T,
674
107k
          aborted ? "closing" : "shutting down", conn->connection_id);
675
107k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
676
107k
  }
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
107k
  if(do_lock)
684
3.41k
    CPOOL_UNLOCK(cpool, data);
685
107k
}
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
124k
{
720
124k
  struct cpool *cpool = cpool_get_instance(data);
721
124k
  struct cpool_reaper_ctx reaper;
722
124k
  timediff_t elapsed;
723
724
124k
  if(!cpool)
725
0
    return;
726
727
124k
  memset(&reaper, 0, sizeof(reaper));
728
124k
  CPOOL_LOCK(cpool, data);
729
124k
  elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data), &cpool->last_cleanup);
730
731
124k
  if(elapsed >= 1000L) {
732
103k
    while(cpool_foreach(data, cpool, &reaper, cpool_reap_dead_cb))
733
0
      ;
734
103k
    cpool->last_cleanup = *Curl_pgrs_now(data);
735
103k
  }
736
124k
  CPOOL_UNLOCK(cpool, data);
737
124k
}
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
3.54k
{
769
3.54k
  struct cpool_find_ctx *fctx = param;
770
3.54k
  (void)data;
771
3.54k
  if(conn->connection_id == fctx->id) {
772
3.41k
    fctx->conn = conn;
773
3.41k
    return 1;
774
3.41k
  }
775
123
  return 0;
776
3.54k
}
777
778
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
779
                                        curl_off_t conn_id)
780
3.41k
{
781
3.41k
  struct cpool *cpool = cpool_get_instance(data);
782
3.41k
  struct cpool_find_ctx fctx;
783
784
3.41k
  if(!cpool)
785
0
    return NULL;
786
3.41k
  fctx.id = conn_id;
787
3.41k
  fctx.conn = NULL;
788
3.41k
  CPOOL_LOCK(cpool, data);
789
3.41k
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
790
3.41k
  CPOOL_UNLOCK(cpool, data);
791
3.41k
  return fctx.conn;
792
3.41k
}
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
6.13k
{
803
6.13k
  struct cpool_do_conn_ctx *dctx = param;
804
805
6.13k
  if(conn->connection_id == dctx->id) {
806
5.95k
    dctx->cb(conn, data, dctx->cbdata);
807
5.95k
    return 1;
808
5.95k
  }
809
180
  return 0;
810
6.13k
}
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
11.2k
{
815
11.2k
  struct cpool *cpool = cpool_get_instance(data);
816
11.2k
  struct cpool_do_conn_ctx dctx;
817
818
11.2k
  if(!cpool)
819
0
    return;
820
11.2k
  dctx.id = conn_id;
821
11.2k
  dctx.cb = cb;
822
11.2k
  dctx.cbdata = cbdata;
823
11.2k
  CPOOL_LOCK(cpool, data);
824
11.2k
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
825
11.2k
  CPOOL_UNLOCK(cpool, data);
826
11.2k
}
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
122k
{
832
122k
  struct cpool *cpool = cpool_get_instance(data);
833
122k
  if(cpool) {
834
122k
    CPOOL_LOCK(cpool, data);
835
122k
    cb(conn, data, cbdata);
836
122k
    CPOOL_UNLOCK(cpool, data);
837
122k
  }
838
0
  else
839
0
    cb(conn, data, cbdata);
840
122k
}
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