Coverage Report

Created: 2026-01-10 07:08

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
166k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
43
44
#define CPOOL_LOCK(c, d)                                                \
45
936k
  do {                                                                  \
46
936k
    if(c) {                                                             \
47
936k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
48
936k
        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
49
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
50
936k
      DEBUGASSERT(!(c)->locked);                                        \
51
936k
      (c)->locked = TRUE;                                               \
52
936k
    }                                                                   \
53
936k
  } while(0)
54
55
#define CPOOL_UNLOCK(c,d)                                               \
56
936k
  do {                                                                  \
57
936k
    if(c) {                                                             \
58
936k
      DEBUGASSERT((c)->locked);                                         \
59
936k
      (c)->locked = FALSE;                                              \
60
936k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
61
936k
        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
62
936k
    }                                                                   \
63
936k
  } 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
122k
{
79
122k
  struct cpool_bundle *bundle;
80
122k
  size_t dest_len = strlen(dest) + 1;
81
82
122k
  bundle = curlx_calloc(1, sizeof(*bundle) + dest_len - 1);
83
122k
  if(!bundle)
84
0
    return NULL;
85
122k
  Curl_llist_init(&bundle->conns, NULL);
86
122k
  bundle->dest_len = dest_len;
87
122k
  memcpy(bundle->dest, dest, bundle->dest_len);
88
122k
  return bundle;
89
122k
}
90
91
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
92
122k
{
93
122k
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
94
122k
  curlx_free(bundle);
95
122k
}
96
97
/* Add a connection to a bundle */
98
static void cpool_bundle_add(struct cpool_bundle *bundle,
99
                             struct connectdata *conn)
100
123k
{
101
123k
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
102
123k
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
103
123k
  conn->bits.in_cpool = TRUE;
104
123k
}
105
106
/* Remove a connection from a bundle */
107
static void cpool_bundle_remove(struct cpool_bundle *bundle,
108
                                struct connectdata *conn)
109
123k
{
110
123k
  (void)bundle;
111
123k
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
112
123k
  Curl_node_remove(&conn->cpool_node);
113
123k
  conn->bits.in_cpool = FALSE;
114
123k
}
115
116
static void cpool_bundle_free_entry(void *freethis)
117
122k
{
118
122k
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
119
122k
}
120
121
void Curl_cpool_init(struct cpool *cpool,
122
                     struct Curl_easy *idata,
123
                     struct Curl_share *share,
124
                     size_t size)
125
174k
{
126
174k
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
127
174k
                 curlx_str_key_compare, cpool_bundle_free_entry);
128
129
174k
  DEBUGASSERT(idata);
130
131
174k
  cpool->idata = idata;
132
174k
  cpool->share = share;
133
174k
  cpool->initialised = TRUE;
134
174k
}
135
136
/* Return the "first" connection in the pool or NULL. */
137
static struct connectdata *cpool_get_first(struct cpool *cpool)
138
182k
{
139
182k
  struct Curl_hash_iterator iter;
140
182k
  struct Curl_hash_element *he;
141
182k
  struct cpool_bundle *bundle;
142
182k
  struct Curl_llist_node *conn_node;
143
144
182k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
145
182k
  for(he = Curl_hash_next_element(&iter); he;
146
182k
      he = Curl_hash_next_element(&iter)) {
147
7.25k
    bundle = he->ptr;
148
7.25k
    conn_node = Curl_llist_head(&bundle->conns);
149
7.25k
    if(conn_node)
150
7.25k
      return Curl_node_elem(conn_node);
151
7.25k
  }
152
174k
  return NULL;
153
182k
}
154
155
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
156
                                              struct connectdata *conn)
157
246k
{
158
246k
  return Curl_hash_pick(&cpool->dest2bundle,
159
246k
                        conn->destination, strlen(conn->destination) + 1);
160
246k
}
161
162
static void cpool_remove_bundle(struct cpool *cpool,
163
                                struct cpool_bundle *bundle)
164
122k
{
165
122k
  if(!cpool)
166
0
    return;
167
122k
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
168
122k
}
169
170
static void cpool_remove_conn(struct cpool *cpool,
171
                              struct connectdata *conn)
172
123k
{
173
123k
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
174
123k
  DEBUGASSERT(cpool);
175
123k
  if(list) {
176
    /* The connection is certainly in the pool, but where? */
177
123k
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
178
123k
    if(bundle && (list == &bundle->conns)) {
179
123k
      cpool_bundle_remove(bundle, conn);
180
123k
      if(!Curl_llist_count(&bundle->conns))
181
122k
        cpool_remove_bundle(cpool, bundle);
182
123k
      conn->bits.in_cpool = FALSE;
183
123k
      cpool->num_conn--;
184
123k
    }
185
0
    else {
186
      /* Should have been in the bundle list */
187
0
      DEBUGASSERT(NULL);
188
0
    }
189
123k
  }
190
123k
}
191
192
void Curl_cpool_destroy(struct cpool *cpool)
193
174k
{
194
174k
  if(cpool && cpool->initialised && cpool->idata) {
195
174k
    struct connectdata *conn;
196
174k
    SIGPIPE_VARIABLE(pipe_st);
197
198
174k
    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
199
174k
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
200
    /* Move all connections to the shutdown list */
201
174k
    sigpipe_init(&pipe_st);
202
174k
    CPOOL_LOCK(cpool, cpool->idata);
203
174k
    conn = cpool_get_first(cpool);
204
182k
    while(conn) {
205
7.25k
      cpool_remove_conn(cpool, conn);
206
7.25k
      sigpipe_apply(cpool->idata, &pipe_st);
207
7.25k
      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
208
7.25k
      conn = cpool_get_first(cpool);
209
7.25k
    }
210
174k
    CPOOL_UNLOCK(cpool, cpool->idata);
211
174k
    sigpipe_restore(&pipe_st);
212
174k
    Curl_hash_destroy(&cpool->dest2bundle);
213
174k
  }
214
174k
}
215
216
static struct cpool *cpool_get_instance(struct Curl_easy *data)
217
1.01M
{
218
1.01M
  if(data) {
219
1.01M
    if(CURL_SHARE_KEEP_CONNECT(data->share))
220
0
      return &data->share->cpool;
221
1.01M
    else if(data->multi_easy)
222
0
      return &data->multi_easy->cpool;
223
1.01M
    else if(data->multi)
224
1.01M
      return &data->multi->cpool;
225
1.01M
  }
226
0
  return NULL;
227
1.01M
}
228
229
void Curl_cpool_xfer_init(struct Curl_easy *data)
230
184k
{
231
184k
  struct cpool *cpool = cpool_get_instance(data);
232
233
184k
  DEBUGASSERT(cpool);
234
184k
  if(cpool) {
235
184k
    CPOOL_LOCK(cpool, data);
236
    /* the identifier inside the connection cache */
237
184k
    data->id = cpool->next_easy_id++;
238
184k
    if(cpool->next_easy_id <= 0)
239
0
      cpool->next_easy_id = 0;
240
184k
    data->state.lastconnect_id = -1;
241
242
184k
    CPOOL_UNLOCK(cpool, data);
243
184k
  }
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
184k
}
250
251
static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool,
252
                                             struct connectdata *conn)
253
122k
{
254
122k
  struct cpool_bundle *bundle;
255
256
122k
  bundle = cpool_bundle_create(conn->destination);
257
122k
  if(!bundle)
258
0
    return NULL;
259
260
122k
  if(!Curl_hash_add(&cpool->dest2bundle,
261
122k
                    bundle->dest, bundle->dest_len, bundle)) {
262
0
    cpool_bundle_destroy(bundle);
263
0
    return NULL;
264
0
  }
265
122k
  return bundle;
266
122k
}
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
122k
{
333
122k
  struct cpool *cpool = cpool_get_instance(data);
334
122k
  struct cpool_bundle *bundle;
335
122k
  size_t dest_limit = 0;
336
122k
  size_t total_limit = 0;
337
122k
  size_t shutdowns;
338
122k
  int result = CPOOL_LIMIT_OK;
339
340
122k
  if(!cpool)
341
0
    return CPOOL_LIMIT_OK;
342
343
122k
  if(cpool->idata->multi) {
344
122k
    dest_limit = cpool->idata->multi->max_host_connections;
345
122k
    total_limit = cpool->idata->multi->max_total_connections;
346
122k
  }
347
348
122k
  if(!dest_limit && !total_limit)
349
122k
    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
123k
{
429
123k
  CURLcode result = CURLE_OK;
430
123k
  struct cpool_bundle *bundle = NULL;
431
123k
  struct cpool *cpool = cpool_get_instance(data);
432
123k
  DEBUGASSERT(conn);
433
434
123k
  DEBUGASSERT(cpool);
435
123k
  if(!cpool)
436
0
    return CURLE_FAILED_INIT;
437
438
123k
  CPOOL_LOCK(cpool, data);
439
123k
  bundle = cpool_find_bundle(cpool, conn);
440
123k
  if(!bundle) {
441
122k
    bundle = cpool_add_bundle(cpool, conn);
442
122k
    if(!bundle) {
443
0
      result = CURLE_OUT_OF_MEMORY;
444
0
      goto out;
445
0
    }
446
122k
  }
447
448
123k
  cpool_bundle_add(bundle, conn);
449
123k
  conn->connection_id = cpool->next_connection_id++;
450
123k
  cpool->num_conn++;
451
123k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
452
123k
             "The cache now contains %zu members",
453
123k
             conn->connection_id, cpool->num_conn);
454
123k
out:
455
123k
  CPOOL_UNLOCK(cpool, data);
456
457
123k
  return result;
458
123k
}
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
130k
{
478
130k
  struct Curl_hash_iterator iter;
479
130k
  struct Curl_hash_element *he;
480
481
130k
  if(!cpool)
482
0
    return FALSE;
483
484
130k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
485
486
130k
  he = Curl_hash_next_element(&iter);
487
130k
  while(he) {
488
11.0k
    struct Curl_llist_node *curr;
489
11.0k
    struct cpool_bundle *bundle = he->ptr;
490
11.0k
    he = Curl_hash_next_element(&iter);
491
492
11.0k
    curr = Curl_llist_head(&bundle->conns);
493
11.3k
    while(curr) {
494
      /* Yes, we need to update curr before calling func(), because func()
495
         might decide to remove the connection */
496
11.2k
      struct connectdata *conn = Curl_node_elem(curr);
497
11.2k
      curr = Curl_node_next(curr);
498
499
11.2k
      if(func(data, conn, param) == 1) {
500
10.8k
        return TRUE;
501
10.8k
      }
502
11.2k
    }
503
11.0k
  }
504
119k
  return FALSE;
505
130k
}
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
29.6k
{
516
29.6k
  unsigned int maxconnects;
517
29.6k
  struct connectdata *oldest_idle = NULL;
518
29.6k
  struct cpool *cpool = cpool_get_instance(data);
519
29.6k
  bool kept = TRUE;
520
521
29.6k
  if(!data)
522
0
    return kept;
523
524
29.6k
  if(!data->multi->maxconnects) {
525
29.6k
    unsigned int running = Curl_multi_xfers_running(data->multi);
526
29.6k
    maxconnects = (running <= UINT_MAX / 4) ? running * 4 : UINT_MAX;
527
29.6k
  }
528
0
  else {
529
0
    maxconnects = data->multi->maxconnects;
530
0
  }
531
532
29.6k
  conn->lastused = *Curl_pgrs_now(data); /* it was used up until now */
533
29.6k
  if(cpool && maxconnects) {
534
    /* may be called form a callback already under lock */
535
29.6k
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
536
29.6k
    if(do_lock)
537
0
      CPOOL_LOCK(cpool, data);
538
29.6k
    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
29.6k
    if(do_lock)
549
0
      CPOOL_UNLOCK(cpool, data);
550
29.6k
  }
551
552
29.6k
  return kept;
553
29.6k
}
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
127k
{
561
127k
  struct cpool *cpool = cpool_get_instance(data);
562
127k
  struct cpool_bundle *bundle;
563
127k
  bool result = FALSE;
564
565
127k
  DEBUGASSERT(cpool);
566
127k
  DEBUGASSERT(conn_cb);
567
127k
  if(!cpool)
568
0
    return FALSE;
569
570
127k
  CPOOL_LOCK(cpool, data);
571
127k
  bundle = Curl_hash_pick(&cpool->dest2bundle,
572
127k
                          CURL_UNCONST(destination),
573
127k
                          strlen(destination) + 1);
574
127k
  if(bundle) {
575
21.8k
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
576
32.2k
    while(curr) {
577
21.8k
      struct connectdata *conn = Curl_node_elem(curr);
578
      /* Get next node now. callback might discard current */
579
21.8k
      curr = Curl_node_next(curr);
580
581
21.8k
      if(conn_cb(conn, userdata)) {
582
11.4k
        result = TRUE;
583
11.4k
        break;
584
11.4k
      }
585
21.8k
    }
586
21.8k
  }
587
588
127k
  if(done_cb) {
589
127k
    result = done_cb(result, userdata);
590
127k
  }
591
127k
  CPOOL_UNLOCK(cpool, data);
592
127k
  return result;
593
127k
}
594
595
static void cpool_discard_conn(struct cpool *cpool,
596
                               struct Curl_easy *data,
597
                               struct connectdata *conn,
598
                               bool aborted)
599
143k
{
600
143k
  bool done = FALSE;
601
602
143k
  DEBUGASSERT(data);
603
143k
  DEBUGASSERT(!data->conn);
604
143k
  DEBUGASSERT(cpool);
605
143k
  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
143k
  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
143k
  if(conn->connect_only)
621
16.4k
    aborted = TRUE;
622
143k
  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
143k
  if(aborted)
630
115k
    done = TRUE;
631
143k
  if(!done) {
632
    /* Attempt to shutdown the connection right away. */
633
28.5k
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
634
28.5k
  }
635
636
143k
  if(done || !data->multi)
637
143k
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
638
0
  else
639
0
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
640
143k
}
641
642
void Curl_conn_terminate(struct Curl_easy *data,
643
                         struct connectdata *conn,
644
                         bool aborted)
645
136k
{
646
136k
  struct cpool *cpool = cpool_get_instance(data);
647
136k
  bool do_lock;
648
649
136k
  DEBUGASSERT(cpool);
650
136k
  DEBUGASSERT(data && !data->conn);
651
136k
  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
136k
  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
136k
  do_lock = !CPOOL_IS_LOCKED(cpool);
665
136k
  if(do_lock)
666
30.6k
    CPOOL_LOCK(cpool, data);
667
668
136k
  if(conn->bits.in_cpool) {
669
116k
    cpool_remove_conn(cpool, conn);
670
116k
    DEBUGASSERT(!conn->bits.in_cpool);
671
116k
  }
672
673
  /* treat the connection as aborted in CONNECT_ONLY situations,
674
   * so no graceful shutdown is attempted. */
675
136k
  if(conn->connect_only)
676
16.3k
    aborted = TRUE;
677
678
136k
  if(data->multi) {
679
    /* Add it to the multi's cpool for shutdown handling */
680
136k
    infof(data, "%s connection #%" FMT_OFF_T,
681
136k
          aborted ? "closing" : "shutting down", conn->connection_id);
682
136k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
683
136k
  }
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
136k
  if(do_lock)
691
30.6k
    CPOOL_UNLOCK(cpool, data);
692
136k
}
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
143k
{
727
143k
  struct cpool *cpool = cpool_get_instance(data);
728
143k
  struct cpool_reaper_ctx reaper;
729
143k
  timediff_t elapsed;
730
731
143k
  if(!cpool)
732
0
    return;
733
734
143k
  memset(&reaper, 0, sizeof(reaper));
735
143k
  CPOOL_LOCK(cpool, data);
736
143k
  elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data), &cpool->last_cleanup);
737
738
143k
  if(elapsed >= 1000L) {
739
112k
    while(cpool_foreach(data, cpool, &reaper, cpool_reap_dead_cb))
740
0
      ;
741
112k
    cpool->last_cleanup = *Curl_pgrs_now(data);
742
112k
  }
743
143k
  CPOOL_UNLOCK(cpool, data);
744
143k
}
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
3.94k
{
776
3.94k
  struct cpool_find_ctx *fctx = param;
777
3.94k
  (void)data;
778
3.94k
  if(conn->connection_id == fctx->id) {
779
3.82k
    fctx->conn = conn;
780
3.82k
    return 1;
781
3.82k
  }
782
123
  return 0;
783
3.94k
}
784
785
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
786
                                        curl_off_t conn_id)
787
3.82k
{
788
3.82k
  struct cpool *cpool = cpool_get_instance(data);
789
3.82k
  struct cpool_find_ctx fctx;
790
791
3.82k
  if(!cpool)
792
0
    return NULL;
793
3.82k
  fctx.id = conn_id;
794
3.82k
  fctx.conn = NULL;
795
3.82k
  CPOOL_LOCK(cpool, data);
796
3.82k
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
797
3.82k
  CPOOL_UNLOCK(cpool, data);
798
3.82k
  return fctx.conn;
799
3.82k
}
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
7.25k
{
810
7.25k
  struct cpool_do_conn_ctx *dctx = param;
811
7.25k
  (void)data;
812
7.25k
  if(conn->connection_id == dctx->id) {
813
7.05k
    dctx->cb(conn, data, dctx->cbdata);
814
7.05k
    return 1;
815
7.05k
  }
816
201
  return 0;
817
7.25k
}
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
13.5k
{
822
13.5k
  struct cpool *cpool = cpool_get_instance(data);
823
13.5k
  struct cpool_do_conn_ctx dctx;
824
825
13.5k
  if(!cpool)
826
0
    return;
827
13.5k
  dctx.id = conn_id;
828
13.5k
  dctx.cb = cb;
829
13.5k
  dctx.cbdata = cbdata;
830
13.5k
  CPOOL_LOCK(cpool, data);
831
13.5k
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
832
13.5k
  CPOOL_UNLOCK(cpool, data);
833
13.5k
}
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
135k
{
839
135k
  struct cpool *cpool = cpool_get_instance(data);
840
135k
  if(cpool) {
841
135k
    CPOOL_LOCK(cpool, data);
842
135k
    cb(conn, data, cbdata);
843
135k
    CPOOL_UNLOCK(cpool, data);
844
135k
  }
845
0
  else
846
0
    cb(conn, data, cbdata);
847
135k
}
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