Skip to content

Commit dcbc111

Browse files
authored
Event loop pin (#391)
* Http connection event loop pinning * Websocket event loop pinning
1 parent 82fb7ed commit dcbc111

File tree

8 files changed

+90
-11
lines changed

8 files changed

+90
-11
lines changed

include/aws/http/connection.h

+7
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,13 @@ struct aws_http_client_connection_options {
391391
* If connection is HTTP/2 and options were not specified, default values are used.
392392
*/
393393
const struct aws_http2_connection_options *http2_options;
394+
395+
/**
396+
* Optional.
397+
* Requests the channel/connection be bound to a specific event loop rather than chosen sequentially from the
398+
* event loop group associated with the client bootstrap.
399+
*/
400+
struct aws_event_loop *requested_event_loop;
394401
};
395402

396403
/* Predefined settings identifiers (RFC-7540 6.5.2) */

include/aws/http/private/proxy_impl.h

+2
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ struct aws_http_proxy_user_data {
120120
aws_client_bootstrap_on_channel_event_fn *original_channel_on_shutdown;
121121

122122
struct aws_http_proxy_config *proxy_config;
123+
124+
struct aws_event_loop *requested_event_loop;
123125
};
124126

125127
struct aws_http_proxy_system_vtable {

include/aws/http/websocket.h

+8
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,14 @@ struct aws_websocket_client_connection_options {
248248
* reaches 0, no further data will be received.
249249
*/
250250
bool manual_window_management;
251+
252+
/**
253+
* Optional
254+
* If set, requests that a specific event loop be used to seat the connection, rather than the next one
255+
* in the event loop group. Useful for serializing all io and external events related to a client onto
256+
* a single thread.
257+
*/
258+
struct aws_event_loop *requested_event_loop;
251259
};
252260

253261
/**

source/connection.c

+1
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,7 @@ int aws_http_client_connect_internal(
10921092
.shutdown_callback = s_client_bootstrap_on_channel_shutdown,
10931093
.enable_read_back_pressure = options.manual_window_management,
10941094
.user_data = http_bootstrap,
1095+
.requested_event_loop = options.requested_event_loop,
10951096
};
10961097

10971098
err = s_system_vtable_ptr->new_socket_channel(&channel_options);

source/proxy_connection.c

+4
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ struct aws_http_proxy_user_data *aws_http_proxy_user_data_new(
136136
user_data->original_http_on_shutdown = options->on_shutdown;
137137
user_data->original_channel_on_setup = on_channel_setup;
138138
user_data->original_channel_on_shutdown = on_channel_shutdown;
139+
user_data->requested_event_loop = options->requested_event_loop;
139140

140141
/* one and only one setup callback must be valid */
141142
AWS_FATAL_ASSERT((user_data->original_http_on_setup == NULL) != (user_data->original_channel_on_setup == NULL));
@@ -984,6 +985,7 @@ static int s_aws_http_client_connect_via_forwarding_proxy(const struct aws_http_
984985
options_copy.on_setup = s_aws_http_on_client_connection_http_forwarding_proxy_setup_fn;
985986
options_copy.on_shutdown = s_aws_http_on_client_connection_http_proxy_shutdown_fn;
986987
options_copy.tls_options = options->proxy_options->tls_options;
988+
options_copy.requested_event_loop = options->requested_event_loop;
987989

988990
int result = aws_http_client_connect_internal(&options_copy, s_proxy_http_request_transform);
989991
if (result == AWS_OP_ERR) {
@@ -1018,6 +1020,7 @@ static int s_create_tunneling_connection(struct aws_http_proxy_user_data *user_d
10181020
connect_options.on_shutdown = s_aws_http_on_client_connection_http_proxy_shutdown_fn;
10191021
connect_options.http1_options = NULL; /* ToDo */
10201022
connect_options.http2_options = NULL; /* ToDo */
1023+
connect_options.requested_event_loop = user_data->requested_event_loop;
10211024

10221025
int result = aws_http_client_connect(&connect_options);
10231026
if (result == AWS_OP_ERR) {
@@ -1565,6 +1568,7 @@ int aws_http_proxy_new_socket_channel(
15651568
http_connection_options.user_data = user_data;
15661569
http_connection_options.on_setup = NULL; /* use channel callbacks, not http callbacks */
15671570
http_connection_options.on_shutdown = NULL; /* use channel callbacks, not http callbacks */
1571+
http_connection_options.requested_event_loop = channel_options->requested_event_loop;
15681572

15691573
if (s_aws_http_client_connect_via_tunneling_proxy(
15701574
&http_connection_options, s_http_proxied_socket_channel_setup, s_http_proxied_socket_channel_shutdown)) {

source/websocket_bootstrap.c

+1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ int aws_websocket_client_connect(const struct aws_websocket_client_connection_op
196196
http_options.user_data = ws_bootstrap;
197197
http_options.on_setup = s_ws_bootstrap_on_http_setup;
198198
http_options.on_shutdown = s_ws_bootstrap_on_http_shutdown;
199+
http_options.requested_event_loop = options->requested_event_loop;
199200

200201
/* Infer port, if not explicitly specified in URI */
201202
http_options.port = options->port;

tests/CMakeLists.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,11 @@ add_test_case(server_new_destroy)
470470
add_test_case(connection_setup_shutdown)
471471
add_test_case(connection_setup_shutdown_tls)
472472
add_test_case(connection_setup_shutdown_proxy_setting_on_ev_not_found)
473+
add_test_case(connection_setup_shutdown_pinned_event_loop)
473474
add_test_case(connection_h2_prior_knowledge)
474475
add_test_case(connection_h2_prior_knowledge_not_work_with_tls)
475476
add_test_case(connection_customized_alpn)
476-
add_test_case(connection_customized_alpn_error_with_unknow_return_string)
477+
add_test_case(connection_customized_alpn_error_with_unknown_return_string)
477478
# These server tests occasionally fail. Resurrect if/when we get back to work on HTTP server.
478479
#add_test_case(connection_destroy_server_with_connection_existing)
479480
#add_test_case(connection_destroy_server_with_multiple_connections_existing)

tests/test_connection.c

+65-10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <aws/common/clock.h>
1212
#include <aws/common/condition_variable.h>
1313
#include <aws/common/log_writer.h>
14+
#include <aws/common/logging.h>
1415
#include <aws/common/string.h>
1516
#include <aws/common/thread.h>
1617
#include <aws/common/uuid.h>
@@ -42,12 +43,14 @@ struct tester_options {
4243
char *server_alpn_list;
4344
char *client_alpn_list;
4445
bool no_connection; /* don't connect server to client */
46+
bool pin_event_loop;
4547
};
4648

4749
/* Singleton used by tests in this file */
4850
struct tester {
4951
struct aws_allocator *alloc;
50-
struct aws_event_loop_group *event_loop_group;
52+
struct aws_event_loop_group *server_event_loop_group;
53+
struct aws_event_loop_group *client_event_loop_group;
5154
struct aws_host_resolver *host_resolver;
5255
struct aws_server_bootstrap *server_bootstrap;
5356
struct aws_http_server *server;
@@ -239,7 +242,7 @@ static void s_client_connection_options_init_tester(
239242
struct aws_http_client_connection_options *client_options,
240243
struct tester *tester) {
241244
struct aws_client_bootstrap_options bootstrap_options = {
242-
.event_loop_group = tester->event_loop_group,
245+
.event_loop_group = tester->client_event_loop_group,
243246
.host_resolver = tester->host_resolver,
244247
};
245248
tester->client_bootstrap = aws_client_bootstrap_new(tester->alloc, &bootstrap_options);
@@ -298,15 +301,37 @@ static int s_tester_init(struct tester *tester, const struct tester_options *opt
298301
ASSERT_SUCCESS(aws_mutex_init(&tester->wait_lock));
299302
ASSERT_SUCCESS(aws_condition_variable_init(&tester->wait_cvar));
300303

301-
tester->event_loop_group = aws_event_loop_group_new_default(tester->alloc, 1, NULL);
304+
/*
305+
* The current http testing framework has several issues that hinder testing event loop pinning:
306+
* (1) Server shutdown can crash with memory corruption if the server uses an event loop group with more than one
307+
* thread
308+
* (2) s_tester_wait mixes results from both client and server and once you unlink them out of the same, single-
309+
* threaded event loop, the test assumptions start breaking due to different serializations of io events.
310+
*
311+
* This leads to a self-defeating situation: in order to test event loop pinning we need event loop groups with
312+
* many threads, but as soon as we use one, existing tests start breaking.
313+
*
314+
* Event loop pinning is a critical blocker for an upcoming release, so rather than trying to figure out the
315+
* underlying race condition within the http testing framework (I suspect it's socket listener related), we
316+
* instead add some complexity to the testing framework such that
317+
* (1) Existing tests continue to use a single event loop group with one thread
318+
* (2) The event loop pinning test uses two event loop groups, the server elg with a single thread and the
319+
* client elg with many threads to actually test pinning.
320+
*/
321+
tester->server_event_loop_group = aws_event_loop_group_new_default(tester->alloc, 1, NULL);
322+
if (options->pin_event_loop) {
323+
tester->client_event_loop_group = aws_event_loop_group_new_default(tester->alloc, 16, NULL);
324+
} else {
325+
tester->client_event_loop_group = aws_event_loop_group_acquire(tester->server_event_loop_group);
326+
}
302327

303328
struct aws_host_resolver_default_options resolver_options = {
304-
.el_group = tester->event_loop_group,
329+
.el_group = tester->client_event_loop_group,
305330
.max_entries = 8,
306331
};
307332

308333
tester->host_resolver = aws_host_resolver_new_default(tester->alloc, &resolver_options);
309-
tester->server_bootstrap = aws_server_bootstrap_new(tester->alloc, tester->event_loop_group);
334+
tester->server_bootstrap = aws_server_bootstrap_new(tester->alloc, tester->server_event_loop_group);
310335
ASSERT_NOT_NULL(tester->server_bootstrap);
311336

312337
struct aws_socket_options socket_options = {
@@ -360,6 +385,11 @@ static int s_tester_init(struct tester *tester, const struct tester_options *opt
360385
aws_byte_cursor_from_c_str("localhost")));
361386
client_options.tls_options = &tester->client_tls_connection_options;
362387
}
388+
389+
if (options->pin_event_loop) {
390+
client_options.requested_event_loop = aws_event_loop_group_get_next_loop(tester->client_event_loop_group);
391+
}
392+
363393
tester->client_options = client_options;
364394

365395
tester->server_connection_num = 0;
@@ -395,7 +425,8 @@ static int s_tester_clean_up(struct tester *tester) {
395425
aws_server_bootstrap_release(tester->server_bootstrap);
396426
aws_client_bootstrap_release(tester->client_bootstrap);
397427
aws_host_resolver_release(tester->host_resolver);
398-
aws_event_loop_group_release(tester->event_loop_group);
428+
aws_event_loop_group_release(tester->client_event_loop_group);
429+
aws_event_loop_group_release(tester->server_event_loop_group);
399430

400431
aws_http_library_clean_up();
401432
aws_mutex_clean_up(&tester->wait_lock);
@@ -645,7 +676,7 @@ static int s_test_connection_customized_alpn(struct aws_allocator *allocator, vo
645676
}
646677
AWS_TEST_CASE(connection_customized_alpn, s_test_connection_customized_alpn);
647678

648-
static int s_test_connection_customized_alpn_error_with_unknow_return_string(
679+
static int s_test_connection_customized_alpn_error_with_unknown_return_string(
649680
struct aws_allocator *allocator,
650681
void *ctx) {
651682
(void)ctx;
@@ -702,8 +733,8 @@ static int s_test_connection_customized_alpn_error_with_unknow_return_string(
702733
return AWS_OP_SUCCESS;
703734
}
704735
AWS_TEST_CASE(
705-
connection_customized_alpn_error_with_unknow_return_string,
706-
s_test_connection_customized_alpn_error_with_unknow_return_string);
736+
connection_customized_alpn_error_with_unknown_return_string,
737+
s_test_connection_customized_alpn_error_with_unknown_return_string);
707738

708739
static int s_test_connection_destroy_server_with_connection_existing(struct aws_allocator *allocator, void *ctx) {
709740
(void)ctx;
@@ -856,7 +887,7 @@ static int s_test_connection_server_shutting_down_new_connection_setup_fail(
856887

857888
/* get the first eventloop of tester, which will be the eventloop for server listener socket, block the listener
858889
* socket */
859-
struct aws_event_loop *server_eventloop = aws_event_loop_group_get_loop_at(tester.event_loop_group, 0);
890+
struct aws_event_loop *server_eventloop = aws_event_loop_group_get_loop_at(tester.server_event_loop_group, 0);
860891
struct aws_task *server_block_task = aws_mem_acquire(allocator, sizeof(struct aws_task));
861892
aws_task_init(server_block_task, s_block_task, &tester, "wait_a_bit");
862893
aws_event_loop_schedule_task_now(server_eventloop, server_block_task);
@@ -916,3 +947,27 @@ static int s_test_connection_server_shutting_down_new_connection_setup_fail(
916947
AWS_TEST_CASE(
917948
connection_server_shutting_down_new_connection_setup_fail,
918949
s_test_connection_server_shutting_down_new_connection_setup_fail);
950+
951+
static int s_test_connection_setup_shutdown_pinned_event_loop(struct aws_allocator *allocator, void *ctx) {
952+
(void)ctx;
953+
struct tester_options options = {
954+
.alloc = allocator,
955+
.pin_event_loop = true,
956+
};
957+
struct tester tester;
958+
ASSERT_SUCCESS(s_tester_init(&tester, &options));
959+
960+
for (int i = 0; i < tester.client_connection_num; i++) {
961+
struct aws_http_connection *connection = tester.client_connections[i];
962+
ASSERT_PTR_EQUALS(
963+
tester.client_options.requested_event_loop, aws_channel_get_event_loop(connection->channel_slot->channel));
964+
}
965+
966+
release_all_client_connections(&tester);
967+
release_all_server_connections(&tester);
968+
ASSERT_SUCCESS(s_tester_wait(&tester, s_tester_connection_shutdown_pred));
969+
970+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
971+
return AWS_OP_SUCCESS;
972+
}
973+
AWS_TEST_CASE(connection_setup_shutdown_pinned_event_loop, s_test_connection_setup_shutdown_pinned_event_loop);

0 commit comments

Comments
 (0)