Coverage Report

Created: 2025-11-23 06:22

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