Coverage Report

Created: 2026-03-12 06:35

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