Skip to content

Commit a2fb16c

Browse files
authored
adapt change from "TLS deliver buffer data during shutdown" (#474)
1 parent 601d778 commit a2fb16c

File tree

6 files changed

+202
-55
lines changed

6 files changed

+202
-55
lines changed

include/aws/http/private/h1_connection.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,16 @@ struct aws_h1_connection {
128128
/* If non-zero, reason to immediately reject new streams. (ex: closing) */
129129
int new_stream_error_code;
130130

131+
/* If true, user has called connection_close() or stream_cancel(),
132+
* but the cross_thread_work_task hasn't processed it yet */
133+
bool shutdown_requested;
134+
int shutdown_requested_error_code;
135+
131136
/* See `cross_thread_work_task` */
132137
bool is_cross_thread_work_task_scheduled : 1;
133138

134139
/* For checking status from outside the event-loop thread. */
135140
bool is_open : 1;
136-
137141
} synced_data;
138142
};
139143

source/h1_connection.c

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ void aws_h1_connection_unlock_synced_data(struct aws_h1_connection *connection)
130130
* - Channel is shutting down in the read direction.
131131
* - Channel is shutting down in the write direction.
132132
* - An error occurs.
133-
* - User wishes to close the connection (this is the only case where the function may run off-thread).
134133
*/
135134
static void s_stop(
136135
struct aws_h1_connection *connection,
@@ -139,15 +138,14 @@ static void s_stop(
139138
bool schedule_shutdown,
140139
int error_code) {
141140

141+
AWS_ASSERT(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
142142
AWS_ASSERT(stop_reading || stop_writing || schedule_shutdown); /* You are required to stop at least 1 thing */
143143

144144
if (stop_reading) {
145-
AWS_ASSERT(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
146145
connection->thread_data.is_reading_stopped = true;
147146
}
148147

149148
if (stop_writing) {
150-
AWS_ASSERT(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
151149
connection->thread_data.is_writing_stopped = true;
152150
}
153151
{ /* BEGIN CRITICAL SECTION */
@@ -169,6 +167,11 @@ static void s_stop(
169167
aws_error_name(error_code));
170168

171169
aws_channel_shutdown(connection->base.channel_slot->channel, error_code);
170+
if (stop_reading) {
171+
/* Increase the window size after shutdown starts, to prevent deadlock when data still pending in the TLS
172+
* handler. */
173+
aws_channel_slot_increment_read_window(connection->base.channel_slot, SIZE_MAX);
174+
}
172175
}
173176
}
174177

@@ -189,14 +192,45 @@ static void s_shutdown_due_to_error(struct aws_h1_connection *connection, int er
189192
s_stop(connection, true /*stop_reading*/, true /*stop_writing*/, true /*schedule_shutdown*/, error_code);
190193
}
191194

195+
/**
196+
* Helper to shutdown the connection from non-channel thread. (User wishes to close the connection)
197+
**/
198+
static void s_shutdown_from_off_thread(struct aws_h1_connection *connection, int error_code) {
199+
bool should_schedule_task = false;
200+
{ /* BEGIN CRITICAL SECTION */
201+
aws_h1_connection_lock_synced_data(connection);
202+
if (!connection->synced_data.is_cross_thread_work_task_scheduled) {
203+
connection->synced_data.is_cross_thread_work_task_scheduled = true;
204+
should_schedule_task = true;
205+
}
206+
if (!connection->synced_data.shutdown_requested) {
207+
connection->synced_data.shutdown_requested = true;
208+
connection->synced_data.shutdown_requested_error_code = error_code;
209+
}
210+
/* Connection has shutdown, new streams should not be allowed. */
211+
connection->synced_data.is_open = false;
212+
connection->synced_data.new_stream_error_code = AWS_ERROR_HTTP_CONNECTION_CLOSED;
213+
aws_h1_connection_unlock_synced_data(connection);
214+
} /* END CRITICAL SECTION */
215+
216+
if (should_schedule_task) {
217+
AWS_LOGF_TRACE(
218+
AWS_LS_HTTP_CONNECTION, "id=%p: Scheduling connection cross-thread work task.", (void *)&connection->base);
219+
aws_channel_schedule_task_now(connection->base.channel_slot->channel, &connection->cross_thread_work_task);
220+
} else {
221+
AWS_LOGF_TRACE(
222+
AWS_LS_HTTP_CONNECTION,
223+
"id=%p: Connection cross-thread work task was already scheduled",
224+
(void *)&connection->base);
225+
}
226+
}
227+
192228
/**
193229
* Public function for closing connection.
194230
*/
195231
static void s_connection_close(struct aws_http_connection *connection_base) {
196232
struct aws_h1_connection *connection = AWS_CONTAINER_OF(connection_base, struct aws_h1_connection, base);
197-
198-
/* Don't stop reading/writing immediately, let that happen naturally during the channel shutdown process. */
199-
s_stop(connection, false /*stop_reading*/, false /*stop_writing*/, true /*schedule_shutdown*/, AWS_ERROR_SUCCESS);
233+
s_shutdown_from_off_thread(connection, AWS_ERROR_SUCCESS);
200234
}
201235

202236
static void s_connection_stop_new_request(struct aws_http_connection *connection_base) {
@@ -412,8 +446,7 @@ void aws_h1_stream_cancel(struct aws_http_stream *stream, int error_code) {
412446
(void *)stream,
413447
error_code,
414448
aws_error_name(error_code));
415-
416-
s_stop(connection, false /*stop_reading*/, false /*stop_writing*/, true /*schedule_shutdown*/, error_code);
449+
s_shutdown_from_off_thread(connection, error_code);
417450
}
418451

419452
struct aws_http_stream *s_make_request(
@@ -495,10 +528,17 @@ static void s_cross_thread_work_task(struct aws_channel_task *channel_task, void
495528
bool has_new_client_streams = !aws_linked_list_empty(&connection->synced_data.new_client_stream_list);
496529
aws_linked_list_move_all_back(
497530
&connection->thread_data.stream_list, &connection->synced_data.new_client_stream_list);
531+
bool shutdown_requested = connection->synced_data.shutdown_requested;
532+
int shutdown_error = connection->synced_data.shutdown_requested_error_code;
533+
connection->synced_data.shutdown_requested = false;
534+
connection->synced_data.shutdown_requested_error_code = 0;
498535

499536
aws_h1_connection_unlock_synced_data(connection);
500537
/* END CRITICAL SECTION */
501538

539+
if (shutdown_requested) {
540+
s_stop(connection, true /*stop_reading*/, true /*stop_writing*/, true /*schedule_shutdown*/, shutdown_error);
541+
}
502542
/* Kick off outgoing-stream task if necessary */
503543
if (has_new_client_streams) {
504544
aws_h1_connection_try_write_outgoing_stream(connection);
@@ -785,13 +825,8 @@ static void s_http_stream_response_first_byte_timeout_task(
785825
(void *)connection_base,
786826
response_first_byte_timeout_ms);
787827

788-
/* Don't stop reading/writing immediately, let that happen naturally during the channel shutdown process. */
789-
s_stop(
790-
connection,
791-
false /*stop_reading*/,
792-
false /*stop_writing*/,
793-
true /*schedule_shutdown*/,
794-
AWS_ERROR_HTTP_RESPONSE_FIRST_BYTE_TIMEOUT);
828+
/* Shutdown the connection. */
829+
s_shutdown_due_to_error(connection, AWS_ERROR_HTTP_RESPONSE_FIRST_BYTE_TIMEOUT);
795830
}
796831

797832
static void s_set_outgoing_message_done(struct aws_h1_stream *stream) {
@@ -1804,6 +1839,12 @@ static int s_handler_process_read_message(
18041839

18051840
AWS_LOGF_TRACE(
18061841
AWS_LS_HTTP_CONNECTION, "id=%p: Incoming message of size %zu.", (void *)&connection->base, message_size);
1842+
if (connection->thread_data.is_reading_stopped) {
1843+
/* Read has stopped, ignore the data, shutdown the channel incase it has not started yet. */
1844+
aws_mem_release(message->allocator, message); /* Release the message as we return success. */
1845+
s_shutdown_due_to_error(connection, AWS_ERROR_HTTP_CONNECTION_CLOSED);
1846+
return AWS_OP_SUCCESS;
1847+
}
18071848

18081849
/* Shrink connection window by amount of data received. See comments at variable's
18091850
* declaration site on why we use this instead of the official `aws_channel_slot.window_size`. */

source/websocket.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,8 +989,13 @@ static void s_shutdown_channel_task(struct aws_channel_task *task, void *arg, en
989989

990990
s_unlock_synced_data(websocket);
991991
/* END CRITICAL SECTION */
992+
websocket->thread_data.is_reading_stopped = true;
993+
websocket->thread_data.is_writing_stopped = true;
992994

993995
aws_channel_shutdown(websocket->channel_slot->channel, error_code);
996+
/* Increase the window size after shutdown starts, to prevent deadlock when data still pending in the upstream
997+
* handler. */
998+
aws_channel_slot_increment_read_window(websocket->channel_slot, SIZE_MAX);
994999
}
9951000

9961001
/* Tell the channel to shut down. It is safe to call this multiple times.

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ add_test_case(strutil_is_http_pseudo_header_name)
159159

160160
add_net_test_case(tls_download_medium_file_h1)
161161
add_net_test_case(tls_download_medium_file_h2)
162+
add_net_test_case(test_tls_download_shutdown_with_window_size_0)
162163

163164
add_test_case(websocket_decoder_sanity_check)
164165
add_test_case(websocket_decoder_simplest_frame)

tests/test_connection.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,9 +508,6 @@ AWS_TEST_CASE(connection_setup_shutdown, s_test_connection_setup_shutdown);
508508
static int s_test_connection_setup_shutdown_tls(struct aws_allocator *allocator, void *ctx) {
509509
(void)ctx;
510510

511-
#ifdef __APPLE__ /* Something is wrong with APPLE */
512-
return AWS_OP_SUCCESS;
513-
#endif
514511
struct tester_options options = {
515512
.alloc = allocator,
516513
.tls = true,

0 commit comments

Comments
 (0)