Skip to content

Commit ad7d98c

Browse files
authored
HTTP/2 stream manager (#356)
1 parent 6c98b24 commit ad7d98c

File tree

13 files changed

+2435
-13
lines changed

13 files changed

+2435
-13
lines changed

include/aws/http/connection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ void aws_http2_connection_get_local_settings(
567567
* Get the settings received from remote peer, which we are using to restricts the message to send.
568568
*
569569
* @param http2_connection HTTP/2 connection.
570-
* @param out_settings fixed size array of aws_http2_setting gets set to the remote settings
570+
* @param out_settings fixed size array of aws_http2_setting gets set to the remote settings
571571
*/
572572
AWS_HTTP_API
573573
void aws_http2_connection_get_remote_settings(

include/aws/http/connection_manager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct aws_http_connection_manager;
1616
struct aws_socket_options;
1717
struct aws_tls_connection_options;
1818
struct proxy_env_var_settings;
19+
struct aws_http2_setting;
1920

2021
typedef void(aws_http_connection_manager_on_connection_setup_fn)(
2122
struct aws_http_connection *connection,

include/aws/http/http.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ enum aws_http_errors {
5151
AWS_ERROR_HTTP_PROXY_CONNECT_FAILED_RETRYABLE,
5252
AWS_ERROR_HTTP_PROTOCOL_SWITCH_FAILURE,
5353
AWS_ERROR_HTTP_MAX_CONCURRENT_STREAMS_EXCEEDED,
54+
AWS_ERROR_HTTP_STREAM_MANAGER_SHUTTING_DOWN,
55+
AWS_ERROR_HTTP_STREAM_MANAGER_CONNECTION_ACQUIRE_FAILURE,
56+
AWS_ERROR_HTTP_STREAM_MANAGER_UNEXPECTED_HTTP_VERSION,
5457

5558
AWS_ERROR_HTTP_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_HTTP_PACKAGE_ID)
5659
};
@@ -82,6 +85,7 @@ enum aws_http_log_subject {
8285
AWS_LS_HTTP_SERVER,
8386
AWS_LS_HTTP_STREAM,
8487
AWS_LS_HTTP_CONNECTION_MANAGER,
88+
AWS_LS_HTTP_STREAM_MANAGER,
8589
AWS_LS_HTTP_WEBSOCKET,
8690
AWS_LS_HTTP_WEBSOCKET_SETUP,
8791
AWS_LS_HTTP_PROXY_NEGOTIATION,
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#ifndef AWS_HTTP2_STREAM_MANAGER_H
2+
#define AWS_HTTP2_STREAM_MANAGER_H
3+
4+
/**
5+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
* SPDX-License-Identifier: Apache-2.0.
7+
*/
8+
9+
#include <aws/http/http.h>
10+
11+
struct aws_http2_stream_manager;
12+
struct aws_client_bootstrap;
13+
struct aws_http_connection;
14+
struct aws_http_connection_manager;
15+
struct aws_socket_options;
16+
struct aws_tls_connection_options;
17+
struct proxy_env_var_settings;
18+
struct aws_http2_setting;
19+
struct aws_http_make_request_options;
20+
struct aws_http_stream;
21+
22+
/**
23+
* Always invoked asynchronously when the stream was created, successfully or not.
24+
* When stream is NULL, error code will be set to indicate what happened.
25+
* If there is a stream returned, you own the stream completely.
26+
* Invoked on the same thread as other callback of the stream, which will be the thread of the connection, ideally.
27+
* If there is no connection made, the callback will be invoked from a sperate thread.
28+
*/
29+
typedef void(
30+
aws_http2_stream_manager_on_stream_acquired_fn)(struct aws_http_stream *stream, int error_code, void *user_data);
31+
32+
/**
33+
* Invoked asynchronously when the stream manager has been shutdown completely.
34+
* Never invoked when `aws_http2_stream_manager_new` failed.
35+
*/
36+
typedef void(aws_http2_stream_manager_shutdown_complete_fn)(void *user_data);
37+
38+
/**
39+
* HTTP/2 stream manager configuration struct.
40+
*
41+
* Contains all of the configuration needed to create an http2 connection as well as
42+
* connection manager under the hood.
43+
*/
44+
struct aws_http2_stream_manager_options {
45+
/**
46+
* basic http connection configuration
47+
*/
48+
struct aws_client_bootstrap *bootstrap;
49+
const struct aws_socket_options *socket_options;
50+
/**
51+
* If TLS options is set, you also need to handle ALPN, otherwise, may not able to get HTTP/2 connection and fail
52+
* the stream manager.
53+
* If TLS options not set, prior knowledge will be used.
54+
*/
55+
const struct aws_tls_connection_options *tls_connection_options;
56+
struct aws_byte_cursor host;
57+
uint16_t port;
58+
59+
/**
60+
* HTTP/2 Stream window control.
61+
* If set to true, the read back pressure mechanism will be enabled for streams created.
62+
* The initial window size can be set through `initial window size`
63+
*/
64+
bool enable_read_back_pressure;
65+
/**
66+
* Optional.
67+
* If set, it will be sent to the peer as the `AWS_HTTP2_SETTINGS_INITIAL_WINDOW_SIZE` in the initial settings for
68+
* HTTP/2 connection.
69+
* If not set, the default will be used, which is 65,535 (2^16-1)(RFC-7540 6.5.2)
70+
* Ignored if enable_read_back_pressure is false.
71+
*/
72+
uint32_t initial_window_size;
73+
74+
/* Connection monitor for the underlying connections made */
75+
const struct aws_http_connection_monitoring_options *monitoring_options;
76+
77+
/* Optional. Proxy configuration for underlying http connection */
78+
const struct aws_http_proxy_options *proxy_options;
79+
const struct proxy_env_var_settings *proxy_ev_settings;
80+
81+
/**
82+
* Required.
83+
* When the stream manager finishes deleting all the resources, the callback will be invoked.
84+
*/
85+
void *shutdown_complete_user_data;
86+
aws_http2_stream_manager_shutdown_complete_fn *shutdown_complete_callback;
87+
88+
/* TODO: More flexible policy about the connections, but will always has these three values below. */
89+
/**
90+
* Optional.
91+
* 0 will be considered as using a default value.
92+
* The ideal number of concurrent streams for a connection. Stream manager will try to create a new connection if
93+
* one connection reaches this number. But, if the max connections reaches, manager will reuse connections to create
94+
* the acquired steams as much as possible. */
95+
size_t ideal_concurrent_streams_per_connection;
96+
/**
97+
* Optional.
98+
* Default is no limit, which will use the limit from the server. 0 will be considered as using the default value.
99+
* The real number of concurrent streams per connection will be controlled by the minmal value of the setting from
100+
* other end and the value here.
101+
*/
102+
size_t max_concurrent_streams_per_connection;
103+
/**
104+
* Required.
105+
* The max number of connections will be open at same time. If all the connections are full, manager will wait until
106+
* available to vender more streams */
107+
size_t max_connections;
108+
};
109+
110+
struct aws_http2_stream_manager_acquire_stream_options {
111+
/**
112+
* Required.
113+
* Invoked when the stream finishes acquiring by stream manager.
114+
*/
115+
aws_http2_stream_manager_on_stream_acquired_fn *callback;
116+
/**
117+
* Optional.
118+
* User data for the callback.
119+
*/
120+
void *user_data;
121+
/* Required. see `aws_http_make_request_options` */
122+
const struct aws_http_make_request_options *options;
123+
};
124+
125+
AWS_EXTERN_C_BEGIN
126+
127+
/**
128+
* Acquire a refcount from the stream manager, stream manager will start to destroy after the refcount drops to zero.
129+
* NULL is acceptable. Initial refcount after new is 1.
130+
*
131+
* @param manager
132+
* @return The same pointer acquiring.
133+
*/
134+
AWS_HTTP_API
135+
struct aws_http2_stream_manager *aws_http2_stream_manager_acquire(struct aws_http2_stream_manager *manager);
136+
137+
/**
138+
* Release a refcount from the stream manager, stream manager will start to destroy after the refcount drops to zero.
139+
* NULL is acceptable. Initial refcount after new is 1.
140+
*
141+
* @param manager
142+
* @return NULL
143+
*/
144+
AWS_HTTP_API
145+
struct aws_http2_stream_manager *aws_http2_stream_manager_release(struct aws_http2_stream_manager *manager);
146+
147+
AWS_HTTP_API
148+
struct aws_http2_stream_manager *aws_http2_stream_manager_new(
149+
struct aws_allocator *allocator,
150+
struct aws_http2_stream_manager_options *options);
151+
152+
/**
153+
* Acquire a stream from stream manager asynchronously.
154+
*
155+
* @param http2_stream_manager
156+
* @param acquire_stream_option see `aws_http2_stream_manager_acquire_stream_options`
157+
*/
158+
AWS_HTTP_API
159+
void aws_http2_stream_manager_acquire_stream(
160+
struct aws_http2_stream_manager *http2_stream_manager,
161+
const struct aws_http2_stream_manager_acquire_stream_options *acquire_stream_option);
162+
163+
AWS_EXTERN_C_END
164+
#endif /* AWS_HTTP2_STREAM_MANAGER_H */
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#ifndef AWS_HTTP2_STREAM_MANAGER_IMPL_H
2+
#define AWS_HTTP2_STREAM_MANAGER_IMPL_H
3+
4+
/**
5+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
* SPDX-License-Identifier: Apache-2.0.
7+
*/
8+
9+
#include <aws/common/mutex.h>
10+
#include <aws/common/ref_count.h>
11+
#include <aws/http/http2_stream_manager.h>
12+
#include <aws/http/private/random_access_set.h>
13+
14+
enum aws_h2_sm_state_type {
15+
AWS_H2SMST_READY,
16+
AWS_H2SMST_DESTROYING, /* On zero external ref count, can destroy */
17+
};
18+
19+
enum aws_h2_sm_connection_state_type {
20+
AWS_H2SMCST_IDEAL,
21+
AWS_H2SMCST_NEARLY_FULL,
22+
AWS_H2SMCST_FULL,
23+
};
24+
25+
/* Live with the streams opening, and if there no outstanding pending acquisition and no opening streams on the
26+
* connection, this structure should die */
27+
struct aws_h2_sm_connection {
28+
struct aws_http2_stream_manager *stream_manager;
29+
struct aws_http_connection *connection;
30+
uint32_t num_streams_assigned; /* From a stream assigned to the connection until the stream completed
31+
or failed to be created from the connection. */
32+
uint32_t max_concurrent_streams; /* lower bound between user configured and the other side */
33+
34+
enum aws_h2_sm_connection_state_type state;
35+
};
36+
37+
/* Live from the user request to acquire a stream to the stream completed. */
38+
struct aws_h2_sm_pending_stream_acquisition {
39+
struct aws_allocator *allocator;
40+
struct aws_linked_list_node node;
41+
struct aws_http_make_request_options options;
42+
struct aws_h2_sm_connection *sm_connection; /* The connection to make request to. Keep
43+
NULL, until find available one and move it to the pending_make_requests
44+
list. */
45+
struct aws_http_message *request;
46+
struct aws_channel_task make_request_task;
47+
aws_http2_stream_manager_on_stream_acquired_fn *callback;
48+
void *user_data;
49+
};
50+
51+
/* connections_acquiring_count, open_stream_count, pending_make_requests_count AND pending_stream_acquisition_count */
52+
enum aws_sm_count_type {
53+
AWS_SMCT_CONNECTIONS_ACQUIRING,
54+
AWS_SMCT_OPEN_STREAM,
55+
AWS_SMCT_PENDING_MAKE_REQUESTS,
56+
AWS_SMCT_PENDING_ACQUISITION,
57+
AWS_SMCT_COUNT,
58+
};
59+
60+
struct aws_http2_stream_manager {
61+
struct aws_allocator *allocator;
62+
void *shutdown_complete_user_data;
63+
aws_http2_stream_manager_shutdown_complete_fn *shutdown_complete_callback;
64+
/**
65+
* Underlying connection manager. Always has the same life time with the stream manager who owns it.
66+
*/
67+
struct aws_http_connection_manager *connection_manager;
68+
/**
69+
* Refcount managed by user. Once this drops to zero, the manager state transitions to shutting down
70+
*/
71+
struct aws_ref_count external_ref_count;
72+
/**
73+
* Internal refcount that keeps connection manager alive.
74+
*
75+
* It's a sum of connections_acquiring_count, open_stream_count, pending_make_requests_count and
76+
* pending_stream_acquisition_count, besides the number of `struct aws_http2_stream_management_transaction` alive.
77+
* And one for external usage.
78+
*
79+
* Once this refcount drops to zero, stream manager should either be cleaned up all the memory all waiting for
80+
* the last task to clean un the memory and do nothing else.
81+
*/
82+
struct aws_ref_count internal_ref_count;
83+
struct aws_client_bootstrap *bootstrap;
84+
85+
size_t max_connections;
86+
87+
/**
88+
* Default is no limit. 0 will be considered as using the default value.
89+
* The ideal number of concurrent streams for a connection. Stream manager will try to create a new connection if
90+
* one connection reaches this number. But, if the max connections reaches, manager will reuse connections to create
91+
* the acquired steams as much as possible. */
92+
size_t ideal_concurrent_streams_per_connection;
93+
/**
94+
* Default is no limit. 0 will be considered as using the default value.
95+
* The real number of concurrent streams per connection will be controlled by the minmal value of the setting from
96+
* other end and the value here.
97+
*/
98+
size_t max_concurrent_streams_per_connection;
99+
100+
/**
101+
* Task to invoke pending acquisition callbacks asynchronously if stream manager is shutting.
102+
*/
103+
struct aws_event_loop *finish_pending_stream_acquisitions_task_event_loop;
104+
105+
/* Any thread may touch this data, but the lock must be held (unless it's an atomic) */
106+
struct {
107+
struct aws_mutex lock;
108+
/*
109+
* A manager can be in one of two states, READY or SHUTTING_DOWN. The state transition
110+
* takes place when ref_count drops to zero.
111+
*/
112+
enum aws_h2_sm_state_type state;
113+
114+
/**
115+
* A set of all connections that meet all requirement to use. Note: there will be connections not in this set,
116+
* but hold by the stream manager, which can be tracked by the streams created on it. Set of `struct
117+
* aws_h2_sm_connection *`
118+
*/
119+
struct aws_random_access_set ideal_available_set;
120+
/**
121+
* A set of all available connections that exceed the soft limits set by users. Note: there will be connections
122+
* not in this set, but hold by the stream manager, which can be tracked by the streams created. Set of `struct
123+
* aws_h2_sm_connection *`
124+
*/
125+
struct aws_random_access_set nonideal_available_set;
126+
/* We don't mantain set for connections that is full or "dead" (Cannot make any new streams). We have streams
127+
* opening from the connection tracking them */
128+
129+
/**
130+
* The set of all incomplete stream acquisition requests (haven't decide what connection to make the request
131+
* to), list of `struct aws_h2_sm_pending_stream_acquisition*`
132+
*/
133+
struct aws_linked_list pending_stream_acquisitions;
134+
135+
/**
136+
* The number of connections acquired from connection manager and not released yet.
137+
*/
138+
size_t holding_connections_count;
139+
140+
/**
141+
* Counts that contributes to the internal refcount.
142+
* When the value changes, s_sm_count_increase/decrease_synced needed.
143+
*
144+
* AWS_SMCT_CONNECTIONS_ACQUIRING: The number of new connections we acquiring from the connection manager.
145+
* AWS_SMCT_OPEN_STREAM: The number of streams that opened and not completed yet.
146+
* AWS_SMCT_PENDING_MAKE_REQUESTS: The number of streams that scheduled to be made from a connection but haven't
147+
* been executed yet.
148+
* AWS_SMCT_PENDING_ACQUISITION: The number of all incomplete stream acquisition requests (haven't decide what
149+
* connection to make the request to). So that we don't have compute the size of a linked list every time.
150+
*/
151+
size_t internal_refcount_stats[AWS_SMCT_COUNT];
152+
153+
bool finish_pending_stream_acquisitions_task_scheduled;
154+
} synced_data;
155+
};
156+
157+
/**
158+
* Encompasses all of the external operations that need to be done for various
159+
* events:
160+
* - User level:
161+
* stream manager release
162+
* stream acquire
163+
* - Internal eventloop (anther thread):
164+
* connection_acquired
165+
* stream_completed
166+
* - Internal (can happen from any thread):
167+
* connection acquire
168+
* connection release
169+
*
170+
* The transaction is built under the manager's lock (and the internal state is updated optimistically),
171+
* but then executed outside of it.
172+
*/
173+
struct aws_http2_stream_management_transaction {
174+
struct aws_http2_stream_manager *stream_manager;
175+
struct aws_allocator *allocator;
176+
size_t new_connections;
177+
struct aws_h2_sm_connection *sm_connection_to_release;
178+
struct aws_linked_list
179+
pending_make_requests; /* List of aws_h2_sm_pending_stream_acquisition with chosen connection */
180+
};
181+
182+
#endif /* AWS_HTTP2_STREAM_MANAGER_IMPL_H */

source/connection.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,9 @@ void aws_http_connection_acquire(struct aws_http_connection *connection) {
390390
}
391391

392392
void aws_http_connection_release(struct aws_http_connection *connection) {
393-
AWS_ASSERT(connection);
393+
if (!connection) {
394+
return;
395+
}
394396
size_t prev_refcount = aws_atomic_fetch_sub(&connection->refcount, 1);
395397
if (prev_refcount == 1) {
396398
AWS_LOGF_TRACE(

0 commit comments

Comments
 (0)