Coverage Report

Created: 2025-10-10 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/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(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(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 = !data->multi->maxconnects ?
535
0
    (Curl_multi_xfers_running(data->multi) * 4) : data->multi->maxconnects;
536
0
  struct connectdata *oldest_idle = NULL;
537
0
  struct cpool *cpool = cpool_get_instance(data);
538
0
  bool kept = TRUE;
539
540
0
  conn->lastused = curlx_now(); /* it was used up until now */
541
0
  if(cpool && maxconnects) {
542
    /* may be called form a callback already under lock */
543
0
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
544
0
    if(do_lock)
545
0
      CPOOL_LOCK(cpool, data);
546
0
    if(cpool->num_conn > maxconnects) {
547
0
      infof(data, "Connection pool is full, closing the oldest of %zu/%u",
548
0
            cpool->num_conn, maxconnects);
549
550
0
      oldest_idle = cpool_get_oldest_idle(cpool);
551
0
      kept = (oldest_idle != conn);
552
0
      if(oldest_idle) {
553
0
        Curl_conn_terminate(data, oldest_idle, FALSE);
554
0
      }
555
0
    }
556
0
    if(do_lock)
557
0
      CPOOL_UNLOCK(cpool, data);
558
0
  }
559
560
0
  return kept;
561
0
}
562
563
bool Curl_cpool_find(struct Curl_easy *data,
564
                     const char *destination,
565
                     Curl_cpool_conn_match_cb *conn_cb,
566
                     Curl_cpool_done_match_cb *done_cb,
567
                     void *userdata)
568
0
{
569
0
  struct cpool *cpool = cpool_get_instance(data);
570
0
  struct cpool_bundle *bundle;
571
0
  bool result = FALSE;
572
573
0
  DEBUGASSERT(cpool);
574
0
  DEBUGASSERT(conn_cb);
575
0
  if(!cpool)
576
0
    return FALSE;
577
578
0
  CPOOL_LOCK(cpool, data);
579
0
  bundle = Curl_hash_pick(&cpool->dest2bundle,
580
0
                          CURL_UNCONST(destination),
581
0
                          strlen(destination) + 1);
582
0
  if(bundle) {
583
0
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
584
0
    while(curr) {
585
0
      struct connectdata *conn = Curl_node_elem(curr);
586
      /* Get next node now. callback might discard current */
587
0
      curr = Curl_node_next(curr);
588
589
0
      if(conn_cb(conn, userdata)) {
590
0
        result = TRUE;
591
0
        break;
592
0
      }
593
0
    }
594
0
  }
595
596
0
  if(done_cb) {
597
0
    result = done_cb(result, userdata);
598
0
  }
599
0
  CPOOL_UNLOCK(cpool, data);
600
0
  return result;
601
0
}
602
603
static void cpool_discard_conn(struct cpool *cpool,
604
                               struct Curl_easy *data,
605
                               struct connectdata *conn,
606
                               bool aborted)
607
0
{
608
0
  bool done = FALSE;
609
610
0
  DEBUGASSERT(data);
611
0
  DEBUGASSERT(!data->conn);
612
0
  DEBUGASSERT(cpool);
613
0
  DEBUGASSERT(!conn->bits.in_cpool);
614
615
  /*
616
   * If this connection is not marked to force-close, leave it open if there
617
   * are other users of it
618
   */
619
0
  if(CONN_INUSE(conn) && !aborted) {
620
0
    CURL_TRC_M(data, "[CPOOL] not discarding #%" FMT_OFF_T
621
0
               " still in use by %u transfers", conn->connection_id,
622
0
               CONN_ATTACHED(conn));
623
0
    return;
624
0
  }
625
626
  /* treat the connection as aborted in CONNECT_ONLY situations, we do
627
   * not know what the APP did with it. */
628
0
  if(conn->connect_only)
629
0
    aborted = TRUE;
630
0
  conn->bits.aborted = aborted;
631
632
  /* We do not shutdown dead connections. The term 'dead' can be misleading
633
   * here, as we also mark errored connections/transfers as 'dead'.
634
   * If we do a shutdown for an aborted transfer, the server might think
635
   * it was successful otherwise (for example an ftps: upload). This is
636
   * not what we want. */
637
0
  if(aborted)
638
0
    done = TRUE;
639
0
  if(!done) {
640
    /* Attempt to shutdown the connection right away. */
641
0
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
642
0
  }
643
644
0
  if(done || !data->multi)
645
0
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
646
0
  else
647
0
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
648
0
}
649
650
void Curl_conn_terminate(struct Curl_easy *data,
651
                         struct connectdata *conn,
652
                         bool aborted)
653
0
{
654
0
  struct cpool *cpool = cpool_get_instance(data);
655
0
  bool do_lock;
656
657
0
  DEBUGASSERT(cpool);
658
0
  DEBUGASSERT(data && !data->conn);
659
0
  if(!cpool)
660
0
    return;
661
662
  /* If this connection is not marked to force-close, leave it open if there
663
   * are other users of it */
664
0
  if(CONN_INUSE(conn) && !aborted) {
665
0
    DEBUGASSERT(0); /* does this ever happen? */
666
0
    DEBUGF(infof(data, "Curl_disconnect when inuse: %u", CONN_ATTACHED(conn)));
667
0
    return;
668
0
  }
669
670
  /* This method may be called while we are under lock, e.g. from a
671
   * user callback in find. */
672
0
  do_lock = !CPOOL_IS_LOCKED(cpool);
673
0
  if(do_lock)
674
0
    CPOOL_LOCK(cpool, data);
675
676
0
  if(conn->bits.in_cpool) {
677
0
    cpool_remove_conn(cpool, conn);
678
0
    DEBUGASSERT(!conn->bits.in_cpool);
679
0
  }
680
681
  /* treat the connection as aborted in CONNECT_ONLY situations,
682
   * so no graceful shutdown is attempted. */
683
0
  if(conn->connect_only)
684
0
    aborted = TRUE;
685
686
0
  if(data->multi) {
687
    /* Add it to the multi's cpool for shutdown handling */
688
0
    infof(data, "%s connection #%" FMT_OFF_T,
689
0
          aborted ? "closing" : "shutting down", conn->connection_id);
690
0
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
691
0
  }
692
0
  else {
693
    /* No multi available, terminate */
694
0
    infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
695
0
    Curl_cshutdn_terminate(cpool->idata, conn, !aborted);
696
0
  }
697
698
0
  if(do_lock)
699
0
    CPOOL_UNLOCK(cpool, data);
700
0
}
701
702
703
struct cpool_reaper_ctx {
704
  struct curltime now;
705
};
706
707
static int cpool_reap_dead_cb(struct Curl_easy *data,
708
                              struct connectdata *conn, void *param)
709
0
{
710
0
  struct cpool_reaper_ctx *rctx = param;
711
0
  if((!CONN_INUSE(conn) && conn->bits.no_reuse) ||
712
0
     Curl_conn_seems_dead(conn, data, &rctx->now)) {
713
    /* stop the iteration here, pass back the connection that was pruned */
714
0
    Curl_conn_terminate(data, conn, FALSE);
715
0
    return 1;
716
0
  }
717
0
  return 0; /* continue iteration */
718
0
}
719
720
/*
721
 * This function scans the data's connection pool for half-open/dead
722
 * connections, closes and removes them.
723
 * The cleanup is done at most once per second.
724
 *
725
 * When called, this transfer has no connection attached.
726
 */
727
void Curl_cpool_prune_dead(struct Curl_easy *data)
728
0
{
729
0
  struct cpool *cpool = cpool_get_instance(data);
730
0
  struct cpool_reaper_ctx rctx;
731
0
  timediff_t elapsed;
732
733
0
  if(!cpool)
734
0
    return;
735
736
0
  rctx.now = curlx_now();
737
0
  CPOOL_LOCK(cpool, data);
738
0
  elapsed = curlx_timediff(rctx.now, cpool->last_cleanup);
739
740
0
  if(elapsed >= 1000L) {
741
0
    while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb))
742
0
      ;
743
0
    cpool->last_cleanup = rctx.now;
744
0
  }
745
0
  CPOOL_UNLOCK(cpool, data);
746
0
}
747
748
static int conn_upkeep(struct Curl_easy *data,
749
                       struct connectdata *conn,
750
                       void *param)
751
0
{
752
0
  struct curltime *now = param;
753
0
  Curl_conn_upkeep(data, conn, now);
754
0
  return 0; /* continue iteration */
755
0
}
756
757
CURLcode Curl_cpool_upkeep(void *data)
758
0
{
759
0
  struct cpool *cpool = cpool_get_instance(data);
760
0
  struct curltime now = curlx_now();
761
762
0
  if(!cpool)
763
0
    return CURLE_OK;
764
765
0
  CPOOL_LOCK(cpool, data);
766
0
  cpool_foreach(data, cpool, &now, conn_upkeep);
767
0
  CPOOL_UNLOCK(cpool, data);
768
0
  return CURLE_OK;
769
0
}
770
771
struct cpool_find_ctx {
772
  curl_off_t id;
773
  struct connectdata *conn;
774
};
775
776
static int cpool_find_conn(struct Curl_easy *data,
777
                           struct connectdata *conn, void *param)
778
0
{
779
0
  struct cpool_find_ctx *fctx = param;
780
0
  (void)data;
781
0
  if(conn->connection_id == fctx->id) {
782
0
    fctx->conn = conn;
783
0
    return 1;
784
0
  }
785
0
  return 0;
786
0
}
787
788
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
789
                                        curl_off_t conn_id)
790
0
{
791
0
  struct cpool *cpool = cpool_get_instance(data);
792
0
  struct cpool_find_ctx fctx;
793
794
0
  if(!cpool)
795
0
    return NULL;
796
0
  fctx.id = conn_id;
797
0
  fctx.conn = NULL;
798
0
  CPOOL_LOCK(cpool, data);
799
0
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
800
0
  CPOOL_UNLOCK(cpool, data);
801
0
  return fctx.conn;
802
0
}
803
804
struct cpool_do_conn_ctx {
805
  curl_off_t id;
806
  Curl_cpool_conn_do_cb *cb;
807
  void *cbdata;
808
};
809
810
static int cpool_do_conn(struct Curl_easy *data,
811
                         struct connectdata *conn, void *param)
812
0
{
813
0
  struct cpool_do_conn_ctx *dctx = param;
814
0
  (void)data;
815
0
  if(conn->connection_id == dctx->id) {
816
0
    dctx->cb(conn, data, dctx->cbdata);
817
0
    return 1;
818
0
  }
819
0
  return 0;
820
0
}
821
822
void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id,
823
                         Curl_cpool_conn_do_cb *cb, void *cbdata)
824
0
{
825
0
  struct cpool *cpool = cpool_get_instance(data);
826
0
  struct cpool_do_conn_ctx dctx;
827
828
0
  if(!cpool)
829
0
    return;
830
0
  dctx.id = conn_id;
831
0
  dctx.cb = cb;
832
0
  dctx.cbdata = cbdata;
833
0
  CPOOL_LOCK(cpool, data);
834
0
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
835
0
  CPOOL_UNLOCK(cpool, data);
836
0
}
837
838
void Curl_cpool_do_locked(struct Curl_easy *data,
839
                          struct connectdata *conn,
840
                          Curl_cpool_conn_do_cb *cb, void *cbdata)
841
0
{
842
0
  struct cpool *cpool = cpool_get_instance(data);
843
0
  if(cpool) {
844
0
    CPOOL_LOCK(cpool, data);
845
0
    cb(conn, data, cbdata);
846
0
    CPOOL_UNLOCK(cpool, data);
847
0
  }
848
0
  else
849
0
    cb(conn, data, cbdata);
850
0
}
851
852
static int cpool_mark_stale(struct Curl_easy *data,
853
                            struct connectdata *conn, void *param)
854
0
{
855
0
  (void)data;
856
0
  (void)param;
857
0
  conn->bits.no_reuse = TRUE;
858
0
  return 0;
859
0
}
860
861
static int cpool_reap_no_reuse(struct Curl_easy *data,
862
                               struct connectdata *conn, void *param)
863
0
{
864
0
  (void)data;
865
0
  (void)param;
866
0
  if(!CONN_INUSE(conn) && conn->bits.no_reuse) {
867
0
    Curl_conn_terminate(data, conn, FALSE);
868
0
    return 1;
869
0
  }
870
0
  return 0; /* continue iteration */
871
0
}
872
873
void Curl_cpool_nw_changed(struct Curl_easy *data)
874
0
{
875
0
  struct cpool *cpool = cpool_get_instance(data);
876
877
0
  if(cpool) {
878
0
    CPOOL_LOCK(cpool, data);
879
0
    cpool_foreach(data, cpool, NULL, cpool_mark_stale);
880
0
    while(cpool_foreach(data, cpool, NULL, cpool_reap_no_reuse))
881
0
      ;
882
    CPOOL_UNLOCK(cpool, data);
883
0
  }
884
0
}
885
886
#if 0
887
/* Useful for debugging the connection pool */
888
void Curl_cpool_print(struct cpool *cpool)
889
{
890
  struct Curl_hash_iterator iter;
891
  struct Curl_llist_node *curr;
892
  struct Curl_hash_element *he;
893
894
  if(!cpool)
895
    return;
896
897
  curl_mfprintf(stderr, "=Bundle cache=\n");
898
899
  Curl_hash_start_iterate(cpool->dest2bundle, &iter);
900
901
  he = Curl_hash_next_element(&iter);
902
  while(he) {
903
    struct cpool_bundle *bundle;
904
    struct connectdata *conn;
905
906
    bundle = he->ptr;
907
908
    curl_mfprintf(stderr, "%s -", he->key);
909
    curr = Curl_llist_head(bundle->conns);
910
    while(curr) {
911
      conn = Curl_node_elem(curr);
912
913
      curl_mfprintf(stderr, " [%p %d]", (void *)conn, conn->refcount);
914
      curr = Curl_node_next(curr);
915
    }
916
    curl_mfprintf(stderr, "\n");
917
918
    he = Curl_hash_next_element(&iter);
919
  }
920
}
921
#endif