Skip to content

Commit f8ae82e

Browse files
Avoid releasing pending mem ticket future while holding the lock (#533)
1 parent 5946dc6 commit f8ae82e

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

source/s3_default_buffer_pool.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ static void s_aws_ticket_wrapper_destroy(void *data) {
389389
aws_mem_release(buffer_pool->base_allocator, ticket);
390390
aws_mem_release(buffer_pool->base_allocator, ticket_wrapper);
391391

392+
struct aws_future_s3_buffer_ticket *pending_ticket_future = NULL;
393+
392394
if (!aws_linked_list_empty(&buffer_pool->pending_reserves)) {
393395
struct aws_linked_list_node *node = aws_linked_list_front(&buffer_pool->pending_reserves);
394396
struct s3_pending_reserve *pending_reserve = AWS_CONTAINER_OF(node, struct s3_pending_reserve, node);
@@ -397,14 +399,15 @@ static void s_aws_ticket_wrapper_destroy(void *data) {
397399

398400
if (new_ticket != NULL) {
399401
struct aws_s3_buffer_ticket *new_ticket_wrapper = s_wrap_default_ticket(new_ticket);
400-
aws_future_s3_buffer_ticket_set_result_by_move(pending_reserve->ticket_future, &new_ticket_wrapper);
401-
aws_future_s3_buffer_ticket_release(pending_reserve->ticket_future);
402+
pending_ticket_future = pending_reserve->ticket_future;
403+
aws_future_s3_buffer_ticket_set_result_by_move(pending_ticket_future, &new_ticket_wrapper);
402404
aws_linked_list_pop_front(&buffer_pool->pending_reserves);
403405
aws_mem_release(buffer_pool->base_allocator, pending_reserve);
404406
}
405407
}
406408

407409
aws_mutex_unlock(&buffer_pool->mutex);
410+
aws_future_s3_buffer_ticket_release(pending_ticket_future);
408411
}
409412

410413
struct aws_s3_default_buffer_ticket *s_try_reserve(

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ add_net_test_case(test_s3_put_object_buffer_acquire_error)
398398
add_test_case(test_s3_buffer_pool_forced_buffer)
399399
add_test_case(test_s3_buffer_pool_forced_buffer_after_limit_hit)
400400
add_test_case(test_s3_buffer_pool_forced_buffer_wont_stop_reservations)
401+
add_test_case(test_s3_buffer_pool_reserve_over_limit_instant_release)
401402

402403
add_net_test_case(client_update_upload_part_timeout)
403404
add_net_test_case(client_meta_request_override_part_size)

tests/s3_default_buffer_pool_tests.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,62 @@ static int s_test_s3_buffer_pool_reserve_over_limit(struct aws_allocator *alloca
268268
};
269269
AWS_TEST_CASE(test_s3_buffer_pool_reserve_over_limit, s_test_s3_buffer_pool_reserve_over_limit)
270270

271+
static void s_on_pool_buffer_reserved_instant_release(void *user_data) {
272+
struct s_reserve_state *state = user_data;
273+
274+
if (aws_future_s3_buffer_ticket_get_error(state->future) == AWS_OP_SUCCESS) {
275+
state->ticket = aws_future_s3_buffer_ticket_get_result_by_move(state->future);
276+
}
277+
278+
state->future = aws_future_s3_buffer_ticket_release(state->future);
279+
}
280+
281+
/* release future in the callback right away to check for potential race conditions */
282+
static int s_test_s3_buffer_pool_reserve_over_limit_instant_release(struct aws_allocator *allocator, void *ctx) {
283+
(void)allocator;
284+
(void)ctx;
285+
286+
struct aws_s3_buffer_pool *buffer_pool = aws_s3_default_buffer_pool_new(
287+
allocator, (struct aws_s3_buffer_pool_config){.part_size = MB_TO_BYTES(8), .memory_limit = GB_TO_BYTES(1)});
288+
289+
struct aws_s3_buffer_ticket *tickets[112];
290+
struct aws_future_s3_buffer_ticket *ticket_futures[112];
291+
for (size_t i = 0; i < 112; ++i) {
292+
ticket_futures[i] = aws_s3_default_buffer_pool_reserve(
293+
buffer_pool, (struct aws_s3_buffer_pool_reserve_meta){.size = MB_TO_BYTES(8)});
294+
ASSERT_TRUE(aws_future_s3_buffer_ticket_is_done(ticket_futures[i]));
295+
ASSERT_INT_EQUALS(aws_future_s3_buffer_ticket_get_error(ticket_futures[i]), AWS_OP_SUCCESS);
296+
tickets[i] = aws_future_s3_buffer_ticket_get_result_by_move(ticket_futures[i]);
297+
struct aws_byte_buf buf = aws_s3_buffer_ticket_claim(tickets[i]);
298+
ASSERT_NOT_NULL(buf.buffer);
299+
}
300+
301+
struct aws_future_s3_buffer_ticket *over_future = aws_s3_default_buffer_pool_reserve(
302+
buffer_pool, (struct aws_s3_buffer_pool_reserve_meta){.size = MB_TO_BYTES(8)});
303+
304+
ASSERT_FALSE(aws_future_s3_buffer_ticket_is_done(over_future));
305+
306+
struct s_reserve_state state = {.future = over_future};
307+
308+
aws_future_s3_buffer_ticket_register_callback(over_future, s_on_pool_buffer_reserved_instant_release, &state);
309+
310+
for (size_t i = 0; i < 112; ++i) {
311+
aws_s3_buffer_ticket_release(tickets[i]);
312+
aws_future_s3_buffer_ticket_release(ticket_futures[i]);
313+
}
314+
315+
ASSERT_NOT_NULL(state.ticket);
316+
317+
aws_s3_buffer_ticket_release(state.ticket);
318+
319+
aws_s3_default_buffer_pool_destroy(buffer_pool);
320+
321+
return 0;
322+
};
323+
AWS_TEST_CASE(
324+
test_s3_buffer_pool_reserve_over_limit_instant_release,
325+
s_test_s3_buffer_pool_reserve_over_limit_instant_release)
326+
271327
static int s_test_s3_buffer_pool_too_small(struct aws_allocator *allocator, void *ctx) {
272328
(void)allocator;
273329
(void)ctx;

0 commit comments

Comments
 (0)