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