Coverage Report

Created: 2025-07-11 06:33

/src/PROJ/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
0
#define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
55
56
#define CPOOL_LOCK(c,d)                                                 \
57
0
  do {                                                                  \
58
0
    if((c)) {                                                           \
59
0
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
60
0
        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
61
0
                        CURL_LOCK_ACCESS_SINGLE);                       \
62
0
      DEBUGASSERT(!(c)->locked);                                        \
63
0
      (c)->locked = TRUE;                                               \
64
0
    }                                                                   \
65
0
  } while(0)
66
67
#define CPOOL_UNLOCK(c,d)                                                 \
68
0
  do {                                                                  \
69
0
    if((c)) {                                                           \
70
0
      DEBUGASSERT((c)->locked);                                         \
71
0
      (c)->locked = FALSE;                                              \
72
0
      if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
73
0
        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
74
0
    }                                                                   \
75
0
  } 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
0
{
93
0
  struct cpool_bundle *bundle;
94
0
  size_t dest_len = strlen(dest);
95
96
0
  bundle = calloc(1, sizeof(*bundle) + dest_len);
97
0
  if(!bundle)
98
0
    return NULL;
99
0
  Curl_llist_init(&bundle->conns, NULL);
100
0
  bundle->dest_len = dest_len + 1;
101
0
  memcpy(bundle->dest, dest, bundle->dest_len);
102
0
  return bundle;
103
0
}
104
105
static void cpool_bundle_destroy(struct cpool_bundle *bundle)
106
0
{
107
0
  DEBUGASSERT(!Curl_llist_count(&bundle->conns));
108
0
  free(bundle);
109
0
}
110
111
/* Add a connection to a bundle */
112
static void cpool_bundle_add(struct cpool_bundle *bundle,
113
                             struct connectdata *conn)
114
0
{
115
0
  DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
116
0
  Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
117
0
  conn->bits.in_cpool = TRUE;
118
0
}
119
120
/* Remove a connection from a bundle */
121
static void cpool_bundle_remove(struct cpool_bundle *bundle,
122
                                struct connectdata *conn)
123
0
{
124
0
  (void)bundle;
125
0
  DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
126
0
  Curl_node_remove(&conn->cpool_node);
127
0
  conn->bits.in_cpool = FALSE;
128
0
}
129
130
static void cpool_bundle_free_entry(void *freethis)
131
0
{
132
0
  cpool_bundle_destroy((struct cpool_bundle *)freethis);
133
0
}
134
135
void Curl_cpool_init(struct cpool *cpool,
136
                     struct Curl_easy *idata,
137
                     struct Curl_share *share,
138
                     size_t size)
139
0
{
140
0
  Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
141
0
                 curlx_str_key_compare, cpool_bundle_free_entry);
142
143
0
  DEBUGASSERT(idata);
144
145
0
  cpool->idata = idata;
146
0
  cpool->share = share;
147
0
  cpool->initialised = TRUE;
148
0
}
149
150
/* Return the "first" connection in the pool or NULL. */
151
static struct connectdata *cpool_get_first(struct cpool *cpool)
152
0
{
153
0
  struct Curl_hash_iterator iter;
154
0
  struct Curl_hash_element *he;
155
0
  struct cpool_bundle *bundle;
156
0
  struct Curl_llist_node *conn_node;
157
158
0
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
159
0
  for(he = Curl_hash_next_element(&iter); he;
160
0
      he = Curl_hash_next_element(&iter)) {
161
0
    bundle = he->ptr;
162
0
    conn_node = Curl_llist_head(&bundle->conns);
163
0
    if(conn_node)
164
0
      return Curl_node_elem(conn_node);
165
0
  }
166
0
  return NULL;
167
0
}
168
169
170
static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
171
                                              struct connectdata *conn)
172
0
{
173
0
  return Curl_hash_pick(&cpool->dest2bundle,
174
0
                        conn->destination, strlen(conn->destination) + 1);
175
0
}
176
177
178
static void cpool_remove_bundle(struct cpool *cpool,
179
                                struct cpool_bundle *bundle)
180
0
{
181
0
  if(!cpool)
182
0
    return;
183
0
  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
184
0
}
185
186
187
static void cpool_remove_conn(struct cpool *cpool,
188
                              struct connectdata *conn)
189
0
{
190
0
  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
191
0
  DEBUGASSERT(cpool);
192
0
  if(list) {
193
    /* The connection is certainly in the pool, but where? */
194
0
    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
195
0
    if(bundle && (list == &bundle->conns)) {
196
0
      cpool_bundle_remove(bundle, conn);
197
0
      if(!Curl_llist_count(&bundle->conns))
198
0
        cpool_remove_bundle(cpool, bundle);
199
0
      conn->bits.in_cpool = FALSE;
200
0
      cpool->num_conn--;
201
0
    }
202
0
    else {
203
      /* Should have been in the bundle list */
204
0
      DEBUGASSERT(NULL);
205
0
    }
206
0
  }
207
0
}
208
209
void Curl_cpool_destroy(struct cpool *cpool)
210
0
{
211
0
  if(cpool && cpool->initialised && cpool->idata) {
212
0
    struct connectdata *conn;
213
0
    SIGPIPE_VARIABLE(pipe_st);
214
215
0
    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
216
0
               cpool->share ? "[SHARE] " : "", cpool->num_conn);
217
    /* Move all connections to the shutdown list */
218
0
    sigpipe_init(&pipe_st);
219
0
    CPOOL_LOCK(cpool, cpool->idata);
220
0
    conn = cpool_get_first(cpool);
221
0
    while(conn) {
222
0
      cpool_remove_conn(cpool, conn);
223
0
      sigpipe_apply(cpool->idata, &pipe_st);
224
0
      connclose(conn, "kill all");
225
0
      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
226
0
      conn = cpool_get_first(cpool);
227
0
    }
228
0
    CPOOL_UNLOCK(cpool, cpool->idata);
229
0
    sigpipe_restore(&pipe_st);
230
0
    Curl_hash_destroy(&cpool->dest2bundle);
231
0
  }
232
0
}
233
234
static struct cpool *cpool_get_instance(struct Curl_easy *data)
235
0
{
236
0
  if(data) {
237
0
    if(CURL_SHARE_KEEP_CONNECT(data->share))
238
0
      return &data->share->cpool;
239
0
    else if(data->multi_easy)
240
0
      return &data->multi_easy->cpool;
241
0
    else if(data->multi)
242
0
      return &data->multi->cpool;
243
0
  }
244
0
  return NULL;
245
0
}
246
247
void Curl_cpool_xfer_init(struct Curl_easy *data)
248
0
{
249
0
  struct cpool *cpool = cpool_get_instance(data);
250
251
0
  DEBUGASSERT(cpool);
252
0
  if(cpool) {
253
0
    CPOOL_LOCK(cpool, data);
254
    /* the identifier inside the connection cache */
255
0
    data->id = cpool->next_easy_id++;
256
0
    if(cpool->next_easy_id <= 0)
257
0
      cpool->next_easy_id = 0;
258
0
    data->state.lastconnect_id = -1;
259
260
0
    CPOOL_UNLOCK(cpool, data);
261
0
  }
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
0
}
268
269
static struct cpool_bundle *
270
cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
271
0
{
272
0
  struct cpool_bundle *bundle;
273
274
0
  bundle = cpool_bundle_create(conn->destination);
275
0
  if(!bundle)
276
0
    return NULL;
277
278
0
  if(!Curl_hash_add(&cpool->dest2bundle,
279
0
                    bundle->dest, bundle->dest_len, bundle)) {
280
0
    cpool_bundle_destroy(bundle);
281
0
    return NULL;
282
0
  }
283
0
  return bundle;
284
0
}
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
0
{
354
0
  struct cpool *cpool = cpool_get_instance(data);
355
0
  struct cpool_bundle *bundle;
356
0
  size_t dest_limit = 0;
357
0
  size_t total_limit = 0;
358
0
  size_t shutdowns;
359
0
  int result = CPOOL_LIMIT_OK;
360
361
0
  if(!cpool)
362
0
    return CPOOL_LIMIT_OK;
363
364
0
  if(cpool->idata->multi) {
365
0
    dest_limit = cpool->idata->multi->max_host_connections;
366
0
    total_limit = cpool->idata->multi->max_total_connections;
367
0
  }
368
369
0
  if(!dest_limit && !total_limit)
370
0
    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
0
{
448
0
  CURLcode result = CURLE_OK;
449
0
  struct cpool_bundle *bundle = NULL;
450
0
  struct cpool *cpool = cpool_get_instance(data);
451
0
  DEBUGASSERT(conn);
452
453
0
  DEBUGASSERT(cpool);
454
0
  if(!cpool)
455
0
    return CURLE_FAILED_INIT;
456
457
0
  CPOOL_LOCK(cpool, data);
458
0
  bundle = cpool_find_bundle(cpool, conn);
459
0
  if(!bundle) {
460
0
    bundle = cpool_add_bundle(cpool, conn);
461
0
    if(!bundle) {
462
0
      result = CURLE_OUT_OF_MEMORY;
463
0
      goto out;
464
0
    }
465
0
  }
466
467
0
  cpool_bundle_add(bundle, conn);
468
0
  conn->connection_id = cpool->next_connection_id++;
469
0
  cpool->num_conn++;
470
0
  CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". "
471
0
             "The cache now contains %zu members",
472
0
             conn->connection_id, cpool->num_conn);
473
0
out:
474
0
  CPOOL_UNLOCK(cpool, data);
475
476
0
  return result;
477
0
}
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
0
{
497
0
  struct Curl_hash_iterator iter;
498
0
  struct Curl_hash_element *he;
499
500
0
  if(!cpool)
501
0
    return FALSE;
502
503
0
  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
504
505
0
  he = Curl_hash_next_element(&iter);
506
0
  while(he) {
507
0
    struct Curl_llist_node *curr;
508
0
    struct cpool_bundle *bundle = he->ptr;
509
0
    he = Curl_hash_next_element(&iter);
510
511
0
    curr = Curl_llist_head(&bundle->conns);
512
0
    while(curr) {
513
      /* Yes, we need to update curr before calling func(), because func()
514
         might decide to remove the connection */
515
0
      struct connectdata *conn = Curl_node_elem(curr);
516
0
      curr = Curl_node_next(curr);
517
518
0
      if(1 == func(data, conn, param)) {
519
0
        return TRUE;
520
0
      }
521
0
    }
522
0
  }
523
0
  return FALSE;
524
0
}
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
0
{
535
0
  unsigned int maxconnects = !data->multi->maxconnects ?
536
0
    (Curl_multi_xfers_running(data->multi) * 4) : data->multi->maxconnects;
537
0
  struct connectdata *oldest_idle = NULL;
538
0
  struct cpool *cpool = cpool_get_instance(data);
539
0
  bool kept = TRUE;
540
541
0
  conn->lastused = curlx_now(); /* it was used up until now */
542
0
  if(cpool && maxconnects) {
543
    /* may be called form a callback already under lock */
544
0
    bool do_lock = !CPOOL_IS_LOCKED(cpool);
545
0
    if(do_lock)
546
0
      CPOOL_LOCK(cpool, data);
547
0
    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
0
    if(do_lock)
558
0
      CPOOL_UNLOCK(cpool, data);
559
0
  }
560
561
0
  return kept;
562
0
}
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
0
{
570
0
  struct cpool *cpool = cpool_get_instance(data);
571
0
  struct cpool_bundle *bundle;
572
0
  bool result = FALSE;
573
574
0
  DEBUGASSERT(cpool);
575
0
  DEBUGASSERT(conn_cb);
576
0
  if(!cpool)
577
0
    return FALSE;
578
579
0
  CPOOL_LOCK(cpool, data);
580
0
  bundle = Curl_hash_pick(&cpool->dest2bundle,
581
0
                          CURL_UNCONST(destination),
582
0
                          strlen(destination) + 1);
583
0
  if(bundle) {
584
0
    struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
585
0
    while(curr) {
586
0
      struct connectdata *conn = Curl_node_elem(curr);
587
      /* Get next node now. callback might discard current */
588
0
      curr = Curl_node_next(curr);
589
590
0
      if(conn_cb(conn, userdata)) {
591
0
        result = TRUE;
592
0
        break;
593
0
      }
594
0
    }
595
0
  }
596
597
0
  if(done_cb) {
598
0
    result = done_cb(result, userdata);
599
0
  }
600
0
  CPOOL_UNLOCK(cpool, data);
601
0
  return result;
602
0
}
603
604
static void cpool_discard_conn(struct cpool *cpool,
605
                               struct Curl_easy *data,
606
                               struct connectdata *conn,
607
                               bool aborted)
608
0
{
609
0
  bool done = FALSE;
610
611
0
  DEBUGASSERT(data);
612
0
  DEBUGASSERT(!data->conn);
613
0
  DEBUGASSERT(cpool);
614
0
  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
0
  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
0
  if(conn->connect_only)
630
0
    aborted = TRUE;
631
0
  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
0
  if(aborted)
639
0
    done = TRUE;
640
0
  if(!done) {
641
    /* Attempt to shutdown the connection right away. */
642
0
    Curl_cshutdn_run_once(cpool->idata, conn, &done);
643
0
  }
644
645
0
  if(done || !data->multi)
646
0
    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
647
0
  else
648
0
    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
649
0
}
650
651
void Curl_conn_terminate(struct Curl_easy *data,
652
                         struct connectdata *conn,
653
                         bool aborted)
654
0
{
655
0
  struct cpool *cpool = cpool_get_instance(data);
656
0
  bool do_lock;
657
658
0
  DEBUGASSERT(cpool);
659
0
  DEBUGASSERT(data && !data->conn);
660
0
  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
0
  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
0
  do_lock = !CPOOL_IS_LOCKED(cpool);
674
0
  if(do_lock)
675
0
    CPOOL_LOCK(cpool, data);
676
677
0
  if(conn->bits.in_cpool) {
678
0
    cpool_remove_conn(cpool, conn);
679
0
    DEBUGASSERT(!conn->bits.in_cpool);
680
0
  }
681
682
  /* treat the connection as aborted in CONNECT_ONLY situations,
683
   * so no graceful shutdown is attempted. */
684
0
  if(conn->connect_only)
685
0
    aborted = TRUE;
686
687
0
  if(data->multi) {
688
    /* Add it to the multi's cpool for shutdown handling */
689
0
    infof(data, "%s connection #%" FMT_OFF_T,
690
0
          aborted ? "closing" : "shutting down", conn->connection_id);
691
0
    cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
692
0
  }
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
0
  if(do_lock)
700
0
    CPOOL_UNLOCK(cpool, data);
701
0
}
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(Curl_conn_seems_dead(conn, data, &rctx->now)) {
713
    /* stop the iteration here, pass back the connection that was pruned */
714
0
    Curl_conn_terminate(data, conn, FALSE);
715
0
    return 1;
716
0
  }
717
0
  return 0; /* continue iteration */
718
0
}
719
720
/*
721
 * This function scans the data's connection pool for half-open/dead
722
 * connections, closes and removes them.
723
 * The cleanup is done at most once per second.
724
 *
725
 * When called, this transfer has no connection attached.
726
 */
727
void Curl_cpool_prune_dead(struct Curl_easy *data)
728
0
{
729
0
  struct cpool *cpool = cpool_get_instance(data);
730
0
  struct cpool_reaper_ctx rctx;
731
0
  timediff_t elapsed;
732
733
0
  if(!cpool)
734
0
    return;
735
736
0
  rctx.now = curlx_now();
737
0
  CPOOL_LOCK(cpool, data);
738
0
  elapsed = curlx_timediff(rctx.now, cpool->last_cleanup);
739
740
0
  if(elapsed >= 1000L) {
741
0
    while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb))
742
0
      ;
743
0
    cpool->last_cleanup = rctx.now;
744
0
  }
745
0
  CPOOL_UNLOCK(cpool, data);
746
0
}
747
748
static int conn_upkeep(struct Curl_easy *data,
749
                       struct connectdata *conn,
750
                       void *param)
751
0
{
752
0
  struct curltime *now = param;
753
0
  Curl_conn_upkeep(data, conn, now);
754
0
  return 0; /* continue iteration */
755
0
}
756
757
CURLcode Curl_cpool_upkeep(void *data)
758
0
{
759
0
  struct cpool *cpool = cpool_get_instance(data);
760
0
  struct curltime now = curlx_now();
761
762
0
  if(!cpool)
763
0
    return CURLE_OK;
764
765
0
  CPOOL_LOCK(cpool, data);
766
0
  cpool_foreach(data, cpool, &now, conn_upkeep);
767
0
  CPOOL_UNLOCK(cpool, data);
768
0
  return CURLE_OK;
769
0
}
770
771
struct cpool_find_ctx {
772
  curl_off_t id;
773
  struct connectdata *conn;
774
};
775
776
static int cpool_find_conn(struct Curl_easy *data,
777
                           struct connectdata *conn, void *param)
778
0
{
779
0
  struct cpool_find_ctx *fctx = param;
780
0
  (void)data;
781
0
  if(conn->connection_id == fctx->id) {
782
0
    fctx->conn = conn;
783
0
    return 1;
784
0
  }
785
0
  return 0;
786
0
}
787
788
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
789
                                        curl_off_t conn_id)
790
0
{
791
0
  struct cpool *cpool = cpool_get_instance(data);
792
0
  struct cpool_find_ctx fctx;
793
794
0
  if(!cpool)
795
0
    return NULL;
796
0
  fctx.id = conn_id;
797
0
  fctx.conn = NULL;
798
0
  CPOOL_LOCK(cpool, data);
799
0
  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
800
0
  CPOOL_UNLOCK(cpool, data);
801
0
  return fctx.conn;
802
0
}
803
804
struct cpool_do_conn_ctx {
805
  curl_off_t id;
806
  Curl_cpool_conn_do_cb *cb;
807
  void *cbdata;
808
};
809
810
static int cpool_do_conn(struct Curl_easy *data,
811
                         struct connectdata *conn, void *param)
812
0
{
813
0
  struct cpool_do_conn_ctx *dctx = param;
814
0
  (void)data;
815
0
  if(conn->connection_id == dctx->id) {
816
0
    dctx->cb(conn, data, dctx->cbdata);
817
0
    return 1;
818
0
  }
819
0
  return 0;
820
0
}
821
822
void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id,
823
                         Curl_cpool_conn_do_cb *cb, void *cbdata)
824
0
{
825
0
  struct cpool *cpool = cpool_get_instance(data);
826
0
  struct cpool_do_conn_ctx dctx;
827
828
0
  if(!cpool)
829
0
    return;
830
0
  dctx.id = conn_id;
831
0
  dctx.cb = cb;
832
0
  dctx.cbdata = cbdata;
833
0
  CPOOL_LOCK(cpool, data);
834
0
  cpool_foreach(data, cpool, &dctx, cpool_do_conn);
835
0
  CPOOL_UNLOCK(cpool, data);
836
0
}
837
838
void Curl_cpool_do_locked(struct Curl_easy *data,
839
                          struct connectdata *conn,
840
                          Curl_cpool_conn_do_cb *cb, void *cbdata)
841
0
{
842
0
  struct cpool *cpool = cpool_get_instance(data);
843
0
  if(cpool) {
844
0
    CPOOL_LOCK(cpool, data);
845
0
    cb(conn, data, cbdata);
846
0
    CPOOL_UNLOCK(cpool, data);
847
0
  }
848
0
  else
849
0
    cb(conn, data, cbdata);
850
0
}
851
852
#if 0
853
/* Useful for debugging the connection pool */
854
void Curl_cpool_print(struct cpool *cpool)
855
{
856
  struct Curl_hash_iterator iter;
857
  struct Curl_llist_node *curr;
858
  struct Curl_hash_element *he;
859
860
  if(!cpool)
861
    return;
862
863
  fprintf(stderr, "=Bundle cache=\n");
864
865
  Curl_hash_start_iterate(cpool->dest2bundle, &iter);
866
867
  he = Curl_hash_next_element(&iter);
868
  while(he) {
869
    struct cpool_bundle *bundle;
870
    struct connectdata *conn;
871
872
    bundle = he->ptr;
873
874
    fprintf(stderr, "%s -", he->key);
875
    curr = Curl_llist_head(bundle->conns);
876
    while(curr) {
877
      conn = Curl_node_elem(curr);
878
879
      fprintf(stderr, " [%p %d]", (void *)conn, conn->refcount);
880
      curr = Curl_node_next(curr);
881
    }
882
    fprintf(stderr, "\n");
883
884
    he = Curl_hash_next_element(&iter);
885
  }
886
}
887
#endif