Coverage Report

Created: 2025-10-10 06:19

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
143k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
54
55
#define CPOOL_LOCK(c,d)                                                 \
56
816k
  do {                                                                  \
57
816k
    if((c)) {                                                           \
58
816k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
59
816k
        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
60
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
61
816k
      DEBUGASSERT(!(c)->locked);                                        \
62
816k
      (c)->locked = TRUE;                                               \
63
816k
    }                                                                   \
64
816k
  } while(0)
65
66
#define CPOOL_UNLOCK(c,d)                                                 \
67
816k
  do {                                                                  \
68
816k
    if((c)) {                                                           \
69
816k
      DEBUGASSERT((c)->locked);                                         \
70
816k
      (c)->locked = FALSE;                                              \
71
816k
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
72
816k
        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
73
816k
    }                                                                   \
74
816k
  } 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
109k
{
92
109k
  struct cpool_bundle *bundle;
93
109k
  size_t dest_len = strlen(dest) + 1;
94
95
109k
  bundle = calloc(1, sizeof(*bundle) + dest_len - 1);
96
109k
  if(!bundle)
97
0
    return NULL;
98
109k
  Curl_llist_init(&bundle->conns, NULL);
99
109k
  bundle->dest_len = dest_len;
100
109k
  memcpy(bundle->dest, dest, bundle->dest_len);
101
109k
  return bundle;
102
109k
}
103
104
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
105
109k
{
106
109k
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
107
109k
  free(bundle);
108
109k
}
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
109k
{
131
109k
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
132
109k
}
133
134
void Curl_cpool_init(struct cpool *cpool,
135
                     struct Curl_easy *idata,
136
                     struct Curl_share *share,
137
                     size_t size)
138
155k
{
139
155k
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
140
155k
                 curlx_str_key_compare, cpool_bundle_free_entry);
141
142
155k
  DEBUGASSERT(idata);
143
144
155k
  cpool->idata = idata;
145
155k
  cpool->share = share;
146
155k
  cpool->initialised = TRUE;
147
155k
}
148
149
/* Return the "first" connection in the pool or NULL. */
150
static struct connectdata *cpool_get_first(struct cpool *cpool)
151
161k
{
152
161k
  struct Curl_hash_iterator iter;
153
161k
  struct Curl_hash_element *he;
154
161k
  struct cpool_bundle *bundle;
155
161k
  struct Curl_llist_node *conn_node;
156
157
161k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
158
161k
  for(he = Curl_hash_next_element(&iter); he;
159
161k
      he = Curl_hash_next_element(&iter)) {
160
6.28k
    bundle = he->ptr;
161
6.28k
    conn_node = Curl_llist_head(&bundle->conns);
162
6.28k
    if(conn_node)
163
6.28k
      return Curl_node_elem(conn_node);
164
6.28k
  }
165
155k
  return NULL;
166
161k
}
167
168
169
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
170
                                              struct connectdata *conn)
171
220k
{
172
220k
  return Curl_hash_pick(&cpool->dest2bundle,
173
220k
                        conn->destination, strlen(conn->destination) + 1);
174
220k
}
175
176
177
static void cpool_remove_bundle(struct cpool *cpool,
178
                                struct cpool_bundle *bundle)
179
109k
{
180
109k
  if(!cpool)
181
0
    return;
182
109k
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
183
109k
}
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
109k
        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
155k
{
210
155k
  if(cpool && cpool->initialised && cpool->idata) {
211
155k
    struct connectdata *conn;
212
155k
    SIGPIPE_VARIABLE(pipe_st);
213
214
155k
    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
215
155k
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
216
    /* Move all connections to the shutdown list */
217
155k
    sigpipe_init(&pipe_st);
218
155k
    CPOOL_LOCK(cpool, cpool->idata);
219
155k
    conn = cpool_get_first(cpool);
220
161k
    while(conn) {
221
6.28k
      cpool_remove_conn(cpool, conn);
222
6.28k
      sigpipe_apply(cpool->idata, &pipe_st);
223
6.28k
      connclose(conn, "kill all");
224
6.28k
      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
225
6.28k
      conn = cpool_get_first(cpool);
226
6.28k
    }
227
155k
    CPOOL_UNLOCK(cpool, cpool->idata);
228
155k
    sigpipe_restore(&pipe_st);
229
155k
    Curl_hash_destroy(&cpool->dest2bundle);
230
155k
  }
231
155k
}
232
233
static struct cpool *cpool_get_instance(struct Curl_easy *data)
234
884k
{
235
884k
  if(data) {
236
884k
    if(CURL_SHARE_KEEP_CONNECT(data->share))
237
0
      return &data->share->cpool;
238
884k
    else if(data->multi_easy)
239
0
      return &data->multi_easy->cpool;
240
884k
    else if(data->multi)
241
884k
      return &data->multi->cpool;
242
884k
  }
243
0
  return NULL;
244
884k
}
245
246
void Curl_cpool_xfer_init(struct Curl_easy *data)
247
164k
{
248
164k
  struct cpool *cpool = cpool_get_instance(data);
249
250
164k
  DEBUGASSERT(cpool);
251
164k
  if(cpool) {
252
164k
    CPOOL_LOCK(cpool, data);
253
    /* the identifier inside the connection cache */
254
164k
    data->id = cpool->next_easy_id++;
255
164k
    if(cpool->next_easy_id <= 0)
256
0
      cpool->next_easy_id = 0;
257
164k
    data->state.lastconnect_id = -1;
258
259
164k
    CPOOL_UNLOCK(cpool, data);
260
164k
  }
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
164k
}
267
268
static struct cpool_bundle *
269
cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
270
109k
{
271
109k
  struct cpool_bundle *bundle;
272
273
109k
  bundle = cpool_bundle_create(conn->destination);
274
109k
  if(!bundle)
275
0
    return NULL;
276
277
109k
  if(!Curl_hash_add(&cpool->dest2bundle,
278
109k
                    bundle->dest, bundle->dest_len, bundle)) {
279
0
    cpool_bundle_destroy(bundle);
280
0
    return NULL;
281
0
  }
282
109k
  return bundle;
283
109k
}
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
108k
{
353
108k
  struct cpool *cpool = cpool_get_instance(data);
354
108k
  struct cpool_bundle *bundle;
355
108k
  size_t dest_limit = 0;
356
108k
  size_t total_limit = 0;
357
108k
  size_t shutdowns;
358
108k
  int result = CPOOL_LIMIT_OK;
359
360
108k
  if(!cpool)
361
0
    return CPOOL_LIMIT_OK;
362
363
108k
  if(cpool->idata->multi) {
364
108k
    dest_limit = cpool->idata->multi->max_host_connections;
365
108k
    total_limit = cpool->idata->multi->max_total_connections;
366
108k
  }
367
368
108k
  if(!dest_limit && !total_limit)
369
108k
    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
109k
    bundle = cpool_add_bundle(cpool, conn);
460
109k
    if(!bundle) {
461
0
      result = CURLE_OUT_OF_MEMORY;
462
0
      goto out;
463
0
    }
464
109k
  }
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
115k
{
496
115k
  struct Curl_hash_iterator iter;
497
115k
  struct Curl_hash_element *he;
498
499
115k
  if(!cpool)
500
0
    return FALSE;
501
502
115k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
503
504
115k
  he = Curl_hash_next_element(&iter);
505
115k
  while(he) {
506
9.83k
    struct Curl_llist_node *curr;
507
9.83k
    struct cpool_bundle *bundle = he->ptr;
508
9.83k
    he = Curl_hash_next_element(&iter);
509
510
9.83k
    curr = Curl_llist_head(&bundle->conns);
511
10.2k
    while(curr) {
512
      /* Yes, we need to update curr before calling func(), because func()
513
         might decide to remove the connection */
514
10.0k
      struct connectdata *conn = Curl_node_elem(curr);
515
10.0k
      curr = Curl_node_next(curr);
516
517
10.0k
      if(func(data, conn, param) == 1) {
518
9.62k
        return TRUE;
519
9.62k
      }
520
10.0k
    }
521
9.83k
  }
522
105k
  return FALSE;
523
115k
}
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
20.0k
{
534
20.0k
  unsigned int maxconnects = !data->multi->maxconnects ?
535
20.0k
    (Curl_multi_xfers_running(data->multi) * 4) : data->multi->maxconnects;
536
20.0k
  struct connectdata *oldest_idle = NULL;
537
20.0k
  struct cpool *cpool = cpool_get_instance(data);
538
20.0k
  bool kept = TRUE;
539
540
20.0k
  conn->lastused = curlx_now(); /* it was used up until now */
541
20.0k
  if(cpool && maxconnects) {
542
    /* may be called form a callback already under lock */
543
20.0k
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
544
20.0k
    if(do_lock)
545
0
      CPOOL_LOCK(cpool, data);
546
20.0k
    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
20.0k
    if(do_lock)
557
0
      CPOOL_UNLOCK(cpool, data);
558
20.0k
  }
559
560
20.0k
  return kept;
561
20.0k
}
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
106k
{
569
106k
  struct cpool *cpool = cpool_get_instance(data);
570
106k
  struct cpool_bundle *bundle;
571
106k
  bool result = FALSE;
572
573
106k
  DEBUGASSERT(cpool);
574
106k
  DEBUGASSERT(conn_cb);
575
106k
  if(!cpool)
576
0
    return FALSE;
577
578
106k
  CPOOL_LOCK(cpool, data);
579
106k
  bundle = Curl_hash_pick(&cpool->dest2bundle,
580
106k
                          CURL_UNCONST(destination),
581
106k
                          strlen(destination) + 1);
582
106k
  if(bundle) {
583
13.1k
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
584
17.2k
    while(curr) {
585
13.1k
      struct connectdata *conn = Curl_node_elem(curr);
586
      /* Get next node now. callback might discard current */
587
13.1k
      curr = Curl_node_next(curr);
588
589
13.1k
      if(conn_cb(conn, userdata)) {
590
9.06k
        result = TRUE;
591
9.06k
        break;
592
9.06k
      }
593
13.1k
    }
594
13.1k
  }
595
596
106k
  if(done_cb) {
597
106k
    result = done_cb(result, userdata);
598
106k
  }
599
106k
  CPOOL_UNLOCK(cpool, data);
600
106k
  return result;
601
106k
}
602
603
static void cpool_discard_conn(struct cpool *cpool,
604
                               struct Curl_easy *data,
605
                               struct connectdata *conn,
606
                               bool aborted)
607
129k
{
608
129k
  bool done = FALSE;
609
610
129k
  DEBUGASSERT(data);
611
129k
  DEBUGASSERT(!data->conn);
612
129k
  DEBUGASSERT(cpool);
613
129k
  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
129k
  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
129k
  if(conn->connect_only)
629
14.9k
    aborted = TRUE;
630
129k
  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
129k
  if(aborted)
638
103k
    done = TRUE;
639
129k
  if(!done) {
640
    /* Attempt to shutdown the connection right away. */
641
26.0k
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
642
26.0k
  }
643
644
129k
  if(done || !data->multi)
645
129k
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
646
0
  else
647
0
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
648
129k
}
649
650
void Curl_conn_terminate(struct Curl_easy *data,
651
                         struct connectdata *conn,
652
                         bool aborted)
653
123k
{
654
123k
  struct cpool *cpool = cpool_get_instance(data);
655
123k
  bool do_lock;
656
657
123k
  DEBUGASSERT(cpool);
658
123k
  DEBUGASSERT(data && !data->conn);
659
123k
  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
123k
  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
123k
  do_lock = !CPOOL_IS_LOCKED(cpool);
673
123k
  if(do_lock)
674
28.8k
    CPOOL_LOCK(cpool, data);
675
676
123k
  if(conn->bits.in_cpool) {
677
103k
    cpool_remove_conn(cpool, conn);
678
103k
    DEBUGASSERT(!conn->bits.in_cpool);
679
103k
  }
680
681
  /* treat the connection as aborted in CONNECT_ONLY situations,
682
   * so no graceful shutdown is attempted. */
683
123k
  if(conn->connect_only)
684
14.7k
    aborted = TRUE;
685
686
123k
  if(data->multi) {
687
    /* Add it to the multi's cpool for shutdown handling */
688
123k
    infof(data, "%s connection #%" FMT_OFF_T,
689
123k
          aborted ? "closing" : "shutting down", conn->connection_id);
690
123k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
691
123k
  }
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
123k
  if(do_lock)
699
28.8k
    CPOOL_UNLOCK(cpool, data);
700
123k
}
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
121k
{
729
121k
  struct cpool *cpool = cpool_get_instance(data);
730
121k
  struct cpool_reaper_ctx rctx;
731
121k
  timediff_t elapsed;
732
733
121k
  if(!cpool)
734
0
    return;
735
736
121k
  rctx.now = curlx_now();
737
121k
  CPOOL_LOCK(cpool, data);
738
121k
  elapsed = curlx_timediff(rctx.now, cpool->last_cleanup);
739
740
121k
  if(elapsed >= 1000L) {
741
99.9k
    while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb))
742
0
      ;
743
99.9k
    cpool->last_cleanup = rctx.now;
744
99.9k
  }
745
121k
  CPOOL_UNLOCK(cpool, data);
746
121k
}
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
3.75k
{
779
3.75k
  struct cpool_find_ctx *fctx = param;
780
3.75k
  (void)data;
781
3.75k
  if(conn->connection_id == fctx->id) {
782
3.58k
    fctx->conn = conn;
783
3.58k
    return 1;
784
3.58k
  }
785
166
  return 0;
786
3.75k
}
787
788
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
789
                                        curl_off_t conn_id)
790
3.58k
{
791
3.58k
  struct cpool *cpool = cpool_get_instance(data);
792
3.58k
  struct cpool_find_ctx fctx;
793
794
3.58k
  if(!cpool)
795
0
    return NULL;
796
3.58k
  fctx.id = conn_id;
797
3.58k
  fctx.conn = NULL;
798
3.58k
  CPOOL_LOCK(cpool, data);
799
3.58k
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
800
3.58k
  CPOOL_UNLOCK(cpool, data);
801
3.58k
  return fctx.conn;
802
3.58k
}
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
6.28k
{
813
6.28k
  struct cpool_do_conn_ctx *dctx = param;
814
6.28k
  (void)data;
815
6.28k
  if(conn->connection_id == dctx->id) {
816
6.04k
    dctx->cb(conn, data, dctx->cbdata);
817
6.04k
    return 1;
818
6.04k
  }
819
240
  return 0;
820
6.28k
}
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
11.6k
{
825
11.6k
  struct cpool *cpool = cpool_get_instance(data);
826
11.6k
  struct cpool_do_conn_ctx dctx;
827
828
11.6k
  if(!cpool)
829
0
    return;
830
11.6k
  dctx.id = conn_id;
831
11.6k
  dctx.cb = cb;
832
11.6k
  dctx.cbdata = cbdata;
833
11.6k
  CPOOL_LOCK(cpool, data);
834
11.6k
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
835
11.6k
  CPOOL_UNLOCK(cpool, data);
836
11.6k
}
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
114k
{
842
114k
  struct cpool *cpool = cpool_get_instance(data);
843
114k
  if(cpool) {
844
114k
    CPOOL_LOCK(cpool, data);
845
114k
    cb(conn, data, cbdata);
846
114k
    CPOOL_UNLOCK(cpool, data);
847
114k
  }
848
0
  else
849
0
    cb(conn, data, cbdata);
850
114k
}
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