Skip to content

Commit 753650c

Browse files
authored
aws_http_connection_is_open() (#29)
- Added `aws_http_connection_is_open()` - Modified `aws_http_connection_close()` so it made the connection **immediately not open**, even if called from outside the event-loop thread.
1 parent 3c0cbee commit 753650c

File tree

6 files changed

+97
-24
lines changed

6 files changed

+97
-24
lines changed

include/aws/http/connection.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ void aws_http_connection_release(struct aws_http_connection *connection);
143143
AWS_HTTP_API
144144
void aws_http_connection_close(struct aws_http_connection *connection);
145145

146+
/**
147+
* Returns true unless the connection is closed or closing.
148+
*/
149+
AWS_HTTP_API
150+
bool aws_http_connection_is_open(const struct aws_http_connection *connection);
151+
146152
AWS_HTTP_API
147153
enum aws_http_version aws_http_connection_get_version(const struct aws_http_connection *connection);
148154

include/aws/http/private/connection_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct aws_http_connection_vtable {
4646

4747
struct aws_http_stream *(*new_client_request_stream)(const struct aws_http_request_options *options);
4848
void (*close)(struct aws_http_connection *connection);
49+
bool (*is_open)(const struct aws_http_connection *connection);
4950
};
5051

5152
/**

source/connection.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ void aws_http_connection_close(struct aws_http_connection *connection) {
174174
connection->vtable->close(connection);
175175
}
176176

177+
bool aws_http_connection_is_open(const struct aws_http_connection *connection) {
178+
assert(connection);
179+
return connection->vtable->is_open(connection);
180+
}
181+
177182
void aws_http_connection_release(struct aws_http_connection *connection) {
178183
assert(connection);
179184
size_t prev_refcount = aws_atomic_fetch_sub(&connection->refcount, 1);

source/connection_h1.c

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ static size_t s_handler_message_overhead(struct aws_channel_handler *handler);
6060
static void s_handler_destroy(struct aws_channel_handler *handler);
6161
static struct aws_http_stream *s_new_client_request_stream(const struct aws_http_request_options *options);
6262
static void s_connection_close(struct aws_http_connection *connection_base);
63+
static bool s_connection_is_open(const struct aws_http_connection *connection_base);
6364
static void s_stream_destroy(struct aws_http_stream *stream_base);
6465
static void s_stream_update_window(struct aws_http_stream *stream, size_t increment_size);
6566
static int s_decoder_on_request(
@@ -86,6 +87,7 @@ static struct aws_http_connection_vtable s_connection_vtable = {
8687

8788
.new_client_request_stream = s_new_client_request_stream,
8889
.close = s_connection_close,
90+
.is_open = s_connection_is_open,
8991
};
9092

9193
static const struct aws_http_stream_vtable s_stream_vtable = {
@@ -189,32 +191,41 @@ struct h1_stream {
189191

190192
/**
191193
* Internal function for shutting down the connection.
192-
* If connection is already shutting down, this call has no effect.
194+
* This function can be called multiple times, from on-thread or off.
195+
* This function is always run once on-thread during channel shutdown.
193196
*/
194197
static void s_shutdown_connection(struct h1_connection *connection, int error_code) {
195-
assert(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
198+
bool on_thread = aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel);
199+
if (on_thread) {
200+
/* If thread_data already knew about shutdown, then no more work to do here. */
201+
if (connection->thread_data.is_shutting_down) {
202+
return;
203+
}
196204

197-
if (!connection->thread_data.is_shutting_down) {
205+
connection->thread_data.is_shutting_down = true;
206+
connection->thread_data.shutdown_error_code = error_code;
207+
}
208+
209+
bool was_shutdown_known;
210+
{ /* BEGIN CRITICAL SECTION */
211+
int err = aws_mutex_lock(&connection->synced_data.lock);
212+
AWS_FATAL_ASSERT(!err);
213+
214+
was_shutdown_known = connection->synced_data.is_shutting_down;
215+
connection->synced_data.is_shutting_down = true;
216+
217+
err = aws_mutex_unlock(&connection->synced_data.lock);
218+
AWS_FATAL_ASSERT(!err);
219+
} /* END CRITICAL SECTION */
220+
221+
if (!was_shutdown_known) {
198222
AWS_LOGF_INFO(
199223
AWS_LS_HTTP_CONNECTION,
200224
"id=%p: Connection shutting down with error code %d (%s).",
201225
(void *)&connection->base,
202226
error_code,
203227
aws_error_name(error_code));
204228

205-
{ /* BEGIN CRITICAL SECTION */
206-
int err = aws_mutex_lock(&connection->synced_data.lock);
207-
AWS_FATAL_ASSERT(!err);
208-
209-
connection->synced_data.is_shutting_down = true;
210-
211-
err = aws_mutex_unlock(&connection->synced_data.lock);
212-
AWS_FATAL_ASSERT(!err);
213-
} /* END CRITICAL SECTION */
214-
215-
connection->thread_data.is_shutting_down = true;
216-
connection->thread_data.shutdown_error_code = error_code;
217-
218229
/* Delay the call to aws_channel_shutdown().
219230
* This ensures that a user calling aws_http_connection_close() won't have completion callbacks
220231
* firing before aws_http_connection_close() has even returned. */
@@ -234,18 +245,27 @@ static void s_shutdown_delay_task(struct aws_channel_task *task, void *arg, enum
234245

235246
/**
236247
* Public function for closing connection.
237-
* If connection is already shutting down, this call has no effect.
238248
*/
239249
static void s_connection_close(struct aws_http_connection *connection_base) {
240250
struct h1_connection *connection = AWS_CONTAINER_OF(connection_base, struct h1_connection, base);
251+
s_shutdown_connection(connection, AWS_ERROR_SUCCESS);
252+
}
241253

242-
if (aws_channel_thread_is_callers_thread(connection_base->channel_slot->channel)) {
243-
/* Invoke internal function so connection ceases work immediately */
244-
s_shutdown_connection(connection, AWS_ERROR_SUCCESS);
245-
} else {
246-
/* Not on thread, so tell channel to shut down, which will result in connection shutting down. */
247-
aws_channel_shutdown(connection_base->channel_slot->channel, AWS_ERROR_SUCCESS);
248-
}
254+
static bool s_connection_is_open(const struct aws_http_connection *connection_base) {
255+
struct h1_connection *connection = AWS_CONTAINER_OF(connection_base, struct h1_connection, base);
256+
bool is_shutting_down;
257+
258+
{ /* BEGIN CRITICAL SECTION */
259+
int err = aws_mutex_lock(&connection->synced_data.lock);
260+
AWS_FATAL_ASSERT(!err);
261+
262+
is_shutting_down = connection->synced_data.is_shutting_down;
263+
264+
err = aws_mutex_unlock(&connection->synced_data.lock);
265+
AWS_FATAL_ASSERT(!err);
266+
} /* END CRITICAL SECTION */
267+
268+
return !is_shutting_down;
249269
}
250270

251271
/**

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ add_test_case(h1_client_close_from_incoming_headers_callback_stops_decoder)
4949
add_test_case(h1_client_close_from_incoming_headers_done_callback_stops_decoder)
5050
add_test_case(h1_client_close_from_incoming_body_callback_stops_decoder)
5151
add_test_case(h1_client_close_from_stream_complete_callback_stops_decoder)
52+
add_test_case(h1_client_close_from_off_thread_makes_not_open)
53+
add_test_case(h1_client_close_from_on_thread_makes_not_open)
5254

5355
set(TEST_BINARY_NAME ${CMAKE_PROJECT_NAME}-tests)
5456
generate_test_driver(${TEST_BINARY_NAME})

tests/test_h1_client.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,45 @@ H1_CLIENT_TEST_CASE(h1_client_close_from_stream_complete_callback_stops_decoder)
12901290
return AWS_OP_SUCCESS;
12911291
}
12921292

1293+
/* After aws_http_connection_close() is called, aws_http_connection_is_open() should return false,
1294+
* even if both calls were made from outside the event-loop thread. */
1295+
H1_CLIENT_TEST_CASE(h1_client_close_from_off_thread_makes_not_open) {
1296+
(void)ctx;
1297+
struct tester tester;
1298+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
1299+
1300+
testing_channel_set_is_on_users_thread(&tester.testing_channel, false);
1301+
1302+
ASSERT_TRUE(aws_http_connection_is_open(tester.connection));
1303+
aws_http_connection_close(tester.connection);
1304+
ASSERT_FALSE(aws_http_connection_is_open(tester.connection));
1305+
1306+
testing_channel_set_is_on_users_thread(&tester.testing_channel, true);
1307+
1308+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
1309+
return AWS_OP_SUCCESS;
1310+
}
1311+
1312+
H1_CLIENT_TEST_CASE(h1_client_close_from_on_thread_makes_not_open) {
1313+
(void)ctx;
1314+
struct tester tester;
1315+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
1316+
1317+
testing_channel_set_is_on_users_thread(&tester.testing_channel, false);
1318+
ASSERT_TRUE(aws_http_connection_is_open(tester.connection));
1319+
1320+
testing_channel_set_is_on_users_thread(&tester.testing_channel, true);
1321+
aws_http_connection_close(tester.connection);
1322+
1323+
testing_channel_set_is_on_users_thread(&tester.testing_channel, false);
1324+
ASSERT_FALSE(aws_http_connection_is_open(tester.connection));
1325+
1326+
testing_channel_set_is_on_users_thread(&tester.testing_channel, true);
1327+
1328+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
1329+
return AWS_OP_SUCCESS;
1330+
}
1331+
12931332
/* Tests TODO
12941333
- Responses
12951334
- Responses finishing before request done sending

0 commit comments

Comments
 (0)