Coverage Report

Created: 2025-08-29 06:10

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