Coverage Report

Created: 2025-11-23 06:13

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
0
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
54
55
#define CPOOL_LOCK(c,d)                                                 \
56
0
  do {                                                                  \
57
0
    if((c)) {                                                           \
58
0
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
59
0
        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
60
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
61
0
      DEBUGASSERT(!(c)->locked);                                        \
62
0
      (c)->locked = TRUE;                                               \
63
0
    }                                                                   \
64
0
  } while(0)
65
66
#define CPOOL_UNLOCK(c,d)                                                 \
67
0
  do {                                                                  \
68
0
    if((c)) {                                                           \
69
0
      DEBUGASSERT((c)->locked);                                         \
70
0
      (c)->locked = FALSE;                                              \
71
0
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
72
0
        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
73
0
    }                                                                   \
74
0
  } 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
0
{
92
0
  struct cpool_bundle *bundle;
93
0
  size_t dest_len = strlen(dest) + 1;
94
95
0
  bundle = calloc(1, sizeof(*bundle) + dest_len - 1);
96
0
  if(!bundle)
97
0
    return NULL;
98
0
  Curl_llist_init(&bundle->conns, NULL);
99
0
  bundle->dest_len = dest_len;
100
0
  memcpy(bundle->dest, dest, bundle->dest_len);
101
0
  return bundle;
102
0
}
103
104
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
105
0
{
106
0
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
107
0
  free(bundle);
108
0
}
109
110
/* Add a connection to a bundle */
111
static void cpool_bundle_add(struct cpool_bundle *bundle,
112
                             struct connectdata *conn)
113
0
{
114
0
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
115
0
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
116
0
  conn->bits.in_cpool = TRUE;
117
0
}
118
119
/* Remove a connection from a bundle */
120
static void cpool_bundle_remove(struct cpool_bundle *bundle,
121
                                struct connectdata *conn)
122
0
{
123
0
  (void)bundle;
124
0
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
125
0
  Curl_node_remove(&conn->cpool_node);
126
0
  conn->bits.in_cpool = FALSE;
127
0
}
128
129
static void cpool_bundle_free_entry(void *freethis)
130
0
{
131
0
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
132
0
}
133
134
void Curl_cpool_init(struct cpool *cpool,
135
                     struct Curl_easy *idata,
136
                     struct Curl_share *share,
137
                     size_t size)
138
0
{
139
0
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
140
0
                 curlx_str_key_compare, cpool_bundle_free_entry);
141
142
0
  DEBUGASSERT(idata);
143
144
0
  cpool->idata = idata;
145
0
  cpool->share = share;
146
0
  cpool->initialised = TRUE;
147
0
}
148
149
/* Return the "first" connection in the pool or NULL. */
150
static struct connectdata *cpool_get_first(struct cpool *cpool)
151
0
{
152
0
  struct Curl_hash_iterator iter;
153
0
  struct Curl_hash_element *he;
154
0
  struct cpool_bundle *bundle;
155
0
  struct Curl_llist_node *conn_node;
156
157
0
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
158
0
  for(he = Curl_hash_next_element(&iter); he;
159
0
      he = Curl_hash_next_element(&iter)) {
160
0
    bundle = he->ptr;
161
0
    conn_node = Curl_llist_head(&bundle->conns);
162
0
    if(conn_node)
163
0
      return Curl_node_elem(conn_node);
164
0
  }
165
0
  return NULL;
166
0
}
167
168
169
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
170
                                              struct connectdata *conn)
171
0
{
172
0
  return Curl_hash_pick(&cpool->dest2bundle,
173
0
                        conn->destination, strlen(conn->destination) + 1);
174
0
}
175
176
177
static void cpool_remove_bundle(struct cpool *cpool,
178
                                struct cpool_bundle *bundle)
179
0
{
180
0
  if(!cpool)
181
0
    return;
182
0
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
183
0
}
184
185
186
static void cpool_remove_conn(struct cpool *cpool,
187
                              struct connectdata *conn)
188
0
{
189
0
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
190
0
  DEBUGASSERT(cpool);
191
0
  if(list) {
192
    /* The connection is certainly in the pool, but where? */
193
0
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
194
0
    if(bundle && (list == &bundle->conns)) {
195
0
      cpool_bundle_remove(bundle, conn);
196
0
      if(!Curl_llist_count(&bundle->conns))
197
0
        cpool_remove_bundle(cpool, bundle);
198
0
      conn->bits.in_cpool = FALSE;
199
0
      cpool->num_conn--;
200
0
    }
201
0
    else {
202
      /* Should have been in the bundle list */
203
0
      DEBUGASSERT(NULL);
204
0
    }
205
0
  }
206
0
}
207
208
void Curl_cpool_destroy(struct cpool *cpool)
209
0
{
210
0
  if(cpool && cpool->initialised && cpool->idata) {
211
0
    struct connectdata *conn;
212
0
    SIGPIPE_VARIABLE(pipe_st);
213
214
0
    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
215
0
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
216
    /* Move all connections to the shutdown list */
217
0
    sigpipe_init(&pipe_st);
218
0
    CPOOL_LOCK(cpool, cpool->idata);
219
0
    conn = cpool_get_first(cpool);
220
0
    while(conn) {
221
0
      cpool_remove_conn(cpool, conn);
222
0
      sigpipe_apply(cpool->idata, &pipe_st);
223
0
      connclose(conn, "kill all");
224
0
      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
225
0
      conn = cpool_get_first(cpool);
226
0
    }
227
0
    CPOOL_UNLOCK(cpool, cpool->idata);
228
0
    sigpipe_restore(&pipe_st);
229
0
    Curl_hash_destroy(&cpool->dest2bundle);
230
0
  }
231
0
}
232
233
static struct cpool *cpool_get_instance(struct Curl_easy *data)
234
0
{
235
0
  if(data) {
236
0
    if(CURL_SHARE_KEEP_CONNECT(data->share))
237
0
      return &data->share->cpool;
238
0
    else if(data->multi_easy)
239
0
      return &data->multi_easy->cpool;
240
0
    else if(data->multi)
241
0
      return &data->multi->cpool;
242
0
  }
243
0
  return NULL;
244
0
}
245
246
void Curl_cpool_xfer_init(struct Curl_easy *data)
247
0
{
248
0
  struct cpool *cpool = cpool_get_instance(data);
249
250
0
  DEBUGASSERT(cpool);
251
0
  if(cpool) {
252
0
    CPOOL_LOCK(cpool, data);
253
    /* the identifier inside the connection cache */
254
0
    data->id = cpool->next_easy_id++;
255
0
    if(cpool->next_easy_id <= 0)
256
0
      cpool->next_easy_id = 0;
257
0
    data->state.lastconnect_id = -1;
258
259
0
    CPOOL_UNLOCK(cpool, data);
260
0
  }
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
0
}
267
268
static struct cpool_bundle *
269
cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
270
0
{
271
0
  struct cpool_bundle *bundle;
272
273
0
  bundle = cpool_bundle_create(conn->destination);
274
0
  if(!bundle)
275
0
    return NULL;
276
277
0
  if(!Curl_hash_add(&cpool->dest2bundle,
278
0
                    bundle->dest, bundle->dest_len, bundle)) {
279
0
    cpool_bundle_destroy(bundle);
280
0
    return NULL;
281
0
  }
282
0
  return bundle;
283
0
}
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
0
{
353
0
  struct cpool *cpool = cpool_get_instance(data);
354
0
  struct cpool_bundle *bundle;
355
0
  size_t dest_limit = 0;
356
0
  size_t total_limit = 0;
357
0
  size_t shutdowns;
358
0
  int result = CPOOL_LIMIT_OK;
359
360
0
  if(!cpool)
361
0
    return CPOOL_LIMIT_OK;
362
363
0
  if(cpool->idata->multi) {
364
0
    dest_limit = cpool->idata->multi->max_host_connections;
365
0
    total_limit = cpool->idata->multi->max_total_connections;
366
0
  }
367
368
0
  if(!dest_limit && !total_limit)
369
0
    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
0
{
447
0
  CURLcode result = CURLE_OK;
448
0
  struct cpool_bundle *bundle = NULL;
449
0
  struct cpool *cpool = cpool_get_instance(data);
450
0
  DEBUGASSERT(conn);
451
452
0
  DEBUGASSERT(cpool);
453
0
  if(!cpool)
454
0
    return CURLE_FAILED_INIT;
455
456
0
  CPOOL_LOCK(cpool, data);
457
0
  bundle = cpool_find_bundle(cpool, conn);
458
0
  if(!bundle) {
459
0
    bundle = cpool_add_bundle(cpool, conn);
460
0
    if(!bundle) {
461
0
      result = CURLE_OUT_OF_MEMORY;
462
0
      goto out;
463
0
    }
464
0
  }
465
466
0
  cpool_bundle_add(bundle, conn);
467
0
  conn->connection_id = cpool->next_connection_id++;
468
0
  cpool->num_conn++;
469
0
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
470
0
             "The cache now contains %zu members",
471
0
             conn->connection_id, cpool->num_conn);
472
0
out:
473
0
  CPOOL_UNLOCK(cpool, data);
474
475
0
  return result;
476
0
}
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
0
{
496
0
  struct Curl_hash_iterator iter;
497
0
  struct Curl_hash_element *he;
498
499
0
  if(!cpool)
500
0
    return FALSE;
501
502
0
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
503
504
0
  he = Curl_hash_next_element(&iter);
505
0
  while(he) {
506
0
    struct Curl_llist_node *curr;
507
0
    struct cpool_bundle *bundle = he->ptr;
508
0
    he = Curl_hash_next_element(&iter);
509
510
0
    curr = Curl_llist_head(&bundle->conns);
511
0
    while(curr) {
512
      /* Yes, we need to update curr before calling func(), because func()
513
         might decide to remove the connection */
514
0
      struct connectdata *conn = Curl_node_elem(curr);
515
0
      curr = Curl_node_next(curr);
516
517
0
      if(func(data, conn, param) == 1) {
518
0
        return TRUE;
519
0
      }
520
0
    }
521
0
  }
522
0
  return FALSE;
523
0
}
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
0
{
534
0
  unsigned int maxconnects;
535
0
  struct connectdata *oldest_idle = NULL;
536
0
  struct cpool *cpool = cpool_get_instance(data);
537
0
  bool kept = TRUE;
538
539
0
  if(!data)
540
0
    return kept;
541
542
0
  if(!data->multi->maxconnects) {
543
0
    unsigned int running = Curl_multi_xfers_running(data->multi);
544
0
    maxconnects = (running <= UINT_MAX / 4) ? running * 4 : UINT_MAX;
545
0
  }
546
0
  else {
547
0
    maxconnects = data->multi->maxconnects;
548
0
  }
549
550
0
  conn->lastused = curlx_now(); /* it was used up until now */
551
0
  if(cpool && maxconnects) {
552
    /* may be called form a callback already under lock */
553
0
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
554
0
    if(do_lock)
555
0
      CPOOL_LOCK(cpool, data);
556
0
    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
0
    if(do_lock)
567
0
      CPOOL_UNLOCK(cpool, data);
568
0
  }
569
570
0
  return kept;
571
0
}
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
0
{
579
0
  struct cpool *cpool = cpool_get_instance(data);
580
0
  struct cpool_bundle *bundle;
581
0
  bool result = FALSE;
582
583
0
  DEBUGASSERT(cpool);
584
0
  DEBUGASSERT(conn_cb);
585
0
  if(!cpool)
586
0
    return FALSE;
587
588
0
  CPOOL_LOCK(cpool, data);
589
0
  bundle = Curl_hash_pick(&cpool->dest2bundle,
590
0
                          CURL_UNCONST(destination),
591
0
                          strlen(destination) + 1);
592
0
  if(bundle) {
593
0
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
594
0
    while(curr) {
595
0
      struct connectdata *conn = Curl_node_elem(curr);
596
      /* Get next node now. callback might discard current */
597
0
      curr = Curl_node_next(curr);
598
599
0
      if(conn_cb(conn, userdata)) {
600
0
        result = TRUE;
601
0
        break;
602
0
      }
603
0
    }
604
0
  }
605
606
0
  if(done_cb) {
607
0
    result = done_cb(result, userdata);
608
0
  }
609
0
  CPOOL_UNLOCK(cpool, data);
610
0
  return result;
611
0
}
612
613
static void cpool_discard_conn(struct cpool *cpool,
614
                               struct Curl_easy *data,
615
                               struct connectdata *conn,
616
                               bool aborted)
617
0
{
618
0
  bool done = FALSE;
619
620
0
  DEBUGASSERT(data);
621
0
  DEBUGASSERT(!data->conn);
622
0
  DEBUGASSERT(cpool);
623
0
  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
0
  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
0
  if(conn->connect_only)
639
0
    aborted = TRUE;
640
0
  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
0
  if(aborted)
648
0
    done = TRUE;
649
0
  if(!done) {
650
    /* Attempt to shutdown the connection right away. */
651
0
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
652
0
  }
653
654
0
  if(done || !data->multi)
655
0
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
656
0
  else
657
0
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
658
0
}
659
660
void Curl_conn_terminate(struct Curl_easy *data,
661
                         struct connectdata *conn,
662
                         bool aborted)
663
0
{
664
0
  struct cpool *cpool = cpool_get_instance(data);
665
0
  bool do_lock;
666
667
0
  DEBUGASSERT(cpool);
668
0
  DEBUGASSERT(data && !data->conn);
669
0
  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
0
  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
0
  do_lock = !CPOOL_IS_LOCKED(cpool);
683
0
  if(do_lock)
684
0
    CPOOL_LOCK(cpool, data);
685
686
0
  if(conn->bits.in_cpool) {
687
0
    cpool_remove_conn(cpool, conn);
688
0
    DEBUGASSERT(!conn->bits.in_cpool);
689
0
  }
690
691
  /* treat the connection as aborted in CONNECT_ONLY situations,
692
   * so no graceful shutdown is attempted. */
693
0
  if(conn->connect_only)
694
0
    aborted = TRUE;
695
696
0
  if(data->multi) {
697
    /* Add it to the multi's cpool for shutdown handling */
698
0
    infof(data, "%s connection #%" FMT_OFF_T,
699
0
          aborted ? "closing" : "shutting down", conn->connection_id);
700
0
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
701
0
  }
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
0
  if(do_lock)
709
0
    CPOOL_UNLOCK(cpool, data);
710
0
}
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
0
{
739
0
  struct cpool *cpool = cpool_get_instance(data);
740
0
  struct cpool_reaper_ctx rctx;
741
0
  timediff_t elapsed;
742
743
0
  if(!cpool)
744
0
    return;
745
746
0
  rctx.now = curlx_now();
747
0
  CPOOL_LOCK(cpool, data);
748
0
  elapsed = curlx_timediff_ms(rctx.now, cpool->last_cleanup);
749
750
0
  if(elapsed >= 1000L) {
751
0
    while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb))
752
0
      ;
753
0
    cpool->last_cleanup = rctx.now;
754
0
  }
755
0
  CPOOL_UNLOCK(cpool, data);
756
0
}
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
0
{
789
0
  struct cpool_find_ctx *fctx = param;
790
0
  (void)data;
791
0
  if(conn->connection_id == fctx->id) {
792
0
    fctx->conn = conn;
793
0
    return 1;
794
0
  }
795
0
  return 0;
796
0
}
797
798
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
799
                                        curl_off_t conn_id)
800
0
{
801
0
  struct cpool *cpool = cpool_get_instance(data);
802
0
  struct cpool_find_ctx fctx;
803
804
0
  if(!cpool)
805
0
    return NULL;
806
0
  fctx.id = conn_id;
807
0
  fctx.conn = NULL;
808
0
  CPOOL_LOCK(cpool, data);
809
0
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
810
0
  CPOOL_UNLOCK(cpool, data);
811
0
  return fctx.conn;
812
0
}
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
0
{
823
0
  struct cpool_do_conn_ctx *dctx = param;
824
0
  (void)data;
825
0
  if(conn->connection_id == dctx->id) {
826
0
    dctx->cb(conn, data, dctx->cbdata);
827
0
    return 1;
828
0
  }
829
0
  return 0;
830
0
}
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
0
{
835
0
  struct cpool *cpool = cpool_get_instance(data);
836
0
  struct cpool_do_conn_ctx dctx;
837
838
0
  if(!cpool)
839
0
    return;
840
0
  dctx.id = conn_id;
841
0
  dctx.cb = cb;
842
0
  dctx.cbdata = cbdata;
843
0
  CPOOL_LOCK(cpool, data);
844
0
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
845
0
  CPOOL_UNLOCK(cpool, data);
846
0
}
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
0
{
852
0
  struct cpool *cpool = cpool_get_instance(data);
853
0
  if(cpool) {
854
0
    CPOOL_LOCK(cpool, data);
855
0
    cb(conn, data, cbdata);
856
0
    CPOOL_UNLOCK(cpool, data);
857
0
  }
858
0
  else
859
0
    cb(conn, data, cbdata);
860
0
}
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