Coverage Report

Created: 2025-08-11 09:23

/src/gdal/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
521k
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
55
56
#define CPOOL_LOCK(c,d)                                                 \
57
1.64M
  do {                                                                  \
58
1.64M
    if((c)) {                                                           \
59
1.64M
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
60
1.64M
        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
61
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
62
1.64M
      DEBUGASSERT(!(c)->locked);                                        \
63
1.64M
      (c)->locked = TRUE;                                               \
64
1.64M
    }                                                                   \
65
1.64M
  } while(0)
66
67
#define CPOOL_UNLOCK(c,d)                                                 \
68
1.64M
  do {                                                                  \
69
1.64M
    if((c)) {                                                           \
70
1.64M
      DEBUGASSERT((c)->locked);                                         \
71
1.64M
      (c)->locked = FALSE;                                              \
72
1.64M
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
73
1.64M
        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
74
1.64M
    }                                                                   \
75
1.64M
  } 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
160k
{
93
160k
  struct cpool_bundle *bundle;
94
160k
  size_t dest_len = strlen(dest);
95
96
160k
  bundle = calloc(1, sizeof(*bundle) + dest_len);
97
160k
  if(!bundle)
98
0
    return NULL;
99
160k
  Curl_llist_init(&bundle->conns, NULL);
100
160k
  bundle->dest_len = dest_len + 1;
101
160k
  memcpy(bundle->dest, dest, bundle->dest_len);
102
160k
  return bundle;
103
160k
}
104
105
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
106
160k
{
107
160k
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
108
160k
  free(bundle);
109
160k
}
110
111
/* Add a connection to a bundle */
112
static void cpool_bundle_add(struct cpool_bundle *bundle,
113
                             struct connectdata *conn)
114
161k
{
115
161k
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
116
161k
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
117
161k
  conn->bits.in_cpool = TRUE;
118
161k
}
119
120
/* Remove a connection from a bundle */
121
static void cpool_bundle_remove(struct cpool_bundle *bundle,
122
                                struct connectdata *conn)
123
161k
{
124
161k
  (void)bundle;
125
161k
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
126
161k
  Curl_node_remove(&conn->cpool_node);
127
161k
  conn->bits.in_cpool = FALSE;
128
161k
}
129
130
static void cpool_bundle_free_entry(void *freethis)
131
160k
{
132
160k
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
133
160k
}
134
135
void Curl_cpool_init(struct cpool *cpool,
136
                     struct Curl_easy *idata,
137
                     struct Curl_share *share,
138
                     size_t size)
139
133k
{
140
133k
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
141
133k
                 curlx_str_key_compare, cpool_bundle_free_entry);
142
143
133k
  DEBUGASSERT(idata);
144
145
133k
  cpool->idata = idata;
146
133k
  cpool->share = share;
147
133k
  cpool->initialised = TRUE;
148
133k
}
149
150
/* Return the "first" connection in the pool or NULL. */
151
static struct connectdata *cpool_get_first(struct cpool *cpool)
152
133k
{
153
133k
  struct Curl_hash_iterator iter;
154
133k
  struct Curl_hash_element *he;
155
133k
  struct cpool_bundle *bundle;
156
133k
  struct Curl_llist_node *conn_node;
157
158
133k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
159
133k
  for(he = Curl_hash_next_element(&iter); he;
160
133k
      he = Curl_hash_next_element(&iter)) {
161
501
    bundle = he->ptr;
162
501
    conn_node = Curl_llist_head(&bundle->conns);
163
501
    if(conn_node)
164
501
      return Curl_node_elem(conn_node);
165
501
  }
166
133k
  return NULL;
167
133k
}
168
169
170
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
171
                                              struct connectdata *conn)
172
322k
{
173
322k
  return Curl_hash_pick(&cpool->dest2bundle,
174
322k
                        conn->destination, strlen(conn->destination) + 1);
175
322k
}
176
177
178
static void cpool_remove_bundle(struct cpool *cpool,
179
                                struct cpool_bundle *bundle)
180
160k
{
181
160k
  if(!cpool)
182
0
    return;
183
160k
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
184
160k
}
185
186
187
static void cpool_remove_conn(struct cpool *cpool,
188
                              struct connectdata *conn)
189
161k
{
190
161k
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
191
161k
  DEBUGASSERT(cpool);
192
161k
  if(list) {
193
    /* The connection is certainly in the pool, but where? */
194
161k
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
195
161k
    if(bundle && (list == &bundle->conns)) {
196
161k
      cpool_bundle_remove(bundle, conn);
197
161k
      if(!Curl_llist_count(&bundle->conns))
198
160k
        cpool_remove_bundle(cpool, bundle);
199
161k
      conn->bits.in_cpool = FALSE;
200
161k
      cpool->num_conn--;
201
161k
    }
202
0
    else {
203
      /* Should have been in the bundle list */
204
0
      DEBUGASSERT(NULL);
205
0
    }
206
161k
  }
207
161k
}
208
209
void Curl_cpool_destroy(struct cpool *cpool)
210
133k
{
211
133k
  if(cpool && cpool->initialised && cpool->idata) {
212
133k
    struct connectdata *conn;
213
133k
    SIGPIPE_VARIABLE(pipe_st);
214
215
133k
    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
216
133k
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
217
    /* Move all connections to the shutdown list */
218
133k
    sigpipe_init(&pipe_st);
219
133k
    CPOOL_LOCK(cpool, cpool->idata);
220
133k
    conn = cpool_get_first(cpool);
221
133k
    while(conn) {
222
501
      cpool_remove_conn(cpool, conn);
223
501
      sigpipe_apply(cpool->idata, &pipe_st);
224
501
      connclose(conn, "kill all");
225
501
      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
226
501
      conn = cpool_get_first(cpool);
227
501
    }
228
133k
    CPOOL_UNLOCK(cpool, cpool->idata);
229
133k
    sigpipe_restore(&pipe_st);
230
133k
    Curl_hash_destroy(&cpool->dest2bundle);
231
133k
  }
232
133k
}
233
234
static struct cpool *cpool_get_instance(struct Curl_easy *data)
235
1.69M
{
236
1.69M
  if(data) {
237
1.69M
    if(CURL_SHARE_KEEP_CONNECT(data->share))
238
0
      return &data->share->cpool;
239
1.69M
    else if(data->multi_easy)
240
415k
      return &data->multi_easy->cpool;
241
1.28M
    else if(data->multi)
242
1.28M
      return &data->multi->cpool;
243
1.69M
  }
244
0
  return NULL;
245
1.69M
}
246
247
void Curl_cpool_xfer_init(struct Curl_easy *data)
248
521k
{
249
521k
  struct cpool *cpool = cpool_get_instance(data);
250
251
521k
  DEBUGASSERT(cpool);
252
521k
  if(cpool) {
253
521k
    CPOOL_LOCK(cpool, data);
254
    /* the identifier inside the connection cache */
255
521k
    data->id = cpool->next_easy_id++;
256
521k
    if(cpool->next_easy_id <= 0)
257
0
      cpool->next_easy_id = 0;
258
521k
    data->state.lastconnect_id = -1;
259
260
521k
    CPOOL_UNLOCK(cpool, data);
261
521k
  }
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
521k
}
268
269
static struct cpool_bundle *
270
cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
271
160k
{
272
160k
  struct cpool_bundle *bundle;
273
274
160k
  bundle = cpool_bundle_create(conn->destination);
275
160k
  if(!bundle)
276
0
    return NULL;
277
278
160k
  if(!Curl_hash_add(&cpool->dest2bundle,
279
160k
                    bundle->dest, bundle->dest_len, bundle)) {
280
0
    cpool_bundle_destroy(bundle);
281
0
    return NULL;
282
0
  }
283
160k
  return bundle;
284
160k
}
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
1
{
317
1
  struct Curl_hash_iterator iter;
318
1
  struct Curl_llist_node *curr;
319
1
  struct Curl_hash_element *he;
320
1
  struct connectdata *oldest_idle = NULL;
321
1
  struct cpool_bundle *bundle;
322
1
  struct curltime now;
323
1
  timediff_t highscore =- 1;
324
1
  timediff_t score;
325
326
1
  now = curlx_now();
327
1
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
328
329
6
  for(he = Curl_hash_next_element(&iter); he;
330
5
      he = Curl_hash_next_element(&iter)) {
331
5
    struct connectdata *conn;
332
5
    bundle = he->ptr;
333
334
10
    for(curr = Curl_llist_head(&bundle->conns); curr;
335
5
        curr = Curl_node_next(curr)) {
336
5
      conn = Curl_node_elem(curr);
337
5
      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
5
      score = curlx_timediff(now, conn->lastused);
341
5
      if(score > highscore) {
342
1
        highscore = score;
343
1
        oldest_idle = conn;
344
1
      }
345
5
    }
346
5
  }
347
1
  return oldest_idle;
348
1
}
349
350
351
int Curl_cpool_check_limits(struct Curl_easy *data,
352
                            struct connectdata *conn)
353
137k
{
354
137k
  struct cpool *cpool = cpool_get_instance(data);
355
137k
  struct cpool_bundle *bundle;
356
137k
  size_t dest_limit = 0;
357
137k
  size_t total_limit = 0;
358
137k
  size_t shutdowns;
359
137k
  int result = CPOOL_LIMIT_OK;
360
361
137k
  if(!cpool)
362
0
    return CPOOL_LIMIT_OK;
363
364
137k
  if(cpool->idata->multi) {
365
137k
    dest_limit = cpool->idata->multi->max_host_connections;
366
137k
    total_limit = cpool->idata->multi->max_total_connections;
367
137k
  }
368
369
137k
  if(!dest_limit && !total_limit)
370
137k
    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
161k
{
448
161k
  CURLcode result = CURLE_OK;
449
161k
  struct cpool_bundle *bundle = NULL;
450
161k
  struct cpool *cpool = cpool_get_instance(data);
451
161k
  DEBUGASSERT(conn);
452
453
161k
  DEBUGASSERT(cpool);
454
161k
  if(!cpool)
455
0
    return CURLE_FAILED_INIT;
456
457
161k
  CPOOL_LOCK(cpool, data);
458
161k
  bundle = cpool_find_bundle(cpool, conn);
459
161k
  if(!bundle) {
460
160k
    bundle = cpool_add_bundle(cpool, conn);
461
160k
    if(!bundle) {
462
0
      result = CURLE_OUT_OF_MEMORY;
463
0
      goto out;
464
0
    }
465
160k
  }
466
467
161k
  cpool_bundle_add(bundle, conn);
468
161k
  conn->connection_id = cpool->next_connection_id++;
469
161k
  cpool->num_conn++;
470
161k
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
471
161k
             "The cache now contains %zu members",
472
161k
             conn->connection_id, cpool->num_conn);
473
161k
out:
474
161k
  CPOOL_UNLOCK(cpool, data);
475
476
161k
  return result;
477
161k
}
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
78.6k
{
497
78.6k
  struct Curl_hash_iterator iter;
498
78.6k
  struct Curl_hash_element *he;
499
500
78.6k
  if(!cpool)
501
0
    return FALSE;
502
503
78.6k
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
504
505
78.6k
  he = Curl_hash_next_element(&iter);
506
80.5k
  while(he) {
507
11.5k
    struct Curl_llist_node *curr;
508
11.5k
    struct cpool_bundle *bundle = he->ptr;
509
11.5k
    he = Curl_hash_next_element(&iter);
510
511
11.5k
    curr = Curl_llist_head(&bundle->conns);
512
13.6k
    while(curr) {
513
      /* Yes, we need to update curr before calling func(), because func()
514
         might decide to remove the connection */
515
11.7k
      struct connectdata *conn = Curl_node_elem(curr);
516
11.7k
      curr = Curl_node_next(curr);
517
518
11.7k
      if(func(data, conn, param) == 1) {
519
9.75k
        return TRUE;
520
9.75k
      }
521
11.7k
    }
522
11.5k
  }
523
68.9k
  return FALSE;
524
78.6k
}
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
9.74k
{
535
9.74k
  unsigned int maxconnects = !data->multi->maxconnects ?
536
9.29k
    (Curl_multi_xfers_running(data->multi) * 4) : data->multi->maxconnects;
537
9.74k
  struct connectdata *oldest_idle = NULL;
538
9.74k
  struct cpool *cpool = cpool_get_instance(data);
539
9.74k
  bool kept = TRUE;
540
541
9.74k
  conn->lastused = curlx_now(); /* it was used up until now */
542
9.74k
  if(cpool && maxconnects) {
543
    /* may be called form a callback already under lock */
544
9.74k
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
545
9.74k
    if(do_lock)
546
0
      CPOOL_LOCK(cpool, data);
547
9.74k
    if(cpool->num_conn > maxconnects) {
548
1
      infof(data, "Connection pool is full, closing the oldest of %zu/%u",
549
1
            cpool->num_conn, maxconnects);
550
551
1
      oldest_idle = cpool_get_oldest_idle(cpool);
552
1
      kept = (oldest_idle != conn);
553
1
      if(oldest_idle) {
554
1
        Curl_conn_terminate(data, oldest_idle, FALSE);
555
1
      }
556
1
    }
557
9.74k
    if(do_lock)
558
0
      CPOOL_UNLOCK(cpool, data);
559
9.74k
  }
560
561
9.74k
  return kept;
562
9.74k
}
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
146k
{
570
146k
  struct cpool *cpool = cpool_get_instance(data);
571
146k
  struct cpool_bundle *bundle;
572
146k
  bool result = FALSE;
573
574
146k
  DEBUGASSERT(cpool);
575
146k
  DEBUGASSERT(conn_cb);
576
146k
  if(!cpool)
577
0
    return FALSE;
578
579
146k
  CPOOL_LOCK(cpool, data);
580
146k
  bundle = Curl_hash_pick(&cpool->dest2bundle,
581
146k
                          CURL_UNCONST(destination),
582
146k
                          strlen(destination) + 1);
583
146k
  if(bundle) {
584
9.96k
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
585
10.8k
    while(curr) {
586
10.0k
      struct connectdata *conn = Curl_node_elem(curr);
587
      /* Get next node now. callback might discard current */
588
10.0k
      curr = Curl_node_next(curr);
589
590
10.0k
      if(conn_cb(conn, userdata)) {
591
9.21k
        result = TRUE;
592
9.21k
        break;
593
9.21k
      }
594
10.0k
    }
595
9.96k
  }
596
597
146k
  if(done_cb) {
598
146k
    result = done_cb(result, userdata);
599
146k
  }
600
146k
  CPOOL_UNLOCK(cpool, data);
601
146k
  return result;
602
146k
}
603
604
static void cpool_discard_conn(struct cpool *cpool,
605
                               struct Curl_easy *data,
606
                               struct connectdata *conn,
607
                               bool aborted)
608
512k
{
609
512k
  bool done = FALSE;
610
611
512k
  DEBUGASSERT(data);
612
512k
  DEBUGASSERT(!data->conn);
613
512k
  DEBUGASSERT(cpool);
614
512k
  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
512k
  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
512k
  if(conn->connect_only)
630
0
    aborted = TRUE;
631
512k
  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
512k
  if(aborted)
639
450k
    done = TRUE;
640
512k
  if(!done) {
641
    /* Attempt to shutdown the connection right away. */
642
62.2k
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
643
62.2k
  }
644
645
512k
  if(done || !data->multi)
646
511k
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
647
455
  else
648
455
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
649
512k
}
650
651
void Curl_conn_terminate(struct Curl_easy *data,
652
                         struct connectdata *conn,
653
                         bool aborted)
654
511k
{
655
511k
  struct cpool *cpool = cpool_get_instance(data);
656
511k
  bool do_lock;
657
658
511k
  DEBUGASSERT(cpool);
659
511k
  DEBUGASSERT(data && !data->conn);
660
511k
  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
511k
  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
511k
  do_lock = !CPOOL_IS_LOCKED(cpool);
674
511k
  if(do_lock)
675
469k
    CPOOL_LOCK(cpool, data);
676
677
511k
  if(conn->bits.in_cpool) {
678
160k
    cpool_remove_conn(cpool, conn);
679
160k
    DEBUGASSERT(!conn->bits.in_cpool);
680
160k
  }
681
682
  /* treat the connection as aborted in CONNECT_ONLY situations,
683
   * so no graceful shutdown is attempted. */
684
511k
  if(conn->connect_only)
685
0
    aborted = TRUE;
686
687
511k
  if(data->multi) {
688
    /* Add it to the multi's cpool for shutdown handling */
689
511k
    infof(data, "%s connection #%" FMT_OFF_T,
690
511k
          aborted ? "closing" : "shutting down", conn->connection_id);
691
511k
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
692
511k
  }
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
511k
  if(do_lock)
700
469k
    CPOOL_UNLOCK(cpool, data);
701
511k
}
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
1.85k
{
711
1.85k
  struct cpool_reaper_ctx *rctx = param;
712
1.85k
  if((!CONN_INUSE(conn) && conn->bits.no_reuse) ||
713
1.85k
     Curl_conn_seems_dead(conn, data, &rctx->now)) {
714
    /* stop the iteration here, pass back the connection that was pruned */
715
23
    Curl_conn_terminate(data, conn, FALSE);
716
23
    return 1;
717
23
  }
718
1.83k
  return 0; /* continue iteration */
719
1.85k
}
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
146k
{
730
146k
  struct cpool *cpool = cpool_get_instance(data);
731
146k
  struct cpool_reaper_ctx rctx;
732
146k
  timediff_t elapsed;
733
734
146k
  if(!cpool)
735
0
    return;
736
737
146k
  rctx.now = curlx_now();
738
146k
  CPOOL_LOCK(cpool, data);
739
146k
  elapsed = curlx_timediff(rctx.now, cpool->last_cleanup);
740
741
146k
  if(elapsed >= 1000L) {
742
68.9k
    while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb))
743
23
      ;
744
68.9k
    cpool->last_cleanup = rctx.now;
745
68.9k
  }
746
146k
  CPOOL_UNLOCK(cpool, data);
747
146k
}
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
0
{
780
0
  struct cpool_find_ctx *fctx = param;
781
0
  (void)data;
782
0
  if(conn->connection_id == fctx->id) {
783
0
    fctx->conn = conn;
784
0
    return 1;
785
0
  }
786
0
  return 0;
787
0
}
788
789
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
790
                                        curl_off_t conn_id)
791
0
{
792
0
  struct cpool *cpool = cpool_get_instance(data);
793
0
  struct cpool_find_ctx fctx;
794
795
0
  if(!cpool)
796
0
    return NULL;
797
0
  fctx.id = conn_id;
798
0
  fctx.conn = NULL;
799
0
  CPOOL_LOCK(cpool, data);
800
0
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
801
0
  CPOOL_UNLOCK(cpool, data);
802
0
  return fctx.conn;
803
0
}
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
9.89k
{
814
9.89k
  struct cpool_do_conn_ctx *dctx = param;
815
9.89k
  (void)data;
816
9.89k
  if(conn->connection_id == dctx->id) {
817
9.72k
    dctx->cb(conn, data, dctx->cbdata);
818
9.72k
    return 1;
819
9.72k
  }
820
170
  return 0;
821
9.89k
}
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
9.73k
{
826
9.73k
  struct cpool *cpool = cpool_get_instance(data);
827
9.73k
  struct cpool_do_conn_ctx dctx;
828
829
9.73k
  if(!cpool)
830
0
    return;
831
9.73k
  dctx.id = conn_id;
832
9.73k
  dctx.cb = cb;
833
9.73k
  dctx.cbdata = cbdata;
834
9.73k
  CPOOL_LOCK(cpool, data);
835
9.73k
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
836
9.73k
  CPOOL_UNLOCK(cpool, data);
837
9.73k
}
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
52.4k
{
843
52.4k
  struct cpool *cpool = cpool_get_instance(data);
844
52.4k
  if(cpool) {
845
52.4k
    CPOOL_LOCK(cpool, data);
846
52.4k
    cb(conn, data, cbdata);
847
52.4k
    CPOOL_UNLOCK(cpool, data);
848
52.4k
  }
849
0
  else
850
0
    cb(conn, data, cbdata);
851
52.4k
}
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