Skip to content

Commit 2c19abf

Browse files
authored
Cancel/Pause stream optimization (#395)
1 parent bb6af37 commit 2c19abf

File tree

6 files changed

+161
-31
lines changed

6 files changed

+161
-31
lines changed

include/aws/s3/private/s3_meta_request_impl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ struct aws_s3_meta_request {
221221
/* True if the finish result has been set. */
222222
uint32_t finish_result_set : 1;
223223

224+
/* To track the aws_s3_request that are active from HTTP level */
225+
struct aws_linked_list ongoing_http_requests_list;
226+
224227
} synced_data;
225228

226229
/* Anything in this structure should only ever be accessed by the client on its process work event loop task. */
@@ -359,6 +362,9 @@ void aws_s3_meta_request_add_event_for_delivery_synced(
359362
* The meta-request's finish callback must not be invoked until this returns false. */
360363
bool aws_s3_meta_request_are_events_out_for_delivery_synced(struct aws_s3_meta_request *meta_request);
361364

365+
/* Cancel the requests with ongoing HTTP activities for the meta request */
366+
void aws_s3_meta_request_cancel_ongoing_http_requests_synced(struct aws_s3_meta_request *meta_request, int error_code);
367+
362368
/* Asynchronously read from the meta request's input stream. Should always be done outside of any mutex,
363369
* as reading from the stream could cause user code to call back into aws-c-s3.
364370
* This will fill the buffer to capacity, unless end of stream is reached.

include/aws/s3/private/s3_request.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ struct aws_s3_request {
113113
/* Linked list node used for queuing. */
114114
struct aws_linked_list_node node;
115115

116+
/* Linked list node used for tracking the request is active from HTTP level. */
117+
struct aws_linked_list_node ongoing_http_requests_list_node;
118+
119+
/* The meta request lock must be held to access the data */
120+
struct {
121+
/* The underlying http stream, only valid when the request is active from HTTP level */
122+
struct aws_http_stream *http_stream;
123+
} synced_data;
124+
116125
/* TODO Ref count on the request is no longer needed--only one part of code should ever be holding onto a request,
117126
* and we can just transfer ownership.*/
118127
struct aws_ref_count ref_count;

source/s3_auto_ranged_put.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,8 @@ static int s_s3_auto_ranged_put_pause(
17111711
*/
17121712
aws_s3_meta_request_set_fail_synced(meta_request, NULL, AWS_ERROR_S3_PAUSED);
17131713

1714+
aws_s3_meta_request_cancel_ongoing_http_requests_synced(meta_request, AWS_ERROR_S3_PAUSED);
1715+
17141716
/* unlock */
17151717
aws_s3_meta_request_unlock_synced_data(meta_request);
17161718

source/s3_meta_request.c

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ int aws_s3_meta_request_init_base(
203203
meta_request->type = options->type;
204204
/* Set up reference count. */
205205
aws_ref_count_init(&meta_request->ref_count, meta_request, s_s3_meta_request_destroy);
206+
aws_linked_list_init(&meta_request->synced_data.ongoing_http_requests_list);
206207

207208
if (part_size == SIZE_MAX) {
208209
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
@@ -345,6 +346,7 @@ void aws_s3_meta_request_cancel(struct aws_s3_meta_request *meta_request) {
345346
/* BEGIN CRITICAL SECTION */
346347
aws_s3_meta_request_lock_synced_data(meta_request);
347348
aws_s3_meta_request_set_fail_synced(meta_request, NULL, AWS_ERROR_S3_CANCELED);
349+
aws_s3_meta_request_cancel_ongoing_http_requests_synced(meta_request, AWS_ERROR_S3_CANCELED);
348350
aws_s3_meta_request_unlock_synced_data(meta_request);
349351
/* END CRITICAL SECTION */
350352
}
@@ -486,6 +488,8 @@ static void s_s3_meta_request_destroy(void *user_data) {
486488
AWS_ASSERT(aws_array_list_length(&meta_request->io_threaded_data.event_delivery_array) == 0);
487489
aws_array_list_clean_up(&meta_request->io_threaded_data.event_delivery_array);
488490

491+
AWS_ASSERT(aws_linked_list_empty(&meta_request->synced_data.ongoing_http_requests_list));
492+
489493
aws_s3_meta_request_result_clean_up(meta_request, &meta_request->synced_data.finish_result);
490494

491495
if (meta_request->vtable != NULL) {
@@ -1077,6 +1081,15 @@ void aws_s3_meta_request_send_request(struct aws_s3_meta_request *meta_request,
10771081
goto error_finish;
10781082
}
10791083

1084+
{
1085+
/* BEGIN CRITICAL SECTION */
1086+
aws_s3_meta_request_lock_synced_data(meta_request);
1087+
aws_linked_list_push_back(
1088+
&meta_request->synced_data.ongoing_http_requests_list, &request->ongoing_http_requests_list_node);
1089+
request->synced_data.http_stream = stream;
1090+
aws_s3_meta_request_unlock_synced_data(meta_request);
1091+
/* END CRITICAL SECTION */
1092+
}
10801093
return;
10811094

10821095
error_finish:
@@ -1366,9 +1379,20 @@ static void s_s3_meta_request_stream_complete(struct aws_http_stream *stream, in
13661379

13671380
struct aws_s3_connection *connection = user_data;
13681381
AWS_PRECONDITION(connection);
1369-
if (connection->request->meta_request->checksum_config.validate_response_checksum) {
1382+
struct aws_s3_request *request = connection->request;
1383+
struct aws_s3_meta_request *meta_request = request->meta_request;
1384+
1385+
if (meta_request->checksum_config.validate_response_checksum) {
13701386
s_get_response_part_finish_checksum_helper(connection, error_code);
13711387
}
1388+
if (error_code != AWS_ERROR_S3_CANCELED && error_code != AWS_ERROR_S3_PAUSED) {
1389+
/* BEGIN CRITICAL SECTION */
1390+
aws_s3_meta_request_lock_synced_data(meta_request);
1391+
AWS_ASSERT(request->synced_data.http_stream != NULL);
1392+
aws_linked_list_remove(&request->ongoing_http_requests_list_node);
1393+
aws_s3_meta_request_unlock_synced_data(meta_request);
1394+
/* END CRITICAL SECTION */
1395+
}
13721396
s_s3_meta_request_send_request_finish(connection, stream, error_code);
13731397
}
13741398

@@ -1644,6 +1668,21 @@ bool aws_s3_meta_request_are_events_out_for_delivery_synced(struct aws_s3_meta_r
16441668
meta_request->synced_data.event_delivery_active;
16451669
}
16461670

1671+
void aws_s3_meta_request_cancel_ongoing_http_requests_synced(struct aws_s3_meta_request *meta_request, int error_code) {
1672+
ASSERT_SYNCED_DATA_LOCK_HELD(meta_request);
1673+
while (!aws_linked_list_empty(&meta_request->synced_data.ongoing_http_requests_list)) {
1674+
struct aws_linked_list_node *request_node =
1675+
aws_linked_list_pop_front(&meta_request->synced_data.ongoing_http_requests_list);
1676+
struct aws_s3_request *request =
1677+
AWS_CONTAINER_OF(request_node, struct aws_s3_request, ongoing_http_requests_list_node);
1678+
if (!request->always_send) {
1679+
/* Cancel the ongoing http stream, unless it's always send. */
1680+
aws_http_stream_cancel(request->synced_data.http_stream, error_code);
1681+
}
1682+
request->synced_data.http_stream = NULL;
1683+
}
1684+
}
1685+
16471686
/* Deliver events in event_delivery_array.
16481687
* This task runs on the meta-request's io_event_loop thread. */
16491688
static void s_s3_meta_request_event_delivery_task(struct aws_task *task, void *arg, enum aws_task_status task_status) {
@@ -1887,9 +1926,9 @@ void aws_s3_meta_request_finish_default(struct aws_s3_meta_request *meta_request
18871926
finish_result.error_code,
18881927
aws_error_str(finish_result.error_code));
18891928

1890-
/* As the meta request has been finished with any HTTP message, we can safely release the http message that hold. So
1891-
* that, the downstream high level language doesn't need to wait for shutdown to clean related resource (eg: input
1892-
* stream) */
1929+
/* As the meta request has been finished with any HTTP message, we can safely release the http message that
1930+
* hold. So that, the downstream high level language doesn't need to wait for shutdown to clean related resource
1931+
* (eg: input stream) */
18931932
meta_request->request_body_async_stream = aws_async_input_stream_release(meta_request->request_body_async_stream);
18941933
meta_request->request_body_parallel_stream =
18951934
aws_parallel_input_stream_release(meta_request->request_body_parallel_stream);

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ add_net_test_case(test_s3_cancel_mpu_create_completed)
4949
add_net_test_case(test_s3_cancel_mpu_one_part_completed)
5050
add_net_test_case(test_s3_cancel_mpu_one_part_completed_async)
5151
add_net_test_case(test_s3_cancel_mpu_all_parts_completed)
52+
add_net_test_case(test_s3_cancel_mpu_ongoing_http_requests)
53+
add_net_test_case(test_s3_pause_mpu_ongoing_http_requests)
5254
add_net_test_case(test_s3_cancel_mpd_nothing_sent)
5355
add_net_test_case(test_s3_cancel_mpd_one_part_sent)
5456
add_net_test_case(test_s3_cancel_mpd_one_part_completed)

tests/s3_cancel_tests.c

Lines changed: 99 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
#include <aws/testing/aws_test_harness.h>
1414

1515
enum s3_update_cancel_type {
16+
S3_UPDATE_CANCEL_TYPE_NO_CANCEL,
17+
1618
S3_UPDATE_CANCEL_TYPE_MPU_CREATE_NOT_SENT,
1719
S3_UPDATE_CANCEL_TYPE_MPU_CREATE_COMPLETED,
1820
S3_UPDATE_CANCEL_TYPE_MPU_ONE_PART_COMPLETED,
1921
S3_UPDATE_CANCEL_TYPE_MPU_ALL_PARTS_COMPLETED,
22+
S3_UPDATE_CANCEL_TYPE_MPU_ONGOING_HTTP_REQUESTS,
2023
S3_UPDATE_CANCEL_TYPE_NUM_MPU_CANCEL_TYPES,
2124

2225
S3_UPDATE_CANCEL_TYPE_MPD_NOTHING_SENT,
@@ -31,6 +34,8 @@ enum s3_update_cancel_type {
3134

3235
struct s3_cancel_test_user_data {
3336
enum s3_update_cancel_type type;
37+
bool pause;
38+
struct aws_s3_meta_request_resume_token *resume_token;
3439
bool abort_successful;
3540
};
3641

@@ -48,74 +53,84 @@ static bool s_s3_meta_request_update_cancel_test(
4853
struct aws_s3_auto_ranged_put *auto_ranged_put = meta_request->impl;
4954
struct aws_s3_auto_ranged_get *auto_ranged_get = meta_request->impl;
5055

51-
bool call_cancel = false;
56+
bool call_cancel_or_pause = false;
5257
bool block_update = false;
5358

5459
aws_s3_meta_request_lock_synced_data(meta_request);
5560

5661
switch (cancel_test_user_data->type) {
62+
case S3_UPDATE_CANCEL_TYPE_NO_CANCEL:
63+
break;
64+
5765
case S3_UPDATE_CANCEL_TYPE_MPU_CREATE_NOT_SENT:
58-
call_cancel = auto_ranged_put->synced_data.create_multipart_upload_sent != 0;
66+
call_cancel_or_pause = auto_ranged_put->synced_data.create_multipart_upload_sent != 0;
5967
break;
6068
case S3_UPDATE_CANCEL_TYPE_MPU_CREATE_COMPLETED:
61-
call_cancel = auto_ranged_put->synced_data.create_multipart_upload_completed != 0;
69+
call_cancel_or_pause = auto_ranged_put->synced_data.create_multipart_upload_completed != 0;
6270
break;
6371
case S3_UPDATE_CANCEL_TYPE_MPU_ONE_PART_COMPLETED:
64-
call_cancel = auto_ranged_put->synced_data.num_parts_completed == 1;
65-
block_update = !call_cancel && auto_ranged_put->synced_data.num_parts_started == 1;
72+
call_cancel_or_pause = auto_ranged_put->synced_data.num_parts_completed == 1;
73+
block_update = !call_cancel_or_pause && auto_ranged_put->synced_data.num_parts_started == 1;
6674
break;
6775
case S3_UPDATE_CANCEL_TYPE_MPU_ALL_PARTS_COMPLETED:
68-
call_cancel = auto_ranged_put->synced_data.num_parts_completed ==
69-
auto_ranged_put->total_num_parts_from_content_length;
76+
call_cancel_or_pause = auto_ranged_put->synced_data.num_parts_completed ==
77+
auto_ranged_put->total_num_parts_from_content_length;
78+
break;
79+
80+
case S3_UPDATE_CANCEL_TYPE_MPU_ONGOING_HTTP_REQUESTS:
81+
call_cancel_or_pause = !aws_linked_list_empty(&meta_request->synced_data.ongoing_http_requests_list);
7082
break;
7183

7284
case S3_UPDATE_CANCEL_TYPE_NUM_MPU_CANCEL_TYPES:
7385
AWS_ASSERT(false);
7486
break;
7587

7688
case S3_UPDATE_CANCEL_TYPE_MPD_NOTHING_SENT:
77-
call_cancel = auto_ranged_get->synced_data.num_parts_requested == 0;
89+
call_cancel_or_pause = auto_ranged_get->synced_data.num_parts_requested == 0;
7890
break;
7991

8092
case S3_UPDATE_CANCEL_TYPE_MPD_HEAD_OBJECT_SENT:
81-
call_cancel = auto_ranged_get->synced_data.head_object_sent != 0;
93+
call_cancel_or_pause = auto_ranged_get->synced_data.head_object_sent != 0;
8294
break;
8395

8496
case S3_UPDATE_CANCEL_TYPE_MPD_HEAD_OBJECT_COMPLETED:
85-
call_cancel = auto_ranged_get->synced_data.head_object_completed != 0;
97+
call_cancel_or_pause = auto_ranged_get->synced_data.head_object_completed != 0;
8698
break;
8799

88100
case S3_UPDATE_CANCEL_TYPE_MPD_GET_WITHOUT_RANGE_SENT:
89-
call_cancel = auto_ranged_get->synced_data.get_without_range_sent != 0;
101+
call_cancel_or_pause = auto_ranged_get->synced_data.get_without_range_sent != 0;
90102
break;
91103

92104
case S3_UPDATE_CANCEL_TYPE_MPD_GET_WITHOUT_RANGE_COMPLETED:
93-
call_cancel = auto_ranged_get->synced_data.get_without_range_completed != 0;
105+
call_cancel_or_pause = auto_ranged_get->synced_data.get_without_range_completed != 0;
94106
break;
95107

96108
case S3_UPDATE_CANCEL_TYPE_MPD_ONE_PART_SENT:
97-
call_cancel = auto_ranged_get->synced_data.num_parts_requested == 1;
109+
call_cancel_or_pause = auto_ranged_get->synced_data.num_parts_requested == 1;
98110
break;
99111

100112
case S3_UPDATE_CANCEL_TYPE_MPD_ONE_PART_COMPLETED:
101-
call_cancel = auto_ranged_get->synced_data.num_parts_completed == 1;
113+
call_cancel_or_pause = auto_ranged_get->synced_data.num_parts_completed == 1;
102114

103115
/* Prevent other parts from being queued while we wait for this one to complete. */
104-
block_update = !call_cancel && auto_ranged_get->synced_data.num_parts_requested == 1;
116+
block_update = !call_cancel_or_pause && auto_ranged_get->synced_data.num_parts_requested == 1;
105117
break;
106118

107119
case S3_UPDATE_CANCEL_TYPE_MPD_TWO_PARTS_COMPLETED:
108-
call_cancel = auto_ranged_get->synced_data.num_parts_completed == 2;
120+
call_cancel_or_pause = auto_ranged_get->synced_data.num_parts_completed == 2;
109121

110122
/* Prevent other parts from being queued while we wait for these two to complete. */
111-
block_update = !call_cancel && auto_ranged_get->synced_data.num_parts_requested == 2;
123+
block_update = !call_cancel_or_pause && auto_ranged_get->synced_data.num_parts_requested == 2;
112124
break;
113125
}
114126

115127
aws_s3_meta_request_unlock_synced_data(meta_request);
116-
117-
if (call_cancel) {
118-
aws_s3_meta_request_cancel(meta_request);
128+
if (call_cancel_or_pause) {
129+
if (cancel_test_user_data->pause) {
130+
aws_s3_meta_request_pause(meta_request, &cancel_test_user_data->resume_token);
131+
} else {
132+
aws_s3_meta_request_cancel(meta_request);
133+
}
119134
}
120135

121136
if (block_update) {
@@ -175,7 +190,8 @@ static struct aws_s3_meta_request *s_meta_request_factory_patch_update_cancel_te
175190
static int s3_cancel_test_helper_ex(
176191
struct aws_allocator *allocator,
177192
enum s3_update_cancel_type cancel_type,
178-
bool async_input_stream) {
193+
bool async_input_stream,
194+
bool pause) {
179195

180196
AWS_ASSERT(allocator);
181197

@@ -184,6 +200,7 @@ static int s3_cancel_test_helper_ex(
184200

185201
struct s3_cancel_test_user_data test_user_data = {
186202
.type = cancel_type,
203+
.pause = pause,
187204
};
188205

189206
tester.user_data = &test_user_data;
@@ -221,13 +238,49 @@ static int s3_cancel_test_helper_ex(
221238
};
222239

223240
ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &options, &meta_request_test_results));
224-
ASSERT_INT_EQUALS(AWS_ERROR_S3_CANCELED, meta_request_test_results.finished_error_code);
241+
int expected_error_code = pause ? AWS_ERROR_S3_PAUSED : AWS_ERROR_S3_CANCELED;
242+
ASSERT_INT_EQUALS(expected_error_code, meta_request_test_results.finished_error_code);
243+
244+
if (cancel_type == S3_UPDATE_CANCEL_TYPE_MPU_ONGOING_HTTP_REQUESTS) {
245+
/* Check the metric and see we have at least a request completed with AWS_ERROR_S3_CANCELED */
246+
/* The meta request completed, we can access the synced data now. */
247+
struct aws_array_list *metrics_list = &meta_request_test_results.synced_data.metrics;
248+
bool cancelled_successfully = false;
249+
for (size_t i = 0; i < aws_array_list_length(metrics_list); ++i) {
250+
struct aws_s3_request_metrics *metrics = NULL;
251+
aws_array_list_get_at(metrics_list, (void **)&metrics, i);
252+
if (metrics->crt_info_metrics.error_code == expected_error_code) {
253+
cancelled_successfully = true;
254+
break;
255+
}
256+
}
257+
ASSERT_TRUE(cancelled_successfully);
258+
}
225259

226260
aws_s3_meta_request_test_results_clean_up(&meta_request_test_results);
227-
228-
if (cancel_type != S3_UPDATE_CANCEL_TYPE_MPU_CREATE_NOT_SENT) {
261+
if (cancel_type != S3_UPDATE_CANCEL_TYPE_MPU_CREATE_NOT_SENT && !pause) {
229262
ASSERT_TRUE(test_user_data.abort_successful);
230263
}
264+
if (pause) {
265+
/* Resume the paused request. */
266+
ASSERT_NOT_NULL(test_user_data.resume_token);
267+
test_user_data.type = S3_UPDATE_CANCEL_TYPE_NO_CANCEL;
268+
struct aws_s3_tester_meta_request_options resume_options = {
269+
.allocator = allocator,
270+
.client = client,
271+
.meta_request_type = AWS_S3_META_REQUEST_TYPE_PUT_OBJECT,
272+
.validate_type = AWS_S3_TESTER_VALIDATE_TYPE_EXPECT_SUCCESS,
273+
.put_options =
274+
{
275+
.ensure_multipart = true,
276+
.async_input_stream = async_input_stream,
277+
.resume_token = test_user_data.resume_token,
278+
},
279+
};
280+
281+
ASSERT_SUCCESS(aws_s3_tester_send_meta_request_with_options(&tester, &resume_options, NULL));
282+
aws_s3_meta_request_resume_token_release(test_user_data.resume_token);
283+
}
231284

232285
/* TODO: perform additional verification with list-multipart-uploads */
233286

@@ -284,7 +337,7 @@ static int s3_cancel_test_helper_ex(
284337
}
285338

286339
static int s3_cancel_test_helper(struct aws_allocator *allocator, enum s3_update_cancel_type cancel_type) {
287-
return s3_cancel_test_helper_ex(allocator, cancel_type, false /*async_input_stream*/);
340+
return s3_cancel_test_helper_ex(allocator, cancel_type, false /*async_input_stream*/, false /*pause*/);
288341
}
289342

290343
static int s3_cancel_test_helper_fc(
@@ -459,8 +512,8 @@ AWS_TEST_CASE(test_s3_cancel_mpu_one_part_completed_async, s_test_s3_cancel_mpu_
459512
static int s_test_s3_cancel_mpu_one_part_completed_async(struct aws_allocator *allocator, void *ctx) {
460513
(void)ctx;
461514

462-
ASSERT_SUCCESS(
463-
s3_cancel_test_helper_ex(allocator, S3_UPDATE_CANCEL_TYPE_MPU_ONE_PART_COMPLETED, true /*async_input_stream*/));
515+
ASSERT_SUCCESS(s3_cancel_test_helper_ex(
516+
allocator, S3_UPDATE_CANCEL_TYPE_MPU_ONE_PART_COMPLETED, true /*async_input_stream*/, false /*pause*/));
464517

465518
return 0;
466519
}
@@ -474,6 +527,25 @@ static int s_test_s3_cancel_mpu_all_parts_completed(struct aws_allocator *alloca
474527
return 0;
475528
}
476529

530+
AWS_TEST_CASE(test_s3_cancel_mpu_ongoing_http_requests, s_test_s3_cancel_mpu_ongoing_http_requests)
531+
static int s_test_s3_cancel_mpu_ongoing_http_requests(struct aws_allocator *allocator, void *ctx) {
532+
(void)ctx;
533+
534+
ASSERT_SUCCESS(s3_cancel_test_helper(allocator, S3_UPDATE_CANCEL_TYPE_MPU_ONGOING_HTTP_REQUESTS));
535+
536+
return 0;
537+
}
538+
539+
AWS_TEST_CASE(test_s3_pause_mpu_ongoing_http_requests, s_test_s3_pause_mpu_ongoing_http_requests)
540+
static int s_test_s3_pause_mpu_ongoing_http_requests(struct aws_allocator *allocator, void *ctx) {
541+
(void)ctx;
542+
543+
ASSERT_SUCCESS(s3_cancel_test_helper_ex(
544+
allocator, S3_UPDATE_CANCEL_TYPE_MPU_ONGOING_HTTP_REQUESTS, false /*async_input_stream*/, true /*pause*/));
545+
546+
return 0;
547+
}
548+
477549
AWS_TEST_CASE(test_s3_cancel_mpd_nothing_sent, s_test_s3_cancel_mpd_nothing_sent)
478550
static int s_test_s3_cancel_mpd_nothing_sent(struct aws_allocator *allocator, void *ctx) {
479551
(void)ctx;

0 commit comments

Comments
 (0)