1 /**
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0.
4 */
5
6 #include <aws/http/connection_manager.h>
7
8 #include <aws/http/connection.h>
9 #include <aws/http/private/connection_manager_system_vtable.h>
10 #include <aws/http/private/connection_monitor.h>
11 #include <aws/http/private/http_impl.h>
12 #include <aws/http/private/proxy_impl.h>
13
14 #include <aws/io/channel_bootstrap.h>
15 #include <aws/io/event_loop.h>
16 #include <aws/io/logging.h>
17 #include <aws/io/socket.h>
18 #include <aws/io/tls_channel_handler.h>
19 #include <aws/io/uri.h>
20
21 #include <aws/common/clock.h>
22 #include <aws/common/hash_table.h>
23 #include <aws/common/linked_list.h>
24 #include <aws/common/mutex.h>
25 #include <aws/common/string.h>
26
27 #if _MSC_VER
28 # pragma warning(disable : 4232) /* function pointer to dll symbol */
29 #endif
30
31 /*
32 * Established connections not currently in use are tracked via this structure.
33 */
34 struct aws_idle_connection {
35 struct aws_allocator *allocator;
36 struct aws_linked_list_node node;
37 uint64_t cull_timestamp;
38 struct aws_http_connection *connection;
39 };
40
41 /*
42 * System vtable to use under normal circumstances
43 */
44 static struct aws_http_connection_manager_system_vtable s_default_system_vtable = {
45 .create_connection = aws_http_client_connect,
46 .release_connection = aws_http_connection_release,
47 .close_connection = aws_http_connection_close,
48 .is_connection_available = aws_http_connection_new_requests_allowed,
49 .get_monotonic_time = aws_high_res_clock_get_ticks,
50 .is_callers_thread = aws_channel_thread_is_callers_thread,
51 .connection_get_channel = aws_http_connection_get_channel,
52 };
53
54 const struct aws_http_connection_manager_system_vtable *g_aws_http_connection_manager_default_system_vtable_ptr =
55 &s_default_system_vtable;
56
aws_http_connection_manager_system_vtable_is_valid(const struct aws_http_connection_manager_system_vtable * table)57 bool aws_http_connection_manager_system_vtable_is_valid(const struct aws_http_connection_manager_system_vtable *table) {
58 return table->create_connection && table->close_connection && table->release_connection &&
59 table->is_connection_available;
60 }
61
62 enum aws_http_connection_manager_state_type { AWS_HCMST_UNINITIALIZED, AWS_HCMST_READY, AWS_HCMST_SHUTTING_DOWN };
63
64 /**
65 * Vocabulary
66 * Acquisition - a request by a user for a connection
67 * Pending Acquisition - a request by a user for a new connection that has not been completed. It may be
68 * waiting on http, a release by another user, or the manager itself.
69 * Pending Connect - a request to the http layer for a new connection that has not been resolved yet
70 * Vended Connection - a successfully established connection that is currently in use by something; must
71 * be released (through the connection manager) by the user before anyone else can use it. The connection
72 * manager does not explicitly track vended connections.
73 * Task Set - A set of operations that should be attempted once the lock is released. A task set includes
74 * completion callbacks (which can't fail) and connection attempts (which can fail either immediately or
75 * asynchronously).
76 *
77 * Requirements/Assumptions
78 * (1) Don't invoke user callbacks while holding the internal state lock
79 * (2) Don't invoke downstream http calls while holding the internal state lock
80 * (3) Only log unusual or rare events while the lock is held. Common-path logging should be while it is
81 * not held.
82 * (4) Don't crash or do awful things (leaking resources is ok though) if the interface contract
83 * (ref counting + balanced acquire/release of connections) is violated by the user
84 *
85 * In order to fulfill (1) and (2), all side-effecting operations within the connection manager follow a pattern:
86 *
87 * (1) Lock
88 * (2) Make state changes based on the operation
89 * (3) Build a set of work (completions, connect calls, releases, self-destruction) as appropriate to the operation
90 * (4) Unlock
91 * (5) Execute the task set
92 *
93 * Asynchronous work order failures are handled in the async callback, but immediate failures require
94 * us to relock and update the internal state. When there's an immediate connect failure, we use a
95 * conservative policy to fail all excess (beyond the # of pending connects) acquisitions; this allows us
96 * to avoid a possible recursive invocation (and potential failures) to connect again.
97 *
98 * Lifecycle
99 * Our connection manager implementation has a reasonably complex lifecycle.
100 *
101 * All state around the life cycle is protected by a lock. It seemed too risky and error-prone
102 * to try and mix an atomic ref count with the internal tracking counters we need.
103 *
104 * Over the course of its lifetime, a connection manager moves through two states:
105 *
106 * READY - connections may be acquired and released. When the external ref count for the manager
107 * drops to zero, the manager moves to:
108 *
109 * SHUTTING_DOWN - connections may no longer be acquired and released (how could they if the external
110 * ref count was accurate?) but in case of user ref errors, we simply fail attempts to do so rather
111 * than crash or underflow. While in this state, we wait for a set of tracking counters to all fall to zero:
112 *
113 * pending_connect_count - the # of unresolved calls to the http layer's connect logic
114 * open_connection_count - the # of connections for whom the release callback (from http) has not been invoked
115 * vended_connection_count - the # of connections held by external users that haven't been released. Under correct
116 * usage this should be zero before SHUTTING_DOWN is entered, but we attempt to handle incorrect usage gracefully.
117 *
118 * While shutting down, as pending connects resolve, we immediately release new incoming (from http) connections
119 *
120 * During the transition from READY to SHUTTING_DOWN, we flush the pending acquisition queue (with failure callbacks)
121 * and since we disallow new acquires, pending_acquisition_count should always be zero after the transition.
122 *
123 */
124 struct aws_http_connection_manager {
125 struct aws_allocator *allocator;
126
127 /*
128 * A union of external downstream dependencies (primarily global http API functions) and
129 * internal implementation references. Selectively overridden by tests in order to
130 * enable strong coverage of internal implementation details.
131 */
132 const struct aws_http_connection_manager_system_vtable *system_vtable;
133
134 /*
135 * Callback to invoke when shutdown has completed and all resources have been cleaned up.
136 */
137 aws_http_connection_manager_shutdown_complete_fn *shutdown_complete_callback;
138
139 /*
140 * User data to pass to the shutdown completion callback.
141 */
142 void *shutdown_complete_user_data;
143
144 /*
145 * Controls access to all mutable state on the connection manager
146 */
147 struct aws_mutex lock;
148
149 /*
150 * A manager can be in one of two states, READY or SHUTTING_DOWN. The state transition
151 * takes place when ref_count drops to zero.
152 */
153 enum aws_http_connection_manager_state_type state;
154
155 /*
156 * The number of all established, idle connections. So
157 * that we don't have compute the size of a linked list every time.
158 */
159 size_t idle_connection_count;
160
161 /*
162 * The set of all available, ready-to-be-used connections, as aws_idle_connection structs.
163 *
164 * This must be a LIFO stack. When connections are released by the user, they must be added on to the back.
165 * When we vend connections to the user, they must be removed from the back first.
166 * In this way, the list will always be sorted from oldest (in terms of time spent idle) to newest. This means
167 * we can always use the cull timestamp of the front connection as the next scheduled time for culling.
168 * It also means that when we cull connections, we can quit the loop as soon as we find a connection
169 * whose timestamp is greater than the current timestamp.
170 */
171 struct aws_linked_list idle_connections;
172
173 /*
174 * The set of all incomplete connection acquisition requests
175 */
176 struct aws_linked_list pending_acquisitions;
177
178 /*
179 * The number of all incomplete connection acquisition requests. So
180 * that we don't have compute the size of a linked list every time.
181 */
182 size_t pending_acquisition_count;
183
184 /*
185 * The number of pending new connection requests we have outstanding to the http
186 * layer.
187 */
188 size_t pending_connects_count;
189
190 /*
191 * The number of connections currently being used by external users.
192 */
193 size_t vended_connection_count;
194
195 /*
196 * Always equal to # of connection shutdown callbacks not yet invoked
197 * or equivalently:
198 *
199 * # of connections ever created by the manager - # shutdown callbacks received
200 */
201 size_t open_connection_count;
202
203 /*
204 * All the options needed to create an http connection
205 */
206 struct aws_client_bootstrap *bootstrap;
207 size_t initial_window_size;
208 struct aws_socket_options socket_options;
209 struct aws_tls_connection_options *tls_connection_options;
210 struct aws_http_proxy_config *proxy_config;
211 struct aws_http_connection_monitoring_options monitoring_options;
212 struct aws_string *host;
213 struct proxy_env_var_settings proxy_ev_settings;
214 struct aws_tls_connection_options *proxy_ev_tls_options;
215 uint16_t port;
216
217 /*
218 * The maximum number of connections this manager should ever have at once.
219 */
220 size_t max_connections;
221
222 /*
223 * Lifecycle tracking for the connection manager. Starts at 1.
224 *
225 * Once this drops to zero, the manager state transitions to shutting down
226 *
227 * The manager is deleted when all other tracking counters have returned to zero.
228 *
229 * We don't use an atomic here because the shutdown phase wants to check many different
230 * values. You could argue that we could use a sum of everything, but we still need the
231 * individual values for proper behavior and error checking during the ready state. Also,
232 * a hybrid atomic/lock solution felt excessively complicated and delicate.
233 */
234 size_t external_ref_count;
235
236 /*
237 * if set to true, read back pressure mechanism will be enabled.
238 */
239 bool enable_read_back_pressure;
240
241 /**
242 * If set to a non-zero value, then connections that stay in the pool longer than the specified
243 * timeout will be closed automatically.
244 */
245 uint64_t max_connection_idle_in_milliseconds;
246
247 /*
248 * Task to cull idle connections. This task is run periodically on the cull_event_loop if a non-zero
249 * culling time interval is specified.
250 */
251 struct aws_task *cull_task;
252 struct aws_event_loop *cull_event_loop;
253 };
254
255 struct aws_http_connection_manager_snapshot {
256 enum aws_http_connection_manager_state_type state;
257
258 size_t idle_connection_count;
259 size_t pending_acquisition_count;
260 size_t pending_connects_count;
261 size_t vended_connection_count;
262 size_t open_connection_count;
263
264 size_t external_ref_count;
265 };
266
267 /*
268 * Correct usage requires AWS_ZERO_STRUCT to have been called beforehand.
269 */
s_aws_http_connection_manager_get_snapshot(struct aws_http_connection_manager * manager,struct aws_http_connection_manager_snapshot * snapshot)270 static void s_aws_http_connection_manager_get_snapshot(
271 struct aws_http_connection_manager *manager,
272 struct aws_http_connection_manager_snapshot *snapshot) {
273
274 snapshot->state = manager->state;
275 snapshot->idle_connection_count = manager->idle_connection_count;
276 snapshot->pending_acquisition_count = manager->pending_acquisition_count;
277 snapshot->pending_connects_count = manager->pending_connects_count;
278 snapshot->vended_connection_count = manager->vended_connection_count;
279 snapshot->open_connection_count = manager->open_connection_count;
280
281 snapshot->external_ref_count = manager->external_ref_count;
282 }
283
s_aws_http_connection_manager_log_snapshot(struct aws_http_connection_manager * manager,struct aws_http_connection_manager_snapshot * snapshot)284 static void s_aws_http_connection_manager_log_snapshot(
285 struct aws_http_connection_manager *manager,
286 struct aws_http_connection_manager_snapshot *snapshot) {
287 if (snapshot->state != AWS_HCMST_UNINITIALIZED) {
288 AWS_LOGF_DEBUG(
289 AWS_LS_HTTP_CONNECTION_MANAGER,
290 "id=%p: snapshot - state=%d, idle_connection_count=%zu, pending_acquire_count=%zu, "
291 "pending_connect_count=%zu, vended_connection_count=%zu, open_connection_count=%zu, ref_count=%zu",
292 (void *)manager,
293 (int)snapshot->state,
294 snapshot->idle_connection_count,
295 snapshot->pending_acquisition_count,
296 snapshot->pending_connects_count,
297 snapshot->vended_connection_count,
298 snapshot->open_connection_count,
299 snapshot->external_ref_count);
300 } else {
301 AWS_LOGF_DEBUG(
302 AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: snapshot not initialized by control flow", (void *)manager);
303 }
304 }
305
aws_http_connection_manager_set_system_vtable(struct aws_http_connection_manager * manager,const struct aws_http_connection_manager_system_vtable * system_vtable)306 void aws_http_connection_manager_set_system_vtable(
307 struct aws_http_connection_manager *manager,
308 const struct aws_http_connection_manager_system_vtable *system_vtable) {
309 AWS_FATAL_ASSERT(aws_http_connection_manager_system_vtable_is_valid(system_vtable));
310
311 manager->system_vtable = system_vtable;
312 }
313
314 /*
315 * Hard Requirement: Manager's lock must held somewhere in the call stack
316 */
s_aws_http_connection_manager_should_destroy(struct aws_http_connection_manager * manager)317 static bool s_aws_http_connection_manager_should_destroy(struct aws_http_connection_manager *manager) {
318 if (manager->state != AWS_HCMST_SHUTTING_DOWN) {
319 return false;
320 }
321
322 if (manager->external_ref_count != 0) {
323 AWS_LOGF_ERROR(
324 AWS_LS_HTTP_CONNECTION_MANAGER,
325 "id=%p: ref count is non zero while in the shut down state",
326 (void *)manager);
327 return false;
328 }
329
330 if (manager->vended_connection_count > 0 || manager->pending_connects_count > 0 ||
331 manager->open_connection_count > 0) {
332 return false;
333 }
334
335 AWS_FATAL_ASSERT(manager->idle_connection_count == 0);
336
337 return true;
338 }
339
340 /*
341 * A struct that functions as both the pending acquisition tracker and the about-to-complete data.
342 *
343 * The list in the connection manager (pending_acquisitions) is the set of all acquisition requests that we
344 * haven't yet resolved.
345 *
346 * In order to make sure we never invoke callbacks while holding the manager's lock, in a number of places
347 * we build a list of one or more acquisitions to complete. Once the lock is released
348 * we complete all the acquisitions in the list using the data within the struct (hence why we have
349 * "result-oriented" members like connection and error_code). This means we can fail an acquisition
350 * simply by setting the error_code and moving it to the current transaction's completion list.
351 */
352 struct aws_http_connection_acquisition {
353 struct aws_allocator *allocator;
354 struct aws_linked_list_node node;
355 struct aws_http_connection_manager *manager; /* Only used by logging */
356 aws_http_connection_manager_on_connection_setup_fn *callback;
357 void *user_data;
358 struct aws_http_connection *connection;
359 int error_code;
360 struct aws_channel_task acquisition_task;
361 };
362
s_connection_acquisition_task(struct aws_channel_task * channel_task,void * arg,enum aws_task_status status)363 static void s_connection_acquisition_task(
364 struct aws_channel_task *channel_task,
365 void *arg,
366 enum aws_task_status status) {
367 (void)channel_task;
368
369 struct aws_http_connection_acquisition *pending_acquisition = arg;
370
371 /* this is a channel task. If it is canceled, that means the channel shutdown. In that case, that's equivalent
372 * to a closed connection. */
373 if (status != AWS_TASK_STATUS_RUN_READY) {
374 AWS_LOGF_WARN(
375 AWS_LS_HTTP_CONNECTION_MANAGER,
376 "id=%p: Failed to complete connection acquisition because the connection was closed",
377 (void *)pending_acquisition->manager);
378 pending_acquisition->callback(NULL, AWS_ERROR_HTTP_CONNECTION_CLOSED, pending_acquisition->user_data);
379 /* release it back to prevent a leak of the connection count. */
380 aws_http_connection_manager_release_connection(pending_acquisition->manager, pending_acquisition->connection);
381 } else {
382 AWS_LOGF_DEBUG(
383 AWS_LS_HTTP_CONNECTION_MANAGER,
384 "id=%p: Successfully completed connection acquisition with connection id=%p",
385 (void *)pending_acquisition->manager,
386 (void *)pending_acquisition->connection);
387 pending_acquisition->callback(
388 pending_acquisition->connection, pending_acquisition->error_code, pending_acquisition->user_data);
389 }
390
391 aws_mem_release(pending_acquisition->allocator, pending_acquisition);
392 }
393
394 /*
395 * Invokes a set of connection acquisition completion callbacks.
396 *
397 * Soft Requirement: The manager's lock must not be held in the callstack.
398 *
399 * Assumes that internal state (like pending_acquisition_count, vended_connection_count, etc...) have already been
400 * updated according to the list's contents.
401 */
s_aws_http_connection_manager_complete_acquisitions(struct aws_linked_list * acquisitions,struct aws_allocator * allocator)402 static void s_aws_http_connection_manager_complete_acquisitions(
403 struct aws_linked_list *acquisitions,
404 struct aws_allocator *allocator) {
405
406 while (!aws_linked_list_empty(acquisitions)) {
407 struct aws_linked_list_node *node = aws_linked_list_pop_front(acquisitions);
408 struct aws_http_connection_acquisition *pending_acquisition =
409 AWS_CONTAINER_OF(node, struct aws_http_connection_acquisition, node);
410
411 if (pending_acquisition->error_code == AWS_OP_SUCCESS) {
412
413 struct aws_channel *channel =
414 pending_acquisition->manager->system_vtable->connection_get_channel(pending_acquisition->connection);
415 AWS_PRECONDITION(channel);
416
417 /* For some workloads, going ahead and moving the connection callback to the connection's thread is a
418 * substantial performance improvement so let's do that */
419 if (!pending_acquisition->manager->system_vtable->is_callers_thread(channel)) {
420 aws_channel_task_init(
421 &pending_acquisition->acquisition_task,
422 s_connection_acquisition_task,
423 pending_acquisition,
424 "s_connection_acquisition_task");
425 aws_channel_schedule_task_now(channel, &pending_acquisition->acquisition_task);
426 return;
427 }
428 AWS_LOGF_DEBUG(
429 AWS_LS_HTTP_CONNECTION_MANAGER,
430 "id=%p: Successfully completed connection acquisition with connection id=%p",
431 (void *)pending_acquisition->manager,
432 (void *)pending_acquisition->connection);
433
434 } else {
435 AWS_LOGF_WARN(
436 AWS_LS_HTTP_CONNECTION_MANAGER,
437 "id=%p: Failed to complete connection acquisition with error_code %d(%s)",
438 (void *)pending_acquisition->manager,
439 pending_acquisition->error_code,
440 aws_error_str(pending_acquisition->error_code));
441 }
442
443 pending_acquisition->callback(
444 pending_acquisition->connection, pending_acquisition->error_code, pending_acquisition->user_data);
445 aws_mem_release(allocator, pending_acquisition);
446 }
447 }
448
449 /*
450 * Moves the first pending connection acquisition into a (task set) list. Call this while holding the lock to
451 * build the set of callbacks to be completed once the lock is released.
452 *
453 * Hard Requirement: Manager's lock must held somewhere in the call stack
454 *
455 * If this was a successful acquisition then connection is non-null
456 * If this was a failed acquisition then connection is null and error_code is hopefully a useful diagnostic (extreme
457 * edge cases exist where it may not be though)
458 */
s_aws_http_connection_manager_move_front_acquisition(struct aws_http_connection_manager * manager,struct aws_http_connection * connection,int error_code,struct aws_linked_list * output_list)459 static void s_aws_http_connection_manager_move_front_acquisition(
460 struct aws_http_connection_manager *manager,
461 struct aws_http_connection *connection,
462 int error_code,
463 struct aws_linked_list *output_list) {
464
465 AWS_FATAL_ASSERT(!aws_linked_list_empty(&manager->pending_acquisitions));
466 struct aws_linked_list_node *node = aws_linked_list_pop_front(&manager->pending_acquisitions);
467
468 AWS_FATAL_ASSERT(manager->pending_acquisition_count > 0);
469 --manager->pending_acquisition_count;
470
471 if (error_code == AWS_ERROR_SUCCESS && connection == NULL) {
472 AWS_LOGF_FATAL(
473 AWS_LS_HTTP_CONNECTION_MANAGER,
474 "id=%p: Connection acquisition completed with NULL connection and no error code. Investigate.",
475 (void *)manager);
476 error_code = AWS_ERROR_UNKNOWN;
477 }
478
479 struct aws_http_connection_acquisition *pending_acquisition =
480 AWS_CONTAINER_OF(node, struct aws_http_connection_acquisition, node);
481 pending_acquisition->connection = connection;
482 pending_acquisition->error_code = error_code;
483
484 aws_linked_list_push_back(output_list, node);
485 }
486
487 /*
488 * Encompasses all of the external operations that need to be done for various
489 * events:
490 * manager release
491 * connection release
492 * connection acquire
493 * connection_setup
494 * connection_shutdown
495 *
496 * The transaction is built under the manager's lock (and the internal state is updated optimistically),
497 * but then executed outside of it.
498 */
499 struct aws_connection_management_transaction {
500 struct aws_http_connection_manager *manager;
501 struct aws_allocator *allocator;
502 struct aws_linked_list completions;
503 struct aws_http_connection *connection_to_release;
504 struct aws_linked_list connections_to_release; /* <struct aws_idle_connection> */
505 struct aws_http_connection_manager_snapshot snapshot;
506 size_t new_connections;
507 bool should_destroy_manager;
508 };
509
s_aws_connection_management_transaction_init(struct aws_connection_management_transaction * work,struct aws_http_connection_manager * manager)510 static void s_aws_connection_management_transaction_init(
511 struct aws_connection_management_transaction *work,
512 struct aws_http_connection_manager *manager) {
513 AWS_ZERO_STRUCT(*work);
514
515 aws_linked_list_init(&work->connections_to_release);
516 aws_linked_list_init(&work->completions);
517 work->manager = manager;
518 work->allocator = manager->allocator;
519 }
520
s_aws_connection_management_transaction_clean_up(struct aws_connection_management_transaction * work)521 static void s_aws_connection_management_transaction_clean_up(struct aws_connection_management_transaction *work) {
522 AWS_FATAL_ASSERT(aws_linked_list_empty(&work->connections_to_release));
523 AWS_FATAL_ASSERT(aws_linked_list_empty(&work->completions));
524 }
525
s_aws_http_connection_manager_build_transaction(struct aws_connection_management_transaction * work)526 static void s_aws_http_connection_manager_build_transaction(struct aws_connection_management_transaction *work) {
527 struct aws_http_connection_manager *manager = work->manager;
528
529 if (manager->state == AWS_HCMST_READY) {
530 /*
531 * Step 1 - If there's free connections, complete acquisition requests
532 */
533 while (!aws_linked_list_empty(&manager->idle_connections) > 0 && manager->pending_acquisition_count > 0) {
534 AWS_FATAL_ASSERT(manager->idle_connection_count >= 1);
535 /*
536 * It is absolutely critical that this is pop_back and not front. By making the idle connections
537 * a LIFO stack, the list will always be sorted from oldest (in terms of idle time) to newest. This means
538 * we can always use the cull timestamp of the first connection as the next scheduled time for culling.
539 * It also means that when we cull connections, we can quit the loop as soon as we find a connection
540 * whose timestamp is greater than the current timestamp.
541 */
542 struct aws_linked_list_node *node = aws_linked_list_pop_back(&manager->idle_connections);
543 struct aws_idle_connection *idle_connection = AWS_CONTAINER_OF(node, struct aws_idle_connection, node);
544 struct aws_http_connection *connection = idle_connection->connection;
545
546 AWS_LOGF_DEBUG(
547 AWS_LS_HTTP_CONNECTION_MANAGER,
548 "id=%p: Grabbing pooled connection (%p)",
549 (void *)manager,
550 (void *)connection);
551 s_aws_http_connection_manager_move_front_acquisition(
552 manager, connection, AWS_ERROR_SUCCESS, &work->completions);
553 ++manager->vended_connection_count;
554 --manager->idle_connection_count;
555 aws_mem_release(idle_connection->allocator, idle_connection);
556 }
557
558 /*
559 * Step 2 - if there's excess pending acquisitions and we have room to make more, make more
560 */
561 if (manager->pending_acquisition_count > manager->pending_connects_count) {
562 AWS_FATAL_ASSERT(
563 manager->max_connections >= manager->vended_connection_count + manager->pending_connects_count);
564
565 work->new_connections = manager->pending_acquisition_count - manager->pending_connects_count;
566 size_t max_new_connections =
567 manager->max_connections - (manager->vended_connection_count + manager->pending_connects_count);
568
569 if (work->new_connections > max_new_connections) {
570 work->new_connections = max_new_connections;
571 }
572
573 manager->pending_connects_count += work->new_connections;
574 }
575 } else {
576 /*
577 * swap our internal connection set with the empty work set
578 */
579 AWS_FATAL_ASSERT(aws_linked_list_empty(&work->connections_to_release));
580 aws_linked_list_swap_contents(&manager->idle_connections, &work->connections_to_release);
581 manager->idle_connection_count = 0;
582
583 /*
584 * Move all manager pending acquisitions to the work completion list
585 */
586 while (!aws_linked_list_empty(&manager->pending_acquisitions)) {
587 AWS_LOGF_DEBUG(
588 AWS_LS_HTTP_CONNECTION_MANAGER,
589 "id=%p: Failing pending connection acquisition due to manager shut down",
590 (void *)manager);
591 s_aws_http_connection_manager_move_front_acquisition(
592 manager, NULL, AWS_ERROR_HTTP_CONNECTION_MANAGER_SHUTTING_DOWN, &work->completions);
593 }
594
595 AWS_LOGF_INFO(
596 AWS_LS_HTTP_CONNECTION_MANAGER,
597 "id=%p: manager release, failing %zu pending acquisitions",
598 (void *)manager,
599 manager->pending_acquisition_count);
600 manager->pending_acquisition_count = 0;
601
602 work->should_destroy_manager = s_aws_http_connection_manager_should_destroy(manager);
603 }
604
605 s_aws_http_connection_manager_get_snapshot(manager, &work->snapshot);
606 }
607
608 static void s_aws_http_connection_manager_execute_transaction(struct aws_connection_management_transaction *work);
609
610 /*
611 * The final last gasp of a connection manager where memory is cleaned up. Destruction is split up into two parts,
612 * a begin and a finish. Idle connection culling requires a scheduled task on an arbitrary event loop. If idle
613 * connection culling is on then this task must be cancelled before destruction can finish, but you can only cancel
614 * a task from the same event loop that it is scheduled on. To resolve this, when using idle connection culling,
615 * we schedule a finish destruction task on the event loop that the culling task is on. This finish task
616 * cancels the culling task and then calls this function. If we are not using idle connection culling, we can
617 * call this function immediately from the start of destruction.
618 */
s_aws_http_connection_manager_finish_destroy(struct aws_http_connection_manager * manager)619 static void s_aws_http_connection_manager_finish_destroy(struct aws_http_connection_manager *manager) {
620 if (manager == NULL) {
621 return;
622 }
623
624 AWS_LOGF_INFO(AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: Destroying self", (void *)manager);
625
626 AWS_FATAL_ASSERT(manager->pending_connects_count == 0);
627 AWS_FATAL_ASSERT(manager->vended_connection_count == 0);
628 AWS_FATAL_ASSERT(manager->pending_acquisition_count == 0);
629 AWS_FATAL_ASSERT(manager->open_connection_count == 0);
630 AWS_FATAL_ASSERT(aws_linked_list_empty(&manager->pending_acquisitions));
631 AWS_FATAL_ASSERT(aws_linked_list_empty(&manager->idle_connections));
632
633 aws_string_destroy(manager->host);
634 if (manager->tls_connection_options) {
635 aws_tls_connection_options_clean_up(manager->tls_connection_options);
636 aws_mem_release(manager->allocator, manager->tls_connection_options);
637 }
638 if (manager->proxy_ev_tls_options) {
639 aws_tls_connection_options_clean_up(manager->proxy_ev_tls_options);
640 aws_mem_release(manager->allocator, manager->proxy_ev_tls_options);
641 }
642 if (manager->proxy_config) {
643 aws_http_proxy_config_destroy(manager->proxy_config);
644 }
645
646 /*
647 * If this task exists then we are actually in the corresponding event loop running the final destruction task.
648 * In that case, we've already cancelled this task and when you cancel, it runs synchronously. So in that
649 * case the task has run as cancelled, it was not rescheduled, and so we can safely release the memory.
650 */
651 if (manager->cull_task) {
652 aws_mem_release(manager->allocator, manager->cull_task);
653 }
654
655 aws_mutex_clean_up(&manager->lock);
656
657 aws_client_bootstrap_release(manager->bootstrap);
658
659 if (manager->shutdown_complete_callback) {
660 manager->shutdown_complete_callback(manager->shutdown_complete_user_data);
661 }
662
663 aws_mem_release(manager->allocator, manager);
664 }
665
666 /* This is scheduled to run on the cull task's event loop. If there's no cull task we just destroy the
667 * manager directly without a cross-thread task. */
s_final_destruction_task(struct aws_task * task,void * arg,enum aws_task_status status)668 static void s_final_destruction_task(struct aws_task *task, void *arg, enum aws_task_status status) {
669 (void)status;
670 struct aws_http_connection_manager *manager = arg;
671 struct aws_allocator *allocator = manager->allocator;
672
673 if (manager->cull_task) {
674 AWS_FATAL_ASSERT(manager->cull_event_loop != NULL);
675 aws_event_loop_cancel_task(manager->cull_event_loop, manager->cull_task);
676 }
677
678 s_aws_http_connection_manager_finish_destroy(manager);
679
680 aws_mem_release(allocator, task);
681 }
682
s_aws_http_connection_manager_begin_destroy(struct aws_http_connection_manager * manager)683 static void s_aws_http_connection_manager_begin_destroy(struct aws_http_connection_manager *manager) {
684 if (manager == NULL) {
685 return;
686 }
687
688 /*
689 * If we have a cull task running then we have to cancel it. But you can only cancel tasks within the event
690 * loop that the task is scheduled on. So to solve this case, if there's a cull task, rather than doing
691 * cleanup synchronously, we schedule a final destruction task (on the cull event loop) which cancels the
692 * cull task before going on to release all the memory and notify the shutdown callback.
693 *
694 * If there's no cull task we can just cleanup synchronously.
695 */
696 if (manager->cull_event_loop != NULL) {
697 AWS_FATAL_ASSERT(manager->cull_task);
698 struct aws_task *final_destruction_task = aws_mem_calloc(manager->allocator, 1, sizeof(struct aws_task));
699 aws_task_init(final_destruction_task, s_final_destruction_task, manager, "final_scheduled_destruction");
700 aws_event_loop_schedule_task_now(manager->cull_event_loop, final_destruction_task);
701 } else {
702 s_aws_http_connection_manager_finish_destroy(manager);
703 }
704 }
705
706 static void s_cull_task(struct aws_task *task, void *arg, enum aws_task_status status);
s_schedule_connection_culling(struct aws_http_connection_manager * manager)707 static void s_schedule_connection_culling(struct aws_http_connection_manager *manager) {
708 if (manager->max_connection_idle_in_milliseconds == 0) {
709 return;
710 }
711
712 if (manager->cull_task == NULL) {
713 manager->cull_task = aws_mem_calloc(manager->allocator, 1, sizeof(struct aws_task));
714 if (manager->cull_task == NULL) {
715 return;
716 }
717
718 aws_task_init(manager->cull_task, s_cull_task, manager, "cull_idle_connections");
719 }
720
721 if (manager->cull_event_loop == NULL) {
722 manager->cull_event_loop = aws_event_loop_group_get_next_loop(manager->bootstrap->event_loop_group);
723 }
724
725 if (manager->cull_event_loop == NULL) {
726 goto on_error;
727 }
728
729 uint64_t cull_task_time = 0;
730 const struct aws_linked_list_node *end = aws_linked_list_end(&manager->idle_connections);
731 struct aws_linked_list_node *oldest_node = aws_linked_list_begin(&manager->idle_connections);
732 if (oldest_node != end) {
733 /*
734 * Since the connections are in LIFO order in the list, the front of the list has the closest
735 * cull time.
736 */
737 struct aws_idle_connection *oldest_idle_connection =
738 AWS_CONTAINER_OF(oldest_node, struct aws_idle_connection, node);
739 cull_task_time = oldest_idle_connection->cull_timestamp;
740 } else {
741 /*
742 * There are no connections in the list, so the absolute minimum anything could be culled is the full
743 * culling interval from now.
744 */
745 uint64_t now = 0;
746 if (manager->system_vtable->get_monotonic_time(&now)) {
747 goto on_error;
748 }
749 cull_task_time =
750 now + aws_timestamp_convert(
751 manager->max_connection_idle_in_milliseconds, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL);
752 }
753
754 aws_event_loop_schedule_task_future(manager->cull_event_loop, manager->cull_task, cull_task_time);
755
756 return;
757
758 on_error:
759
760 manager->cull_event_loop = NULL;
761 aws_mem_release(manager->allocator, manager->cull_task);
762 manager->cull_task = NULL;
763 }
764
aws_http_connection_manager_new(struct aws_allocator * allocator,struct aws_http_connection_manager_options * options)765 struct aws_http_connection_manager *aws_http_connection_manager_new(
766 struct aws_allocator *allocator,
767 struct aws_http_connection_manager_options *options) {
768
769 aws_http_fatal_assert_library_initialized();
770
771 if (!options) {
772 AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION_MANAGER, "Invalid options - options is null");
773 aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
774 return NULL;
775 }
776
777 if (!options->socket_options) {
778 AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION_MANAGER, "Invalid options - socket_options is null");
779 aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
780 return NULL;
781 }
782
783 if (options->max_connections == 0) {
784 AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION_MANAGER, "Invalid options - max_connections cannot be 0");
785 aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
786 return NULL;
787 }
788
789 struct aws_http_connection_manager *manager =
790 aws_mem_calloc(allocator, 1, sizeof(struct aws_http_connection_manager));
791 if (manager == NULL) {
792 return NULL;
793 }
794
795 manager->allocator = allocator;
796
797 if (aws_mutex_init(&manager->lock)) {
798 goto on_error;
799 }
800
801 aws_linked_list_init(&manager->idle_connections);
802 aws_linked_list_init(&manager->pending_acquisitions);
803
804 manager->host = aws_string_new_from_cursor(allocator, &options->host);
805 if (manager->host == NULL) {
806 goto on_error;
807 }
808
809 if (options->tls_connection_options) {
810 manager->tls_connection_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options));
811 if (aws_tls_connection_options_copy(manager->tls_connection_options, options->tls_connection_options)) {
812 goto on_error;
813 }
814 }
815 if (options->proxy_options) {
816 manager->proxy_config = aws_http_proxy_config_new_from_manager_options(allocator, options);
817 if (manager->proxy_config == NULL) {
818 goto on_error;
819 }
820 }
821
822 if (options->monitoring_options) {
823 manager->monitoring_options = *options->monitoring_options;
824 }
825
826 manager->state = AWS_HCMST_READY;
827 manager->initial_window_size = options->initial_window_size;
828 manager->port = options->port;
829 manager->max_connections = options->max_connections;
830 manager->socket_options = *options->socket_options;
831 manager->bootstrap = aws_client_bootstrap_acquire(options->bootstrap);
832 manager->system_vtable = g_aws_http_connection_manager_default_system_vtable_ptr;
833 manager->external_ref_count = 1;
834 manager->shutdown_complete_callback = options->shutdown_complete_callback;
835 manager->shutdown_complete_user_data = options->shutdown_complete_user_data;
836 manager->enable_read_back_pressure = options->enable_read_back_pressure;
837 manager->max_connection_idle_in_milliseconds = options->max_connection_idle_in_milliseconds;
838 if (options->proxy_ev_settings) {
839 manager->proxy_ev_settings = *options->proxy_ev_settings;
840 }
841 if (manager->proxy_ev_settings.tls_options) {
842 manager->proxy_ev_tls_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_tls_connection_options));
843 if (aws_tls_connection_options_copy(manager->proxy_ev_tls_options, manager->proxy_ev_settings.tls_options)) {
844 goto on_error;
845 }
846 manager->proxy_ev_settings.tls_options = manager->proxy_ev_tls_options;
847 }
848 s_schedule_connection_culling(manager);
849
850 AWS_LOGF_INFO(AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: Successfully created", (void *)manager);
851
852 return manager;
853
854 on_error:
855
856 s_aws_http_connection_manager_begin_destroy(manager);
857
858 return NULL;
859 }
860
aws_http_connection_manager_acquire(struct aws_http_connection_manager * manager)861 void aws_http_connection_manager_acquire(struct aws_http_connection_manager *manager) {
862 aws_mutex_lock(&manager->lock);
863 AWS_FATAL_ASSERT(manager->external_ref_count > 0);
864 manager->external_ref_count += 1;
865 aws_mutex_unlock(&manager->lock);
866 }
867
aws_http_connection_manager_release(struct aws_http_connection_manager * manager)868 void aws_http_connection_manager_release(struct aws_http_connection_manager *manager) {
869 struct aws_connection_management_transaction work;
870 s_aws_connection_management_transaction_init(&work, manager);
871
872 AWS_LOGF_INFO(AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: release", (void *)manager);
873
874 aws_mutex_lock(&manager->lock);
875
876 if (manager->external_ref_count > 0) {
877 manager->external_ref_count -= 1;
878
879 if (manager->external_ref_count == 0) {
880 AWS_LOGF_INFO(
881 AWS_LS_HTTP_CONNECTION_MANAGER,
882 "id=%p: ref count now zero, starting shut down process",
883 (void *)manager);
884 manager->state = AWS_HCMST_SHUTTING_DOWN;
885 s_aws_http_connection_manager_build_transaction(&work);
886 }
887 } else {
888 AWS_LOGF_ERROR(
889 AWS_LS_HTTP_CONNECTION_MANAGER,
890 "id=%p: Connection manager release called with a zero reference count",
891 (void *)manager);
892 }
893
894 aws_mutex_unlock(&manager->lock);
895
896 s_aws_http_connection_manager_execute_transaction(&work);
897 }
898
899 static void s_aws_http_connection_manager_on_connection_setup(
900 struct aws_http_connection *connection,
901 int error_code,
902 void *user_data);
903
904 static void s_aws_http_connection_manager_on_connection_shutdown(
905 struct aws_http_connection *connection,
906 int error_code,
907 void *user_data);
908
s_aws_http_connection_manager_new_connection(struct aws_http_connection_manager * manager)909 static int s_aws_http_connection_manager_new_connection(struct aws_http_connection_manager *manager) {
910 struct aws_http_client_connection_options options;
911 AWS_ZERO_STRUCT(options);
912 options.self_size = sizeof(struct aws_http_client_connection_options);
913 options.bootstrap = manager->bootstrap;
914 options.tls_options = manager->tls_connection_options;
915 options.allocator = manager->allocator;
916 options.user_data = manager;
917 options.host_name = aws_byte_cursor_from_string(manager->host);
918 options.port = manager->port;
919 options.initial_window_size = manager->initial_window_size;
920 options.socket_options = &manager->socket_options;
921 options.on_setup = s_aws_http_connection_manager_on_connection_setup;
922 options.on_shutdown = s_aws_http_connection_manager_on_connection_shutdown;
923 options.manual_window_management = manager->enable_read_back_pressure;
924 options.proxy_ev_settings = &manager->proxy_ev_settings;
925
926 if (aws_http_connection_monitoring_options_is_valid(&manager->monitoring_options)) {
927 options.monitoring_options = &manager->monitoring_options;
928 }
929
930 struct aws_http_proxy_options proxy_options;
931 AWS_ZERO_STRUCT(proxy_options);
932
933 if (manager->proxy_config) {
934 aws_http_proxy_options_init_from_config(&proxy_options, manager->proxy_config);
935 options.proxy_options = &proxy_options;
936 }
937
938 if (manager->system_vtable->create_connection(&options)) {
939 AWS_LOGF_ERROR(
940 AWS_LS_HTTP_CONNECTION_MANAGER,
941 "id=%p: http connection creation failed with error code %d(%s)",
942 (void *)manager,
943 aws_last_error(),
944 aws_error_str(aws_last_error()));
945 return AWS_OP_ERR;
946 }
947
948 return AWS_OP_SUCCESS;
949 }
950
s_aws_http_connection_manager_execute_transaction(struct aws_connection_management_transaction * work)951 static void s_aws_http_connection_manager_execute_transaction(struct aws_connection_management_transaction *work) {
952
953 struct aws_http_connection_manager *manager = work->manager;
954
955 bool should_destroy = work->should_destroy_manager;
956 int representative_error = 0;
957 size_t new_connection_failures = 0;
958
959 /*
960 * Step 1 - Logging
961 */
962 s_aws_http_connection_manager_log_snapshot(manager, &work->snapshot);
963
964 /*
965 * Step 2 - Perform any requested connection releases
966 */
967 while (!aws_linked_list_empty(&work->connections_to_release)) {
968 struct aws_linked_list_node *node = aws_linked_list_pop_back(&work->connections_to_release);
969 struct aws_idle_connection *idle_connection = AWS_CONTAINER_OF(node, struct aws_idle_connection, node);
970
971 AWS_LOGF_INFO(
972 AWS_LS_HTTP_CONNECTION_MANAGER,
973 "id=%p: Releasing connection (id=%p)",
974 (void *)manager,
975 (void *)idle_connection->connection);
976 manager->system_vtable->release_connection(idle_connection->connection);
977 aws_mem_release(idle_connection->allocator, idle_connection);
978 }
979
980 if (work->connection_to_release) {
981 AWS_LOGF_INFO(
982 AWS_LS_HTTP_CONNECTION_MANAGER,
983 "id=%p: Releasing connection (id=%p)",
984 (void *)manager,
985 (void *)work->connection_to_release);
986 manager->system_vtable->release_connection(work->connection_to_release);
987 }
988
989 /*
990 * Step 3 - Make new connections
991 */
992 struct aws_array_list errors;
993 AWS_ZERO_STRUCT(errors);
994 /* Even if we can't init this array, we still need to invoke error callbacks properly */
995 bool push_errors = false;
996
997 if (work->new_connections > 0) {
998 AWS_LOGF_INFO(
999 AWS_LS_HTTP_CONNECTION_MANAGER,
1000 "id=%p: Requesting %zu new connections from http",
1001 (void *)manager,
1002 work->new_connections);
1003 push_errors = aws_array_list_init_dynamic(&errors, work->allocator, work->new_connections, sizeof(int)) ==
1004 AWS_ERROR_SUCCESS;
1005 }
1006
1007 for (size_t i = 0; i < work->new_connections; ++i) {
1008 if (s_aws_http_connection_manager_new_connection(manager)) {
1009 ++new_connection_failures;
1010 representative_error = aws_last_error();
1011 if (push_errors) {
1012 AWS_FATAL_ASSERT(aws_array_list_push_back(&errors, &representative_error) == AWS_OP_SUCCESS);
1013 }
1014 }
1015 }
1016
1017 if (new_connection_failures > 0) {
1018 /*
1019 * We failed and aren't going to receive a callback, but the current state assumes we will receive
1020 * a callback. So we need to re-lock and update the state ourselves.
1021 */
1022 aws_mutex_lock(&manager->lock);
1023
1024 AWS_FATAL_ASSERT(manager->pending_connects_count >= new_connection_failures);
1025 manager->pending_connects_count -= new_connection_failures;
1026
1027 /*
1028 * Rather than failing one acquisition for each connection failure, if there's at least one
1029 * connection failure, we instead fail all excess acquisitions, since there's no pending
1030 * connect that will necessarily resolve them.
1031 *
1032 * Try to correspond an error with the acquisition failure, but as a fallback just use the
1033 * representative error.
1034 */
1035 size_t i = 0;
1036 while (manager->pending_acquisition_count > manager->pending_connects_count) {
1037 int error = representative_error;
1038 if (i < aws_array_list_length(&errors)) {
1039 aws_array_list_get_at(&errors, &error, i);
1040 }
1041
1042 AWS_LOGF_DEBUG(
1043 AWS_LS_HTTP_CONNECTION_MANAGER,
1044 "id=%p: Failing excess connection acquisition with error code %d",
1045 (void *)manager,
1046 (int)error);
1047 s_aws_http_connection_manager_move_front_acquisition(manager, NULL, error, &work->completions);
1048 ++i;
1049 }
1050
1051 should_destroy = s_aws_http_connection_manager_should_destroy(manager);
1052
1053 aws_mutex_unlock(&manager->lock);
1054 }
1055
1056 /*
1057 * Step 4 - Perform acquisition callbacks
1058 */
1059 s_aws_http_connection_manager_complete_acquisitions(&work->completions, work->allocator);
1060
1061 aws_array_list_clean_up(&errors);
1062
1063 /*
1064 * Step 5 - destroy the manager if necessary
1065 */
1066 if (should_destroy) {
1067 s_aws_http_connection_manager_begin_destroy(manager);
1068 }
1069
1070 /*
1071 * Step 6 - Clean up work. Do this here rather than at the end of every caller.
1072 */
1073 s_aws_connection_management_transaction_clean_up(work);
1074 }
1075
aws_http_connection_manager_acquire_connection(struct aws_http_connection_manager * manager,aws_http_connection_manager_on_connection_setup_fn * callback,void * user_data)1076 void aws_http_connection_manager_acquire_connection(
1077 struct aws_http_connection_manager *manager,
1078 aws_http_connection_manager_on_connection_setup_fn *callback,
1079 void *user_data) {
1080
1081 AWS_LOGF_DEBUG(AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: Acquire connection", (void *)manager);
1082
1083 struct aws_http_connection_acquisition *request =
1084 aws_mem_calloc(manager->allocator, 1, sizeof(struct aws_http_connection_acquisition));
1085 if (request == NULL) {
1086 callback(NULL, aws_last_error(), user_data);
1087 return;
1088 }
1089
1090 request->allocator = manager->allocator;
1091 request->callback = callback;
1092 request->user_data = user_data;
1093 request->manager = manager;
1094
1095 struct aws_connection_management_transaction work;
1096 s_aws_connection_management_transaction_init(&work, manager);
1097
1098 aws_mutex_lock(&manager->lock);
1099
1100 if (manager->state != AWS_HCMST_READY) {
1101 AWS_LOGF_ERROR(
1102 AWS_LS_HTTP_CONNECTION_MANAGER,
1103 "id=%p: Acquire connection called when manager in shut down state",
1104 (void *)manager);
1105
1106 request->error_code = AWS_ERROR_HTTP_CONNECTION_MANAGER_INVALID_STATE_FOR_ACQUIRE;
1107 }
1108
1109 aws_linked_list_push_back(&manager->pending_acquisitions, &request->node);
1110 ++manager->pending_acquisition_count;
1111
1112 s_aws_http_connection_manager_build_transaction(&work);
1113
1114 aws_mutex_unlock(&manager->lock);
1115
1116 s_aws_http_connection_manager_execute_transaction(&work);
1117 }
1118
s_idle_connection(struct aws_http_connection_manager * manager,struct aws_http_connection * connection)1119 static int s_idle_connection(struct aws_http_connection_manager *manager, struct aws_http_connection *connection) {
1120 struct aws_idle_connection *idle_connection =
1121 aws_mem_calloc(manager->allocator, 1, sizeof(struct aws_idle_connection));
1122 if (idle_connection == NULL) {
1123 return AWS_OP_ERR;
1124 }
1125
1126 idle_connection->allocator = manager->allocator;
1127 idle_connection->connection = connection;
1128
1129 uint64_t idle_start_timestamp = 0;
1130 if (manager->system_vtable->get_monotonic_time(&idle_start_timestamp)) {
1131 goto on_error;
1132 }
1133
1134 idle_connection->cull_timestamp =
1135 idle_start_timestamp +
1136 aws_timestamp_convert(
1137 manager->max_connection_idle_in_milliseconds, AWS_TIMESTAMP_MILLIS, AWS_TIMESTAMP_NANOS, NULL);
1138
1139 aws_linked_list_push_back(&manager->idle_connections, &idle_connection->node);
1140 ++manager->idle_connection_count;
1141
1142 return AWS_OP_SUCCESS;
1143
1144 on_error:
1145
1146 aws_mem_release(idle_connection->allocator, idle_connection);
1147
1148 return AWS_OP_ERR;
1149 }
1150
aws_http_connection_manager_release_connection(struct aws_http_connection_manager * manager,struct aws_http_connection * connection)1151 int aws_http_connection_manager_release_connection(
1152 struct aws_http_connection_manager *manager,
1153 struct aws_http_connection *connection) {
1154
1155 struct aws_connection_management_transaction work;
1156 s_aws_connection_management_transaction_init(&work, manager);
1157
1158 int result = AWS_OP_ERR;
1159 bool should_release_connection = !manager->system_vtable->is_connection_available(connection);
1160
1161 AWS_LOGF_DEBUG(
1162 AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: Releasing connection (id=%p)", (void *)manager, (void *)connection);
1163
1164 aws_mutex_lock(&manager->lock);
1165
1166 /* We're probably hosed in this case, but let's not underflow */
1167 if (manager->vended_connection_count == 0) {
1168 AWS_LOGF_FATAL(
1169 AWS_LS_HTTP_CONNECTION_MANAGER,
1170 "id=%p: Connection released when vended connection count is zero",
1171 (void *)manager);
1172 aws_raise_error(AWS_ERROR_HTTP_CONNECTION_MANAGER_VENDED_CONNECTION_UNDERFLOW);
1173 goto release;
1174 }
1175
1176 result = AWS_OP_SUCCESS;
1177
1178 --manager->vended_connection_count;
1179
1180 if (!should_release_connection) {
1181 if (s_idle_connection(manager, connection)) {
1182 should_release_connection = true;
1183 }
1184 }
1185
1186 s_aws_http_connection_manager_build_transaction(&work);
1187 if (should_release_connection) {
1188 work.connection_to_release = connection;
1189 }
1190
1191 release:
1192
1193 aws_mutex_unlock(&manager->lock);
1194
1195 s_aws_http_connection_manager_execute_transaction(&work);
1196
1197 return result;
1198 }
1199
s_aws_http_connection_manager_on_connection_setup(struct aws_http_connection * connection,int error_code,void * user_data)1200 static void s_aws_http_connection_manager_on_connection_setup(
1201 struct aws_http_connection *connection,
1202 int error_code,
1203 void *user_data) {
1204 struct aws_http_connection_manager *manager = user_data;
1205
1206 struct aws_connection_management_transaction work;
1207 s_aws_connection_management_transaction_init(&work, manager);
1208
1209 if (connection != NULL) {
1210 AWS_LOGF_DEBUG(
1211 AWS_LS_HTTP_CONNECTION_MANAGER,
1212 "id=%p: Received new connection (id=%p) from http layer",
1213 (void *)manager,
1214 (void *)connection);
1215 } else {
1216 AWS_LOGF_WARN(
1217 AWS_LS_HTTP_CONNECTION_MANAGER,
1218 "id=%p: Failed to obtain new connection from http layer, error %d(%s)",
1219 (void *)manager,
1220 error_code,
1221 aws_error_str(error_code));
1222 }
1223
1224 aws_mutex_lock(&manager->lock);
1225
1226 bool is_shutting_down = manager->state == AWS_HCMST_SHUTTING_DOWN;
1227
1228 AWS_FATAL_ASSERT(manager->pending_connects_count > 0);
1229 --manager->pending_connects_count;
1230
1231 if (connection != NULL) {
1232 if (is_shutting_down || s_idle_connection(manager, connection)) {
1233 /*
1234 * release it immediately
1235 */
1236 AWS_LOGF_DEBUG(
1237 AWS_LS_HTTP_CONNECTION_MANAGER,
1238 "id=%p: New connection (id=%p) releasing immediately",
1239 (void *)manager,
1240 (void *)connection);
1241 work.connection_to_release = connection;
1242 }
1243 ++manager->open_connection_count;
1244 } else {
1245 /*
1246 * To be safe, if we have an excess of pending acquisitions (beyond the number of pending
1247 * connects), we need to fail all of the excess. Technically, we might be able to try and
1248 * make a new connection, if there's room, but that could lead to some bad failure loops.
1249 *
1250 * This won't happen during shutdown since there are no pending acquisitions at that point.
1251 */
1252 while (manager->pending_acquisition_count > manager->pending_connects_count) {
1253 AWS_LOGF_DEBUG(
1254 AWS_LS_HTTP_CONNECTION_MANAGER,
1255 "id=%p: Failing excess connection acquisition with error code %d",
1256 (void *)manager,
1257 (int)error_code);
1258 s_aws_http_connection_manager_move_front_acquisition(manager, NULL, error_code, &work.completions);
1259 }
1260 }
1261
1262 s_aws_http_connection_manager_build_transaction(&work);
1263
1264 aws_mutex_unlock(&manager->lock);
1265
1266 s_aws_http_connection_manager_execute_transaction(&work);
1267 }
1268
s_aws_http_connection_manager_on_connection_shutdown(struct aws_http_connection * connection,int error_code,void * user_data)1269 static void s_aws_http_connection_manager_on_connection_shutdown(
1270 struct aws_http_connection *connection,
1271 int error_code,
1272 void *user_data) {
1273 (void)error_code;
1274
1275 struct aws_http_connection_manager *manager = user_data;
1276
1277 AWS_LOGF_DEBUG(
1278 AWS_LS_HTTP_CONNECTION_MANAGER,
1279 "id=%p: shutdown received for connection (id=%p)",
1280 (void *)manager,
1281 (void *)connection);
1282
1283 struct aws_connection_management_transaction work;
1284 s_aws_connection_management_transaction_init(&work, manager);
1285
1286 aws_mutex_lock(&manager->lock);
1287
1288 AWS_FATAL_ASSERT(manager->open_connection_count > 0);
1289 --manager->open_connection_count;
1290
1291 /*
1292 * Find and, if found, remove it from idle connections
1293 */
1294 const struct aws_linked_list_node *end = aws_linked_list_end(&manager->idle_connections);
1295 for (struct aws_linked_list_node *node = aws_linked_list_begin(&manager->idle_connections); node != end;
1296 node = aws_linked_list_next(node)) {
1297 struct aws_idle_connection *current_idle_connection = AWS_CONTAINER_OF(node, struct aws_idle_connection, node);
1298 if (current_idle_connection->connection == connection) {
1299 aws_linked_list_remove(node);
1300 work.connection_to_release = connection;
1301 aws_mem_release(current_idle_connection->allocator, current_idle_connection);
1302 --manager->idle_connection_count;
1303 break;
1304 }
1305 }
1306
1307 s_aws_http_connection_manager_build_transaction(&work);
1308
1309 aws_mutex_unlock(&manager->lock);
1310
1311 s_aws_http_connection_manager_execute_transaction(&work);
1312 }
1313
s_cull_idle_connections(struct aws_http_connection_manager * manager)1314 static void s_cull_idle_connections(struct aws_http_connection_manager *manager) {
1315 AWS_LOGF_INFO(AWS_LS_HTTP_CONNECTION_MANAGER, "id=%p: culling idle connections", (void *)manager);
1316
1317 if (manager == NULL || manager->max_connection_idle_in_milliseconds == 0) {
1318 return;
1319 }
1320
1321 uint64_t now = 0;
1322 if (manager->system_vtable->get_monotonic_time(&now)) {
1323 return;
1324 }
1325
1326 struct aws_connection_management_transaction work;
1327 s_aws_connection_management_transaction_init(&work, manager);
1328
1329 aws_mutex_lock(&manager->lock);
1330
1331 /* Only if we're not shutting down */
1332 if (manager->state == AWS_HCMST_READY) {
1333 const struct aws_linked_list_node *end = aws_linked_list_end(&manager->idle_connections);
1334 struct aws_linked_list_node *current_node = aws_linked_list_begin(&manager->idle_connections);
1335 while (current_node != end) {
1336 struct aws_linked_list_node *node = current_node;
1337 struct aws_idle_connection *current_idle_connection =
1338 AWS_CONTAINER_OF(node, struct aws_idle_connection, node);
1339 if (current_idle_connection->cull_timestamp > now) {
1340 break;
1341 }
1342
1343 current_node = aws_linked_list_next(current_node);
1344 aws_linked_list_remove(node);
1345 aws_linked_list_push_back(&work.connections_to_release, node);
1346 --manager->idle_connection_count;
1347
1348 AWS_LOGF_DEBUG(
1349 AWS_LS_HTTP_CONNECTION_MANAGER,
1350 "id=%p: culling idle connection (%p)",
1351 (void *)manager,
1352 (void *)current_idle_connection->connection);
1353 }
1354 }
1355
1356 s_aws_http_connection_manager_get_snapshot(manager, &work.snapshot);
1357
1358 aws_mutex_unlock(&manager->lock);
1359
1360 s_aws_http_connection_manager_execute_transaction(&work);
1361 }
1362
s_cull_task(struct aws_task * task,void * arg,enum aws_task_status status)1363 static void s_cull_task(struct aws_task *task, void *arg, enum aws_task_status status) {
1364 (void)task;
1365 if (status != AWS_TASK_STATUS_RUN_READY) {
1366 return;
1367 }
1368
1369 struct aws_http_connection_manager *manager = arg;
1370
1371 s_cull_idle_connections(manager);
1372
1373 s_schedule_connection_culling(manager);
1374 }
1375