/src/curl/lib/conncache.c
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> |
9 | | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
10 | | * |
11 | | * This software is licensed as described in the file COPYING, which |
12 | | * you should have received as part of this distribution. The terms |
13 | | * are also available at https://curl.se/docs/copyright.html. |
14 | | * |
15 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
16 | | * copies of the Software, and permit persons to whom the Software is |
17 | | * furnished to do so, under the terms of the COPYING file. |
18 | | * |
19 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
20 | | * KIND, either express or implied. |
21 | | * |
22 | | * SPDX-License-Identifier: curl |
23 | | * |
24 | | ***************************************************************************/ |
25 | | #include "curl_setup.h" |
26 | | |
27 | | #include "urldata.h" |
28 | | #include "url.h" |
29 | | #include "cfilters.h" |
30 | | #include "progress.h" |
31 | | #include "multiif.h" |
32 | | #include "curl_trc.h" |
33 | | #include "cshutdn.h" |
34 | | #include "conncache.h" |
35 | | #include "curl_share.h" |
36 | | #include "sigpipe.h" |
37 | | #include "uint-spbset.h" |
38 | | |
39 | | |
40 | 4.17k | #define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) |
41 | | |
42 | | #define CPOOL_LOCK(c, d) \ |
43 | 34.2k | do { \ |
44 | 34.2k | if(c) { \ |
45 | 34.2k | if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ |
46 | 34.2k | Curl_share_lock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT, \ |
47 | 0 | CURL_LOCK_ACCESS_SINGLE); \ |
48 | 34.2k | DEBUGASSERT(!(c)->locked); \ |
49 | 34.2k | (c)->locked = TRUE; \ |
50 | 34.2k | } \ |
51 | 34.2k | } while(0) |
52 | | |
53 | | #define CPOOL_UNLOCK(c, d) \ |
54 | 34.2k | do { \ |
55 | 34.2k | if(c) { \ |
56 | 34.2k | DEBUGASSERT((c)->locked); \ |
57 | 34.2k | (c)->locked = FALSE; \ |
58 | 34.2k | if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ |
59 | 34.2k | Curl_share_unlock_share((c)->share, (d), CURL_LOCK_DATA_CONNECT); \ |
60 | 34.2k | } \ |
61 | 34.2k | } while(0) |
62 | | |
63 | | /* A set of connection pool ids to the same destination. */ |
64 | | struct cpool_bundle { |
65 | | struct uint32_spbset ids; /* bitset of connection `pool_id`s */ |
66 | | uint32_t refcount; |
67 | | }; |
68 | | |
69 | | static void cpool_bundle_unlink(struct cpool_bundle **pbundle) |
70 | 25.0k | { |
71 | 25.0k | if(*pbundle) { |
72 | 12.5k | struct cpool_bundle *bundle = *pbundle; |
73 | | |
74 | 12.5k | DEBUGASSERT(bundle->refcount); |
75 | 12.5k | *pbundle = NULL; |
76 | 12.5k | if(bundle->refcount) |
77 | 12.5k | bundle->refcount--; |
78 | 12.5k | if(!bundle->refcount) { |
79 | 4.17k | Curl_uint32_spbset_destroy(&bundle->ids); |
80 | 4.17k | curlx_free(bundle); |
81 | 4.17k | } |
82 | 12.5k | } |
83 | 25.0k | } |
84 | | |
85 | | static void cpool_bundle_link(struct cpool_bundle **pdest, |
86 | | struct cpool_bundle *src) |
87 | 16.6k | { |
88 | 16.6k | if(*pdest != src) { |
89 | 8.34k | cpool_bundle_unlink(pdest); |
90 | 8.34k | *pdest = src; |
91 | 8.34k | if(src) { |
92 | 8.34k | DEBUGASSERT(src->refcount < UINT32_MAX); |
93 | 8.34k | src->refcount++; |
94 | 8.34k | } |
95 | 8.34k | } |
96 | 16.6k | } |
97 | | |
98 | | static struct cpool_bundle *cpool_create_bundle(void) |
99 | 4.17k | { |
100 | | /* created with refcount == 1 */ |
101 | 4.17k | struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle)); |
102 | 4.17k | if(!bundle) |
103 | 0 | return NULL; |
104 | | |
105 | 4.17k | Curl_uint32_spbset_init(&bundle->ids); |
106 | 4.17k | bundle->refcount = 1; |
107 | 4.17k | return bundle; |
108 | 4.17k | } |
109 | | |
110 | | /* Add a connection to a bundle */ |
111 | | static bool cpool_bundle_add_id(struct cpool *cpool, |
112 | | struct cpool_bundle *bundle, |
113 | | const char *destination, |
114 | | uint32_t pool_id) |
115 | 4.17k | { |
116 | 4.17k | if(!Curl_uint32_spbset_add(&bundle->ids, pool_id)) |
117 | 0 | return FALSE; |
118 | 4.17k | if(Curl_uint32_spbset_count(&bundle->ids) == 1) { |
119 | | /* first one added, register bundle */ |
120 | 4.17k | struct cpool_bundle *new_ref = NULL; |
121 | 4.17k | cpool_bundle_link(&new_ref, bundle); |
122 | 4.17k | if(!Curl_hash_add(&cpool->dest2bundle, |
123 | 4.17k | destination, strlen(destination), new_ref)) { |
124 | 0 | cpool_bundle_unlink(&new_ref); |
125 | 0 | return FALSE; |
126 | 0 | } |
127 | 4.17k | } |
128 | 4.17k | return TRUE; |
129 | 4.17k | } |
130 | | |
131 | | /* Remove a connection from a bundle */ |
132 | | static void cpool_bundle_remove_id(struct cpool *cpool, |
133 | | struct cpool_bundle *bundle, |
134 | | const char *destination, |
135 | | uint32_t pool_id) |
136 | 4.17k | { |
137 | 4.17k | Curl_uint32_spbset_remove(&bundle->ids, pool_id); |
138 | 4.17k | if(!Curl_uint32_spbset_count(&bundle->ids)) { |
139 | | /* Now empty, unregister bundle */ |
140 | 4.17k | Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination)); |
141 | 4.17k | } |
142 | 4.17k | } |
143 | | |
144 | | static void cpool_bundle_hash_dtor(void *entry) |
145 | 4.17k | { |
146 | 4.17k | struct cpool_bundle *bundle = entry; |
147 | 4.17k | cpool_bundle_unlink(&bundle); |
148 | 4.17k | } |
149 | | |
150 | | static struct cpool_bundle *cpool_get_bundle(struct cpool *cpool, |
151 | | const char *destination) |
152 | 12.4k | { |
153 | 12.4k | struct cpool_bundle *bundle = NULL; |
154 | 12.4k | cpool_bundle_link(&bundle, Curl_hash_pick( |
155 | 12.4k | &cpool->dest2bundle, destination, strlen(destination))); |
156 | 12.4k | return bundle; |
157 | 12.4k | } |
158 | | |
159 | | static void cpool_remove_conn(struct cpool *cpool, |
160 | | struct connectdata *conn) |
161 | 4.17k | { |
162 | 4.17k | uint32_t cpid = conn->cpid; |
163 | | |
164 | 4.17k | DEBUGASSERT(cpool); |
165 | 4.17k | if(cpid != UINT32_MAX) { |
166 | 4.17k | struct cpool_bundle *bundle; |
167 | | |
168 | 4.17k | #ifdef DEBUGBUILD |
169 | 4.17k | { |
170 | 4.17k | struct connectdata *pooled_conn = |
171 | 4.17k | Curl_uint32_tbl_get(&cpool->conns, cpid); |
172 | 4.17k | DEBUGASSERT(conn == pooled_conn); |
173 | 4.17k | } |
174 | 4.17k | #endif |
175 | 4.17k | Curl_uint32_tbl_remove(&cpool->conns, cpid); |
176 | 4.17k | Curl_uint32_bset_remove(&cpool->idles, cpid); |
177 | 4.17k | conn->cpid = UINT32_MAX; |
178 | | |
179 | 4.17k | bundle = cpool_get_bundle(cpool, conn->destination); |
180 | 4.17k | DEBUGASSERT(bundle); |
181 | 4.17k | if(bundle) |
182 | 4.17k | cpool_bundle_remove_id(cpool, bundle, conn->destination, cpid); |
183 | 4.17k | cpool_bundle_unlink(&bundle); |
184 | 4.17k | } |
185 | 4.17k | } |
186 | | |
187 | | static void cpool_discard_conn(struct cpool *cpool, |
188 | | struct Curl_easy *data, |
189 | | struct connectdata *conn, |
190 | | bool aborted) |
191 | 4.17k | { |
192 | 4.17k | struct Curl_easy *admin; |
193 | 4.17k | bool done = FALSE; |
194 | | |
195 | 4.17k | DEBUGASSERT(data); |
196 | 4.17k | DEBUGASSERT(!data->conn); |
197 | 4.17k | DEBUGASSERT(cpool); |
198 | 4.17k | DEBUGASSERT(conn->cpid == UINT32_MAX); |
199 | | |
200 | 4.17k | admin = Curl_get_admin(data); |
201 | | /* We MUST NOT discard a connection that is still in use and would |
202 | | * trigger Use-After-Free. */ |
203 | 4.17k | if(CONN_INUSE(conn)) { |
204 | 0 | CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T |
205 | 0 | " still in use by %u transfers", conn->connection_id, |
206 | 0 | conn->attached_xfers); |
207 | 0 | DEBUGASSERT(0); |
208 | 0 | return; |
209 | 0 | } |
210 | | |
211 | | /* treat the connection as aborted in CONNECT_ONLY situations, we do |
212 | | * not know what the APP did with it. */ |
213 | 4.17k | if(conn->bits.connect_only) |
214 | 19 | aborted = TRUE; |
215 | 4.17k | conn->bits.aborted = aborted; |
216 | | |
217 | | /* We do not shutdown dead connections. The term 'dead' can be misleading |
218 | | * here, as we also mark errored connections/transfers as 'dead'. |
219 | | * If we do a shutdown for an aborted transfer, the server might think |
220 | | * it was successful otherwise (for example an ftps: upload). This is |
221 | | * not what we want. */ |
222 | 4.17k | if(aborted) |
223 | 2.42k | done = TRUE; |
224 | 4.17k | if(!done) { |
225 | | /* Attempt to shutdown the connection right away. */ |
226 | 1.74k | Curl_cshutdn_try_once(admin, conn, &done); |
227 | 1.74k | } |
228 | | |
229 | 4.17k | if(done || !data->multi) |
230 | 4.17k | Curl_cshutdn_terminate(admin, conn, FALSE); |
231 | 0 | else { |
232 | 0 | struct Curl_multi *multi = data->multi; |
233 | 0 | size_t max_shutdowns = multi->max_total_connections; |
234 | 0 | uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); |
235 | |
|
236 | 0 | if(num_conns < max_shutdowns) |
237 | 0 | max_shutdowns -= num_conns; |
238 | 0 | else if(max_shutdowns) |
239 | 0 | max_shutdowns = 1; |
240 | 0 | else /* no connection limit set, let's restrict growth nevertheless */ |
241 | 0 | max_shutdowns = CURLMAX(num_conns / 4, 128); |
242 | 0 | Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns); |
243 | 0 | } |
244 | 4.17k | } |
245 | | |
246 | | void Curl_cpool_init(struct cpool *cpool, |
247 | | struct Curl_share *share, |
248 | | size_t size) |
249 | 10.8k | { |
250 | 10.8k | Curl_uint32_tbl_init(&cpool->conns); |
251 | 10.8k | Curl_uint32_bset_init(&cpool->idles); |
252 | 10.8k | Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES, |
253 | 10.8k | cpool_bundle_hash_dtor); |
254 | 10.8k | cpool->share = share; |
255 | 10.8k | cpool->initialized = TRUE; |
256 | 10.8k | cpool->in_shutdown = FALSE; |
257 | 10.8k | cpool->locked = FALSE; |
258 | 10.8k | } |
259 | | |
260 | | void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin) |
261 | 10.8k | { |
262 | 10.8k | if(cpool && cpool->initialized && admin) { |
263 | 10.8k | void *entry; |
264 | 10.8k | uint32_t cpid; |
265 | | |
266 | 10.8k | CURL_TRC_M(admin, "%s[CPOOL] destroy, %u connections", |
267 | 10.8k | cpool->share ? "[SHARE] " : "", |
268 | 10.8k | Curl_uint32_tbl_count(&cpool->conns)); |
269 | | /* Move all connections to the shutdown list */ |
270 | 10.8k | CPOOL_LOCK(cpool, admin); |
271 | 10.8k | if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) { |
272 | 0 | struct Curl_sigpipe_ctx pipe_ctx; |
273 | |
|
274 | 0 | sigpipe_init(&pipe_ctx); |
275 | 0 | sigpipe_apply(admin, &pipe_ctx); |
276 | 0 | do { |
277 | 0 | struct connectdata *conn = entry; |
278 | 0 | cpool_remove_conn(cpool, conn); |
279 | 0 | cpool_discard_conn(cpool, admin, conn, FALSE); |
280 | 0 | } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry)); |
281 | 0 | sigpipe_restore(&pipe_ctx); |
282 | 0 | } |
283 | 10.8k | CPOOL_UNLOCK(cpool, admin); |
284 | 10.8k | Curl_hash_destroy(&cpool->dest2bundle); |
285 | 10.8k | Curl_uint32_bset_destroy(&cpool->idles); |
286 | 10.8k | Curl_uint32_tbl_destroy(&cpool->conns); |
287 | 10.8k | } |
288 | 10.8k | } |
289 | | |
290 | | static struct cpool *cpool_get_instance(struct Curl_easy *data) |
291 | 31.7k | { |
292 | | /* admin handles do not necessarily find the correct pool */ |
293 | 31.7k | DEBUGASSERT(data->mid); |
294 | 31.7k | if(CURL_SHARE_KEEP_CONNECT(data->share)) |
295 | 0 | return &data->share->cpool; |
296 | 31.7k | else if(data->multi_easy) |
297 | 0 | return &data->multi_easy->cpool; |
298 | 31.7k | else if(data->multi) |
299 | 31.7k | return &data->multi->cpool; |
300 | 0 | return NULL; |
301 | 31.7k | } |
302 | | |
303 | | struct cpool *Curl_cpool_get_instance(struct Curl_easy *data) |
304 | 0 | { |
305 | 0 | return cpool_get_instance(data); |
306 | 0 | } |
307 | | |
308 | | void Curl_cpool_xfer_init(struct Curl_easy *data) |
309 | 10.8k | { |
310 | 10.8k | struct cpool *cpool = cpool_get_instance(data); |
311 | | |
312 | 10.8k | if(cpool) { |
313 | 10.8k | CPOOL_LOCK(cpool, data); |
314 | | /* the identifier inside the connection cache */ |
315 | 10.8k | data->id = cpool->next_easy_id++; |
316 | 10.8k | if(cpool->next_easy_id == CURL_OFF_T_MAX) |
317 | 0 | cpool->next_easy_id = 0; |
318 | 10.8k | data->state.last_conn_id = -1; |
319 | 10.8k | data->state.last_cpid = UINT32_MAX; |
320 | | |
321 | 10.8k | CPOOL_UNLOCK(cpool, data); |
322 | 10.8k | } |
323 | 0 | else { |
324 | | /* We should not get here, but in a non-debug build, do something */ |
325 | 0 | DEBUGASSERT(0); |
326 | 0 | data->id = 0; |
327 | 0 | data->state.last_conn_id = -1; |
328 | 0 | data->state.last_cpid = UINT32_MAX; |
329 | 0 | } |
330 | 10.8k | } |
331 | | |
332 | | static bool cpool_conn_can_be_closed(struct connectdata *conn) |
333 | 0 | { |
334 | | /* CONNECT_ONLY sockets remain in use by the application. */ |
335 | 0 | return (conn && !CONN_INUSE(conn) && !conn->bits.connect_only); |
336 | 0 | } |
337 | | |
338 | | static struct connectdata *cpool_bundle_get_oldest_idle( |
339 | | struct cpool *cpool, |
340 | | struct cpool_bundle *bundle, |
341 | | const struct curltime *pnow) |
342 | 0 | { |
343 | 0 | uint32_t cpid, oldest_id = UINT32_MAX; |
344 | 0 | timediff_t unused_ms; |
345 | 0 | timediff_t oldest_ms = -1; |
346 | |
|
347 | 0 | if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) { |
348 | 0 | do { |
349 | 0 | struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); |
350 | 0 | if(cpool_conn_can_be_closed(conn)) { |
351 | | /* Set higher score for the age passed since the connection was used */ |
352 | 0 | unused_ms = |
353 | 0 | curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; |
354 | 0 | if(unused_ms > oldest_ms) { |
355 | 0 | oldest_ms = unused_ms; |
356 | 0 | oldest_id = cpid; |
357 | 0 | } |
358 | 0 | } |
359 | 0 | else if(!conn) { |
360 | 0 | DEBUGASSERT(0); |
361 | 0 | Curl_uint32_spbset_remove(&bundle->ids, cpid); |
362 | 0 | } |
363 | 0 | } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid)); |
364 | 0 | } |
365 | 0 | return Curl_uint32_tbl_get(&cpool->conns, oldest_id); |
366 | 0 | } |
367 | | |
368 | | static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, |
369 | | const struct curltime *pnow, |
370 | | timediff_t min_age_ms) |
371 | 0 | { |
372 | 0 | uint32_t cpid, oldest_id = UINT32_MAX; |
373 | 0 | timediff_t oldest_idle_ms = -1; |
374 | 0 | timediff_t idle_ms; |
375 | | |
376 | | /* Iterate over all connections that once were idle. They may |
377 | | * no longer be, in which case we remove them from the set again. */ |
378 | 0 | if(Curl_uint32_bset_first(&cpool->idles, &cpid)) { |
379 | 0 | do { |
380 | 0 | struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); |
381 | 0 | if(cpool_conn_can_be_closed(conn)) { |
382 | 0 | idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; |
383 | 0 | if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { |
384 | 0 | oldest_idle_ms = idle_ms; |
385 | 0 | oldest_id = cpid; |
386 | 0 | } |
387 | 0 | } |
388 | 0 | else { |
389 | 0 | DEBUGASSERT(conn); |
390 | 0 | Curl_uint32_bset_remove(&cpool->idles, cpid); |
391 | 0 | } |
392 | 0 | } while(Curl_uint32_bset_next(&cpool->idles, cpid, &cpid)); |
393 | 0 | } |
394 | 0 | return Curl_uint32_tbl_get(&cpool->conns, oldest_id); |
395 | 0 | } |
396 | | |
397 | | static void cpool_conn_close(struct cpool *cpool, |
398 | | struct Curl_easy *data, |
399 | | struct connectdata *conn, |
400 | | bool aborted) |
401 | 4.17k | { |
402 | 4.17k | struct Curl_easy *admin; |
403 | 4.17k | bool do_lock; |
404 | | |
405 | 4.17k | DEBUGASSERT(cpool); |
406 | 4.17k | DEBUGASSERT(data && !data->conn); |
407 | 4.17k | if(!cpool) |
408 | 0 | return; |
409 | | |
410 | | /* If this connection is not marked to force-close, leave it open if there |
411 | | * are other users of it */ |
412 | 4.17k | if(CONN_INUSE(conn) && !aborted) { |
413 | 0 | DEBUGASSERT(0); /* does this ever happen? */ |
414 | 0 | DEBUGF(infof(data, "conn terminate when inuse: %u", conn->attached_xfers)); |
415 | 0 | return; |
416 | 0 | } |
417 | | |
418 | | /* This method may be called while we are under lock, e.g. from a |
419 | | * user callback in find. */ |
420 | 4.17k | admin = Curl_get_admin(data); |
421 | 4.17k | do_lock = !CPOOL_IS_LOCKED(cpool); |
422 | 4.17k | if(do_lock) |
423 | 11 | CPOOL_LOCK(cpool, admin); |
424 | | |
425 | 4.17k | if(conn->cpid != UINT32_MAX) { |
426 | 4.17k | cpool_remove_conn(cpool, conn); |
427 | 4.17k | DEBUGASSERT(conn->cpid == UINT32_MAX); |
428 | 4.17k | } |
429 | | |
430 | | /* treat the connection as aborted in CONNECT_ONLY situations, |
431 | | * so no graceful shutdown is attempted. */ |
432 | 4.17k | if(conn->bits.connect_only) |
433 | 19 | aborted = TRUE; |
434 | | |
435 | 4.17k | if(data->multi) { |
436 | | /* Add it to the multi's cpool for shutdown handling */ |
437 | 4.17k | infof(data, "%s connection #%" FMT_OFF_T, |
438 | 4.17k | aborted ? "closing" : "shutting down", conn->connection_id); |
439 | 4.17k | cpool_discard_conn(&data->multi->cpool, data, conn, aborted); |
440 | 4.17k | } |
441 | 0 | else { |
442 | | /* No multi available, terminate */ |
443 | 0 | infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); |
444 | 0 | Curl_cshutdn_terminate(admin, conn, !aborted); |
445 | 0 | } |
446 | | |
447 | 4.17k | if(do_lock) |
448 | 11 | CPOOL_UNLOCK(cpool, admin); |
449 | 4.17k | } |
450 | | |
451 | | void Curl_conn_close(struct Curl_easy *data, |
452 | | struct connectdata *conn, |
453 | | bool aborted) |
454 | 4.17k | { |
455 | 4.17k | struct cpool *cpool = cpool_get_instance(data); |
456 | 4.17k | cpool_conn_close(cpool, data, conn, aborted); |
457 | 4.17k | } |
458 | | |
459 | | /* Evict an idle connection to make room in the pool. A pool owned by |
460 | | * a share has no multi that could perform a controlled shutdown of the |
461 | | * connection; terminate it right away. Otherwise, hand it to the |
462 | | * transfer's multi for shutdown. Expects the pool to be locked. */ |
463 | | static void cpool_evict_conn(struct cpool *cpool, |
464 | | struct Curl_easy *admin, |
465 | | struct connectdata *conn) |
466 | 0 | { |
467 | 0 | if(cpool->share) { |
468 | 0 | cpool_remove_conn(cpool, conn); |
469 | 0 | Curl_cshutdn_terminate(admin, conn, TRUE); |
470 | 0 | } |
471 | 0 | else |
472 | 0 | cpool_conn_close(cpool, admin, conn, FALSE); |
473 | 0 | } |
474 | | |
475 | 4.17k | #define CPOOL_LIMIT_OK 0 |
476 | 0 | #define CPOOL_LIMIT_DEST 1 |
477 | 0 | #define CPOOL_LIMIT_TOTAL 2 |
478 | | |
479 | | static int cpool_check_limits(struct Curl_easy *data, |
480 | | uint32_t max_total, |
481 | | struct connectdata *to_add, |
482 | | uint32_t max_host, |
483 | | struct cpool_bundle *bundle, |
484 | | const struct curltime *pnow) |
485 | 4.17k | { |
486 | 4.17k | struct cpool *cpool = cpool_get_instance(data); |
487 | 4.17k | struct cshutdn *cshutdn = data->multi ? &data->multi->cshutdn : NULL; |
488 | 4.17k | struct Curl_easy *admin = NULL; |
489 | | |
490 | 4.17k | if(!cpool) |
491 | 0 | return CPOOL_LIMIT_OK; |
492 | | |
493 | 4.17k | if(!max_total && !max_host) |
494 | 4.17k | return CPOOL_LIMIT_OK; |
495 | | |
496 | 0 | if(max_host) { |
497 | 0 | size_t live = 0, shutdowns = 0; |
498 | | /* if we are at or above `dest_limit`, try to get rid of connections |
499 | | * in shutdown and, if that does not lower it, evict idle connections |
500 | | * from the pool. */ |
501 | 0 | admin = Curl_get_admin(data); |
502 | 0 | if(bundle) { |
503 | 0 | live = Curl_uint32_spbset_count(&bundle->ids); |
504 | 0 | if(live >= max_host) { |
505 | 0 | size_t over = live - max_host + 1; |
506 | 0 | for(; over; --over) { |
507 | 0 | struct connectdata *oldest_idle = |
508 | 0 | cpool_bundle_get_oldest_idle(cpool, bundle, pnow); |
509 | 0 | if(!oldest_idle) |
510 | 0 | break; |
511 | | /* disconnect the old conn and continue */ |
512 | 0 | CURL_TRC_M(admin, "Shutting down connection #%" FMT_OFF_T |
513 | 0 | " to '%s' due to destination limit of %u", |
514 | 0 | oldest_idle->connection_id, oldest_idle->destination, |
515 | 0 | max_host); |
516 | 0 | cpool_evict_conn(cpool, admin, oldest_idle); |
517 | 0 | } |
518 | 0 | live = Curl_uint32_spbset_count(&bundle->ids); |
519 | 0 | } |
520 | 0 | } |
521 | 0 | if(cshutdn) { |
522 | 0 | shutdowns = Curl_cshutdn_dest_count(cshutdn, to_add->destination); |
523 | 0 | if(shutdowns && ((live + shutdowns) >= max_host)) { |
524 | 0 | size_t over = live + shutdowns - max_host + 1; |
525 | 0 | shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, |
526 | 0 | to_add->destination, over); |
527 | 0 | } |
528 | 0 | } |
529 | | /* We may not have gotten rid of as many connections as we wanted to */ |
530 | 0 | if((live + shutdowns) >= max_host) |
531 | 0 | return CPOOL_LIMIT_DEST; |
532 | 0 | } |
533 | | |
534 | | /* Internal transfers are outside the total limit */ |
535 | 0 | if(max_total && !data->state.internal) { |
536 | 0 | size_t shutdowns = 0; |
537 | 0 | uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); |
538 | |
|
539 | 0 | if(!admin) |
540 | 0 | admin = Curl_get_admin(data); |
541 | 0 | if(num_conns >= max_total) { |
542 | 0 | size_t over = num_conns - max_total + 1; |
543 | 0 | for(; over; --over) { |
544 | 0 | struct connectdata *oldest_idle = |
545 | 0 | cpool_get_oldest_idle(cpool, pnow, 0); |
546 | 0 | if(!oldest_idle) |
547 | 0 | break; |
548 | | /* disconnect the old conn and continue */ |
549 | 0 | CURL_TRC_M(admin, "Shutting down idle connection #%" |
550 | 0 | FMT_OFF_T " to '%s' due to total limit of %u", |
551 | 0 | oldest_idle->connection_id, oldest_idle->destination, |
552 | 0 | max_total); |
553 | 0 | cpool_evict_conn(cpool, admin, oldest_idle); |
554 | 0 | } |
555 | 0 | } |
556 | |
|
557 | 0 | num_conns = Curl_uint32_tbl_count(&cpool->conns); |
558 | 0 | if(cshutdn) { |
559 | 0 | shutdowns = Curl_cshutdn_count(cshutdn); |
560 | 0 | if(shutdowns && (num_conns + shutdowns) >= max_total) { |
561 | 0 | size_t over = num_conns + shutdowns - max_total + 1; |
562 | 0 | shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, NULL, over); |
563 | 0 | } |
564 | 0 | } |
565 | 0 | if((num_conns + shutdowns) >= max_total) |
566 | 0 | return CPOOL_LIMIT_TOTAL; |
567 | 0 | } |
568 | | |
569 | 0 | return CPOOL_LIMIT_OK; |
570 | 0 | } |
571 | | |
572 | | CURLcode Curl_cpool_add(struct Curl_easy *data, |
573 | | struct connectdata *conn, |
574 | | uint32_t max_total, |
575 | | uint32_t max_host, |
576 | | const struct curltime *pnow) |
577 | 4.17k | { |
578 | 4.17k | CURLcode result = CURLE_OK; |
579 | 4.17k | struct cpool_bundle *bundle = NULL; |
580 | 4.17k | struct cpool *cpool = cpool_get_instance(data); |
581 | 4.17k | uint32_t capacity, cpid; |
582 | 4.17k | DEBUGASSERT(conn); |
583 | | |
584 | 4.17k | DEBUGASSERT(cpool); |
585 | 4.17k | if(!cpool) |
586 | 0 | return CURLE_FAILED_INIT; |
587 | | |
588 | 4.17k | DEBUGASSERT(conn->cpid == UINT32_MAX); |
589 | 4.17k | conn->created = *pnow; |
590 | 4.17k | conn->shutdown.start_ms[FIRSTSOCKET] = |
591 | 4.17k | conn->shutdown.start_ms[SECONDARYSOCKET] = -1; |
592 | | |
593 | 4.17k | CPOOL_LOCK(cpool, data); |
594 | | /* Find the bundle, should it exist, and check the limits */ |
595 | 4.17k | bundle = cpool_get_bundle(cpool, conn->destination); |
596 | | |
597 | 4.17k | switch(cpool_check_limits(data, max_total, conn, max_host, bundle, pnow)) { |
598 | 0 | case CPOOL_LIMIT_DEST: |
599 | 0 | infof(data, "No more connections allowed to host"); |
600 | 0 | result = CURLE_NO_CONNECTION_AVAILABLE; |
601 | 0 | goto out; |
602 | 0 | case CPOOL_LIMIT_TOTAL: |
603 | 0 | infof(data, "No connections available, total of %u reached.", |
604 | 0 | data->multi->max_total_connections); |
605 | 0 | result = CURLE_NO_CONNECTION_AVAILABLE; |
606 | 0 | goto out; |
607 | 4.17k | default: |
608 | 4.17k | break; |
609 | 4.17k | } |
610 | | |
611 | | /* We add the connection */ |
612 | 4.17k | capacity = Curl_uint32_tbl_capacity(&cpool->conns); |
613 | 4.17k | if(capacity == UINT32_MAX) { |
614 | 0 | infof(data, "No connections available, pool at max capacity"); |
615 | 0 | result = CURLE_NO_CONNECTION_AVAILABLE; |
616 | 0 | goto out; |
617 | 0 | } |
618 | 4.17k | if(Curl_uint32_tbl_count(&cpool->conns) >= capacity) { |
619 | 4.17k | uint32_t growth, ncapacity; |
620 | | |
621 | 4.17k | growth = CURLMAX(capacity, 16); |
622 | 4.17k | if((UINT32_MAX - growth) >= capacity) |
623 | 4.17k | ncapacity = capacity + growth; |
624 | 0 | else |
625 | 0 | ncapacity = UINT32_MAX; |
626 | | /* Grow bitset first, so table resize failure does not leave a too |
627 | | * short bitset should that one fail. */ |
628 | 4.17k | if(Curl_uint32_bset_resize(&cpool->idles, ncapacity) || |
629 | 4.17k | Curl_uint32_tbl_resize(&cpool->conns, ncapacity)) { |
630 | 0 | result = CURLE_OUT_OF_MEMORY; |
631 | 0 | goto out; |
632 | 0 | } |
633 | 4.17k | } |
634 | | |
635 | 4.17k | if(!bundle) { |
636 | 4.17k | bundle = cpool_create_bundle(); |
637 | 4.17k | if(!bundle) { |
638 | 0 | result = CURLE_OUT_OF_MEMORY; |
639 | 0 | goto out; |
640 | 0 | } |
641 | 4.17k | } |
642 | | |
643 | 4.17k | if(!Curl_uint32_tbl_add(&cpool->conns, conn, &cpid)) { |
644 | 0 | DEBUGASSERT(0); |
645 | 0 | result = CURLE_OUT_OF_MEMORY; |
646 | 0 | goto out; |
647 | 0 | } |
648 | 4.17k | conn->cpid = cpid; |
649 | | |
650 | 4.17k | if(!cpool_bundle_add_id(cpool, bundle, conn->destination, cpid)) { |
651 | 0 | Curl_uint32_tbl_remove(&cpool->conns, cpid); |
652 | 0 | conn->cpid = UINT32_MAX; |
653 | 0 | result = CURLE_OUT_OF_MEMORY; |
654 | 0 | goto out; |
655 | 0 | } |
656 | | |
657 | 4.17k | conn->connection_id = cpool->next_connection_id++; |
658 | 4.17k | CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". " |
659 | 4.17k | "The cache now contains %u members", |
660 | 4.17k | conn->connection_id, Curl_uint32_tbl_count(&cpool->conns)); |
661 | 4.17k | out: |
662 | 4.17k | cpool_bundle_unlink(&bundle); |
663 | 4.17k | CPOOL_UNLOCK(cpool, data); |
664 | | |
665 | 4.17k | return result; |
666 | 4.17k | } |
667 | | |
668 | | /* This function iterates the entire connection pool and calls the function |
669 | | func() with the connection pointer as the first argument and the supplied |
670 | | 'param' argument as the other. |
671 | | |
672 | | The cpool lock is still held when the callback is called. It needs it, |
673 | | so that it can safely continue traversing the lists once the callback |
674 | | returns. |
675 | | |
676 | | Returns TRUE if the loop was aborted due to the callback's return code. |
677 | | |
678 | | Return 0 from func() to continue the loop, return 1 to abort it. |
679 | | */ |
680 | | static bool cpool_foreach(struct Curl_easy *data, |
681 | | struct cpool *cpool, |
682 | | void *param, |
683 | | int (*func)(struct cpool *cpool, |
684 | | struct Curl_easy *data, |
685 | | struct connectdata *conn, void *param)) |
686 | 4.14k | { |
687 | 4.14k | void *entry; |
688 | 4.14k | uint32_t cpid; |
689 | | |
690 | 4.14k | if(!cpool) |
691 | 0 | return FALSE; |
692 | | |
693 | 4.14k | if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) { |
694 | 0 | do { |
695 | 0 | struct connectdata *conn = entry; |
696 | 0 | if(conn && func(cpool, data, conn, param) == 1) |
697 | 0 | return TRUE; |
698 | 0 | } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry)); |
699 | 0 | } |
700 | 4.14k | return FALSE; |
701 | 4.14k | } |
702 | | |
703 | | /* |
704 | | * A connection (already in the pool) has become idle. Do any |
705 | | * cleanups in regard to the pool's limits. |
706 | | * |
707 | | * Return TRUE if idle connection kept in pool, FALSE if closed. |
708 | | */ |
709 | | static bool cpool_conn_now_idle(struct cpool *cpool, |
710 | | struct Curl_easy *data, |
711 | | struct connectdata *conn, |
712 | | const struct curltime *pnow) |
713 | 11 | { |
714 | 11 | struct connectdata *oldest_idle = NULL; |
715 | 11 | struct Curl_easy *admin; |
716 | 11 | uint32_t maxconnects; |
717 | 11 | bool kept = TRUE; |
718 | 11 | timediff_t min_age_ms = 0; |
719 | | |
720 | 11 | if(!data || !data->multi) |
721 | 0 | return kept; |
722 | | |
723 | 11 | Curl_uint32_bset_add(&cpool->idles, conn->cpid); |
724 | | |
725 | 11 | if(!data->multi->maxconnects) { |
726 | | /* Attached transfers is a weak indicator of business. */ |
727 | 11 | uint32_t attached = Curl_multi_xfers_attached(data->multi); |
728 | 11 | maxconnects = (attached <= UINT32_MAX / 2) ? attached * 2 : UINT32_MAX; |
729 | | /* We are guessing. So, only evict a "seemingly superfluous" connection |
730 | | * when has not been used for this long, */ |
731 | 11 | min_age_ms = 1000; |
732 | 11 | } |
733 | 0 | else { |
734 | 0 | maxconnects = data->multi->maxconnects; |
735 | 0 | } |
736 | | |
737 | 11 | if(cpool && maxconnects && (maxconnects < UINT32_MAX)) { |
738 | 11 | uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); |
739 | 11 | admin = Curl_get_admin(data); |
740 | 11 | if(num_conns > maxconnects) { |
741 | 0 | infof(data, "Connection pool is full, closing the oldest of %u/%u", |
742 | 0 | num_conns, maxconnects); |
743 | 0 | oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms); |
744 | 0 | kept = (oldest_idle != conn); |
745 | 0 | if(oldest_idle) { |
746 | 0 | cpool_evict_conn(cpool, admin, oldest_idle); |
747 | 0 | } |
748 | 0 | } |
749 | 11 | } |
750 | | |
751 | 11 | return kept; |
752 | 11 | } |
753 | | |
754 | | static void cpool_prune_dead(struct cpool *cpool, |
755 | | struct Curl_easy *data, |
756 | | const struct curltime *pnow); |
757 | | |
758 | | static bool cpool_find_act(struct cpool *cpool, |
759 | | struct Curl_easy *data, |
760 | | struct connectdata *conn, |
761 | | cpool_match_result match) |
762 | 0 | { |
763 | 0 | switch(match) { |
764 | 0 | case CPOOL_MATCH_FOUND: |
765 | 0 | return TRUE; |
766 | 0 | case CPOOL_MATCH_TOO_OLD: |
767 | 0 | if(!cpool_conn_can_be_closed(conn)) |
768 | 0 | return FALSE; |
769 | 0 | FALLTHROUGH(); |
770 | 0 | case CPOOL_MATCH_CLOSE: |
771 | 0 | cpool_conn_close(cpool, data, conn, FALSE); |
772 | 0 | break; |
773 | 0 | default: |
774 | 0 | break; |
775 | 0 | } |
776 | 0 | return FALSE; |
777 | 0 | } |
778 | | |
779 | | bool Curl_cpool_find(struct Curl_easy *data, |
780 | | const char *destination, |
781 | | bool prune_dead, |
782 | | const struct curltime *pnow, |
783 | | Curl_cpool_conn_match_cb *conn_cb, |
784 | | Curl_cpool_done_match_cb *done_cb, |
785 | | void *userdata) |
786 | 4.14k | { |
787 | 4.14k | struct cpool *cpool = cpool_get_instance(data); |
788 | 4.14k | struct cpool_bundle *bundle = NULL; |
789 | 4.14k | bool found = FALSE; |
790 | | |
791 | 4.14k | DEBUGASSERT(cpool); |
792 | 4.14k | DEBUGASSERT(conn_cb); |
793 | 4.14k | if(!cpool) |
794 | 0 | return FALSE; |
795 | | |
796 | 4.14k | CPOOL_LOCK(cpool, data); |
797 | | |
798 | 4.14k | if(prune_dead) |
799 | 4.14k | cpool_prune_dead(cpool, data, pnow); |
800 | | |
801 | 4.14k | if(data->state.last_cpid != UINT32_MAX) { |
802 | 0 | struct connectdata *conn = |
803 | 0 | Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); |
804 | 0 | if(conn && (data->state.last_conn_id == conn->connection_id)) { |
805 | | /* Indeed the last connection used by `data` still exists. |
806 | | * Check that first, so that connection use gets "sticky". */ |
807 | 0 | found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata)); |
808 | 0 | if(found) |
809 | 0 | goto out; |
810 | 0 | } |
811 | 0 | else { |
812 | | /* No longer found here. */ |
813 | 0 | data->state.last_conn_id = -1; |
814 | 0 | data->state.last_cpid = UINT32_MAX; |
815 | 0 | } |
816 | 0 | } |
817 | | |
818 | 4.14k | bundle = cpool_get_bundle(cpool, destination); |
819 | 4.14k | if(bundle) { |
820 | 0 | uint32_t cpid; |
821 | 0 | if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) { |
822 | 0 | do { |
823 | 0 | struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); |
824 | 0 | if(conn) { |
825 | 0 | if(data->state.last_conn_id != conn->connection_id) { |
826 | 0 | found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata)); |
827 | 0 | if(found) |
828 | 0 | goto out; |
829 | 0 | } |
830 | 0 | } |
831 | 0 | else { |
832 | 0 | DEBUGASSERT(0); |
833 | 0 | Curl_uint32_spbset_remove(&bundle->ids, cpid); |
834 | 0 | } |
835 | 0 | } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid)); |
836 | 0 | } |
837 | 0 | } |
838 | | |
839 | 4.14k | out: |
840 | 4.14k | if(done_cb) { |
841 | 4.14k | found = done_cb(userdata); |
842 | 4.14k | } |
843 | 4.14k | cpool_bundle_unlink(&bundle); |
844 | 4.14k | CPOOL_UNLOCK(cpool, data); |
845 | 4.14k | return found; |
846 | 4.14k | } |
847 | | |
848 | | struct cpool_reaper_ctx { |
849 | | size_t reaped; |
850 | | const struct curltime *pnow; |
851 | | }; |
852 | | |
853 | | static int cpool_reap_dead_cb(struct cpool *cpool, |
854 | | struct Curl_easy *admin, |
855 | | struct connectdata *conn, void *param) |
856 | 0 | { |
857 | 0 | struct cpool_reaper_ctx *reaper = param; |
858 | |
|
859 | 0 | if(cpool_conn_can_be_closed(conn)) { |
860 | 0 | if(conn->bits.no_reuse || conn->bits.close || |
861 | 0 | !Curl_cpool_conn_seems_healthy(conn, admin, reaper->pnow)) { |
862 | | /* terminate conn and stop the iteration */ |
863 | 0 | reaper->reaped++; |
864 | 0 | cpool_conn_close(cpool, admin, conn, FALSE); |
865 | 0 | } |
866 | 0 | } |
867 | 0 | return 0; /* continue iteration */ |
868 | 0 | } |
869 | | |
870 | | /* |
871 | | * This function scans the data's connection pool for half-open/dead |
872 | | * connections, closes and removes them. |
873 | | * The cleanup is done at most once per second. |
874 | | */ |
875 | | static void cpool_prune_dead(struct cpool *cpool, |
876 | | struct Curl_easy *data, |
877 | | const struct curltime *pnow) |
878 | 4.14k | { |
879 | 4.14k | timediff_t elapsed_ms = curlx_ptimediff_ms(pnow, &cpool->last_cleanup); |
880 | | |
881 | 4.14k | if(elapsed_ms >= 1000L) { |
882 | 4.14k | struct Curl_easy *admin = Curl_get_admin(data); |
883 | 4.14k | struct cpool_reaper_ctx reaper; |
884 | | |
885 | 4.14k | memset(&reaper, 0, sizeof(reaper)); |
886 | 4.14k | reaper.pnow = pnow; |
887 | 4.14k | cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb); |
888 | 4.14k | cpool->last_cleanup = *pnow; |
889 | 4.14k | } |
890 | 4.14k | } |
891 | | |
892 | | static int conn_upkeep(struct cpool *cpool, |
893 | | struct Curl_easy *admin, |
894 | | struct connectdata *conn, |
895 | | void *param) |
896 | 0 | { |
897 | 0 | const struct curltime *pnow = Curl_pgrs_now(admin); |
898 | |
|
899 | 0 | (void)param; |
900 | 0 | if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastupkeep_ms) >= |
901 | 0 | admin->set.upkeep_interval_ms) { |
902 | 0 | CURLcode result; |
903 | |
|
904 | 0 | conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created); |
905 | | /* briefly attach for action */ |
906 | 0 | Curl_attach_connection(admin, conn, FALSE); |
907 | 0 | result = Curl_conn_keep_alive(admin, conn); |
908 | 0 | Curl_detach_connection(admin); |
909 | |
|
910 | 0 | if(result && cpool_conn_can_be_closed(conn)) { |
911 | 0 | cpool_conn_close(cpool, admin, conn, FALSE); |
912 | 0 | } |
913 | 0 | } |
914 | 0 | return 0; /* continue iteration */ |
915 | 0 | } |
916 | | |
917 | | CURLcode Curl_cpool_upkeep(struct Curl_easy *data) |
918 | 0 | { |
919 | 0 | struct cpool *cpool = cpool_get_instance(data); |
920 | 0 | struct Curl_easy *admin = Curl_get_admin(data); |
921 | |
|
922 | 0 | if(!cpool) |
923 | 0 | return CURLE_OK; |
924 | | |
925 | 0 | CPOOL_LOCK(cpool, admin); |
926 | 0 | cpool_foreach(admin, cpool, NULL, conn_upkeep); |
927 | 0 | CPOOL_UNLOCK(cpool, admin); |
928 | 0 | return CURLE_OK; |
929 | 0 | } |
930 | | |
931 | | struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data) |
932 | 20 | { |
933 | 20 | struct cpool *cpool = cpool_get_instance(data); |
934 | 20 | if(cpool && (data->state.last_cpid != UINT32_MAX)) { |
935 | 19 | struct connectdata *conn; |
936 | | |
937 | 19 | CPOOL_LOCK(cpool, data); |
938 | 19 | conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); |
939 | 19 | CPOOL_UNLOCK(cpool, data); |
940 | 19 | if(conn && (data->state.last_conn_id == conn->connection_id)) |
941 | 11 | return conn; |
942 | 8 | data->state.last_cpid = UINT32_MAX; |
943 | 8 | data->state.last_conn_id = -1; |
944 | 8 | } |
945 | 9 | return NULL; |
946 | 20 | } |
947 | | |
948 | | void Curl_cpool_return(struct Curl_easy *data, |
949 | | struct connectdata *conn, |
950 | | Curl_cpool_return_cb *cb, void *cbdata, |
951 | | const struct curltime *pnow) |
952 | 4.17k | { |
953 | 4.17k | struct cpool *cpool = cpool_get_instance(data); |
954 | | |
955 | 4.17k | CPOOL_LOCK(cpool, data); |
956 | | |
957 | | /* remember times, connection had been used just before */ |
958 | 4.17k | conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms = |
959 | 4.17k | curlx_ptimediff_ms(pnow, &conn->created); |
960 | | |
961 | 4.17k | switch(cb(data, conn, cbdata, pnow)) { |
962 | 0 | case CPOOL_DO_KEEP: |
963 | 0 | break; |
964 | 11 | case CPOOL_DO_IDLE: |
965 | | /* the connection is no longer in use by any transfer */ |
966 | 11 | if(cpool_conn_now_idle(cpool, data, conn, pnow)) { |
967 | | /* connection kept in the cpool */ |
968 | 11 | infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", |
969 | 11 | conn->connection_id, conn->origin->user_hostname, |
970 | 11 | conn->origin->port); |
971 | 11 | } |
972 | 0 | else { /* connection was removed from the cpool and destroyed. */ |
973 | 0 | data->state.last_conn_id = -1; |
974 | 0 | data->state.last_cpid = UINT32_MAX; |
975 | 0 | } |
976 | 11 | break; |
977 | 1.74k | case CPOOL_DO_CLOSE: |
978 | 1.74k | Curl_conn_close(data, conn, FALSE); |
979 | 1.74k | break; |
980 | 2.41k | case CPOOL_DO_TERMINATE: |
981 | 2.41k | Curl_conn_close(data, conn, TRUE); |
982 | 2.41k | break; |
983 | 4.17k | } |
984 | | |
985 | 4.17k | CPOOL_UNLOCK(cpool, data); |
986 | 4.17k | } |
987 | | |
988 | | static int cpool_reap_no_reuse(struct cpool *cpool, |
989 | | struct Curl_easy *admin, |
990 | | struct connectdata *conn, void *param) |
991 | 0 | { |
992 | 0 | (void)param; |
993 | 0 | conn->bits.no_reuse = TRUE; |
994 | 0 | if(cpool_conn_can_be_closed(conn)) |
995 | 0 | cpool_conn_close(cpool, admin, conn, FALSE); |
996 | 0 | return 0; /* continue iteration */ |
997 | 0 | } |
998 | | |
999 | | void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) |
1000 | 0 | { |
1001 | 0 | if(cpool && admin) { |
1002 | 0 | CPOOL_LOCK(cpool, admin); |
1003 | 0 | cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse); |
1004 | 0 | CPOOL_UNLOCK(cpool, admin); |
1005 | 0 | } |
1006 | 0 | } |
1007 | | |
1008 | | /* A connection has to have been idle for less than 'conn_max_idle_ms' |
1009 | | (the success rate is too low after this), or created less than |
1010 | | 'conn_max_age_ms' ago, to be subject for reuse. */ |
1011 | | static bool cpool_conn_too_old(struct Curl_easy *data, |
1012 | | struct connectdata *conn, |
1013 | | const struct curltime *pnow) |
1014 | 0 | { |
1015 | 0 | timediff_t age_ms; |
1016 | |
|
1017 | 0 | if(data->set.conn_max_idle_ms) { |
1018 | 0 | age_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; |
1019 | 0 | if(age_ms > data->set.conn_max_idle_ms) { |
1020 | 0 | infof(data, "Too old connection (%" FMT_TIMEDIFF_T |
1021 | 0 | " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", |
1022 | 0 | age_ms, data->set.conn_max_idle_ms); |
1023 | 0 | return TRUE; |
1024 | 0 | } |
1025 | 0 | } |
1026 | | |
1027 | 0 | if(data->set.conn_max_age_ms) { |
1028 | 0 | age_ms = curlx_ptimediff_ms(pnow, &conn->created); |
1029 | 0 | if(age_ms > data->set.conn_max_age_ms) { |
1030 | 0 | infof(data, |
1031 | 0 | "Too old connection (created %" FMT_TIMEDIFF_T |
1032 | 0 | " ms ago, max lifetime is %" FMT_TIMEDIFF_T " ms), disconnect it", |
1033 | 0 | age_ms, data->set.conn_max_age_ms); |
1034 | 0 | return TRUE; |
1035 | 0 | } |
1036 | 0 | } |
1037 | | |
1038 | 0 | return FALSE; |
1039 | 0 | } |
1040 | | |
1041 | | bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, |
1042 | | struct Curl_easy *data, |
1043 | | const struct curltime *pnow) |
1044 | 0 | { |
1045 | 0 | struct Curl_easy *admin; |
1046 | 0 | bool healthy = TRUE; |
1047 | 0 | bool poll_input; |
1048 | |
|
1049 | 0 | DEBUGASSERT(!data->conn); |
1050 | 0 | if(cpool_conn_can_be_closed(conn) && cpool_conn_too_old(data, conn, pnow)) |
1051 | 0 | return FALSE; |
1052 | | |
1053 | | /* An idle, non-multiplexed connection must not be reused when it has |
1054 | | * input pending (RFC 9112, section 9.2), so that is polled on every |
1055 | | * reuse. The once a second limit only applies to the other checks. */ |
1056 | 0 | poll_input = cpool_conn_can_be_closed(conn) && |
1057 | 0 | !Curl_conn_is_multiplex(conn, FIRSTSOCKET) && |
1058 | 0 | !conn->scheme->run->connection_is_dead; |
1059 | 0 | if(!poll_input && |
1060 | 0 | (curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) |
1061 | 0 | return TRUE; |
1062 | | |
1063 | 0 | admin = Curl_get_admin(data); |
1064 | 0 | if(conn->scheme->run->connection_is_dead) { |
1065 | 0 | Curl_attach_connection(admin, conn, FALSE); |
1066 | 0 | healthy = !conn->scheme->run->connection_is_dead(admin, conn); |
1067 | 0 | Curl_detach_connection(admin); |
1068 | 0 | } |
1069 | 0 | else { |
1070 | 0 | bool input_pending = FALSE; |
1071 | |
|
1072 | 0 | Curl_attach_connection(admin, conn, FALSE); |
1073 | 0 | healthy = Curl_conn_is_alive(admin, conn, &input_pending); |
1074 | 0 | Curl_detach_connection(admin); |
1075 | 0 | if(healthy && input_pending && poll_input) { |
1076 | | /* Non-multiplexed connections without attached transfers should |
1077 | | * not have input pending. The input might be a TLS Notify Close, |
1078 | | * for all we know. */ |
1079 | 0 | DEBUGF(infof(data, "connection has no transfer but input, not healthy")); |
1080 | 0 | healthy = FALSE; |
1081 | 0 | } |
1082 | 0 | } |
1083 | |
|
1084 | 0 | if(healthy) |
1085 | 0 | conn->lastchecked_ms = curlx_ptimediff_ms(pnow, &conn->created); |
1086 | 0 | return healthy; |
1087 | 0 | } |
1088 | | |
1089 | | void Curl_cpool_conn_was_used(struct Curl_easy *data, |
1090 | | struct connectdata *conn, |
1091 | | const struct curltime *pnow) |
1092 | 1.93k | { |
1093 | 1.93k | (void)data; |
1094 | 1.93k | conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created); |
1095 | 1.93k | } |
1096 | | |
1097 | | timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, |
1098 | | struct connectdata *conn, |
1099 | | const struct curltime *pnow) |
1100 | 0 | { |
1101 | 0 | (void)data; |
1102 | 0 | return curlx_ptimediff_ms(pnow, &conn->created); |
1103 | 0 | } |