From 6236599339e5571b6dbc4b5f2831b1913aa2e69a Mon Sep 17 00:00:00 2001 From: Waqar Ahmed Khan Date: Fri, 3 May 2024 14:23:59 -0700 Subject: [PATCH] Remove promise class (#1110) --- include/aws/common/promise.h | 103 -------------------- source/promise.c | 118 ----------------------- tests/CMakeLists.txt | 6 -- tests/promise_test.c | 181 ----------------------------------- 4 files changed, 408 deletions(-) delete mode 100644 include/aws/common/promise.h delete mode 100644 source/promise.c delete mode 100644 tests/promise_test.c diff --git a/include/aws/common/promise.h b/include/aws/common/promise.h deleted file mode 100644 index 37f930ee1..000000000 --- a/include/aws/common/promise.h +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#ifndef AWS_COMMON_PROMISE_H -#define AWS_COMMON_PROMISE_H - -#include - -AWS_PUSH_SANE_WARNING_LEVEL - -/* - * Standard promise interface. Promise can be waited on by multiple threads, and as long as it is - * ref-counted correctly, will provide the resultant value/error code to all waiters. - * All promise API calls are internally thread-safe. - */ -struct aws_promise; - -AWS_EXTERN_C_BEGIN - -/* - * Creates a new promise - */ -AWS_COMMON_API -struct aws_promise *aws_promise_new(struct aws_allocator *allocator); - -/* - * Indicate a new reference to a promise. At minimum, each new thread making use of the promise should - * acquire it. - */ -AWS_COMMON_API -struct aws_promise *aws_promise_acquire(struct aws_promise *promise); - -/* - * Releases a reference on the promise. When the refcount hits 0, the promise is cleaned up and freed. - */ -AWS_COMMON_API -void aws_promise_release(struct aws_promise *promise); - -/* - * Waits infinitely for the promise to be completed - */ -AWS_COMMON_API -void aws_promise_wait(struct aws_promise *promise); -/* - * Waits for the requested time in nanoseconds. Returns true if the promise was completed. - */ -AWS_COMMON_API -bool aws_promise_wait_for(struct aws_promise *promise, size_t nanoseconds); - -/* - * Completes the promise and stores the result along with an optional destructor. If the value - * is not taken via `aws_promise_take_value`, it will be destroyed when the promise's reference - * count reaches zero. - * NOTE: Promise cannot be completed twice - */ -AWS_COMMON_API -void aws_promise_complete(struct aws_promise *promise, void *value, void (*dtor)(void *)); - -/* - * Completes the promise and stores the error code - * NOTE: Promise cannot be completed twice - */ -AWS_COMMON_API -void aws_promise_fail(struct aws_promise *promise, int error_code); - -/* - * Returns whether or not the promise has completed (regardless of success or failure) - */ -AWS_COMMON_API -bool aws_promise_is_complete(struct aws_promise *promise); - -/* - * Returns the error code recorded if the promise failed, or 0 if it succeeded - * NOTE: It is fatal to attempt to retrieve the error code before the promise is completed - */ -AWS_COMMON_API -int aws_promise_error_code(struct aws_promise *promise); - -/* - * Returns the value provided to the promise if it succeeded, or NULL if none was provided - * or the promise failed. Check `aws_promise_error_code` to be sure. - * NOTE: The ownership of the value is retained by the promise. - * NOTE: It is fatal to attempt to retrieve the value before the promise is completed - */ -AWS_COMMON_API -void *aws_promise_value(struct aws_promise *promise); - -/* - * Returns the value provided to the promise if it succeeded, or NULL if none was provided - * or the promise failed. Check `aws_promise_error_code` to be sure. - * NOTE: The promise relinquishes ownership of the value, the caller is now responsible for - * freeing any resources associated with the value - * NOTE: It is fatal to attempt to take the value before the promise is completed - */ -AWS_COMMON_API -void *aws_promise_take_value(struct aws_promise *promise); - -AWS_EXTERN_C_END -AWS_POP_SANE_WARNING_LEVEL - -#endif // AWS_COMMON_PROMISE_H diff --git a/source/promise.c b/source/promise.c deleted file mode 100644 index 7c8572457..000000000 --- a/source/promise.c +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#include -#include -#include -#include - -struct aws_promise { - struct aws_allocator *allocator; - struct aws_mutex mutex; - struct aws_condition_variable cv; - struct aws_ref_count rc; - bool complete; - int error_code; - void *value; - - /* destructor for value, will be invoked if the value is not taken */ - void (*dtor)(void *); -}; - -static void s_aws_promise_dtor(void *ptr) { - struct aws_promise *promise = ptr; - aws_condition_variable_clean_up(&promise->cv); - aws_mutex_clean_up(&promise->mutex); - if (promise->value && promise->dtor) { - promise->dtor(promise->value); - } - aws_mem_release(promise->allocator, promise); -} - -struct aws_promise *aws_promise_new(struct aws_allocator *allocator) { - struct aws_promise *promise = aws_mem_calloc(allocator, 1, sizeof(struct aws_promise)); - promise->allocator = allocator; - aws_ref_count_init(&promise->rc, promise, s_aws_promise_dtor); - aws_mutex_init(&promise->mutex); - aws_condition_variable_init(&promise->cv); - return promise; -} - -struct aws_promise *aws_promise_acquire(struct aws_promise *promise) { - aws_ref_count_acquire(&promise->rc); - return promise; -} - -void aws_promise_release(struct aws_promise *promise) { - aws_ref_count_release(&promise->rc); -} - -static bool s_promise_completed(void *user_data) { - struct aws_promise *promise = user_data; - return promise->complete; -} - -void aws_promise_wait(struct aws_promise *promise) { - aws_mutex_lock(&promise->mutex); - aws_condition_variable_wait_pred(&promise->cv, &promise->mutex, s_promise_completed, promise); - aws_mutex_unlock(&promise->mutex); -} - -bool aws_promise_wait_for(struct aws_promise *promise, size_t nanoseconds) { - aws_mutex_lock(&promise->mutex); - aws_condition_variable_wait_for_pred( - &promise->cv, &promise->mutex, (int64_t)nanoseconds, s_promise_completed, promise); - const bool complete = promise->complete; - aws_mutex_unlock(&promise->mutex); - return complete; -} - -bool aws_promise_is_complete(struct aws_promise *promise) { - aws_mutex_lock(&promise->mutex); - const bool complete = promise->complete; - aws_mutex_unlock(&promise->mutex); - return complete; -} - -void aws_promise_complete(struct aws_promise *promise, void *value, void (*dtor)(void *)) { - aws_mutex_lock(&promise->mutex); - AWS_FATAL_ASSERT(!promise->complete && "aws_promise_complete: cannot complete a promise more than once"); - promise->value = value; - promise->dtor = dtor; - promise->complete = true; - /* Notify before unlocking to prevent a race condition where the recipient spuriously - * awakens after the unlock, sees a fulfilled promise, and attempts to free its resources - * before the notification has actually occured. */ - aws_condition_variable_notify_all(&promise->cv); - aws_mutex_unlock(&promise->mutex); -} - -void aws_promise_fail(struct aws_promise *promise, int error_code) { - AWS_FATAL_ASSERT(error_code != 0 && "aws_promise_fail: cannot fail a promise with a 0 error_code"); - aws_mutex_lock(&promise->mutex); - AWS_FATAL_ASSERT(!promise->complete && "aws_promise_fail: cannot complete a promise more than once"); - promise->error_code = error_code; - promise->complete = true; - aws_condition_variable_notify_all(&promise->cv); - aws_mutex_unlock(&promise->mutex); -} - -int aws_promise_error_code(struct aws_promise *promise) { - AWS_FATAL_ASSERT(aws_promise_is_complete(promise)); - return promise->error_code; -} - -void *aws_promise_value(struct aws_promise *promise) { - AWS_FATAL_ASSERT(aws_promise_is_complete(promise)); - return promise->value; -} - -void *aws_promise_take_value(struct aws_promise *promise) { - AWS_FATAL_ASSERT(aws_promise_is_complete(promise)); - void *value = promise->value; - promise->value = NULL; - promise->dtor = NULL; - return value; -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6f224e4f2..ee281ac28 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -489,12 +489,6 @@ add_test_case(test_normalize_posix_directory_separator) add_test_case(test_normalize_windows_directory_separator) add_test_case(test_byte_buf_init_from_file) -add_test_case(promise_test_wait_forever) -add_test_case(promise_test_wait_for_a_bit) -add_test_case(promise_test_finish_immediately) -add_test_case(promise_test_finish_before_wait) -add_test_case(promise_test_multiple_waiters) - add_test_case(test_json_parse_from_string) add_test_case(test_json_parse_to_string) diff --git a/tests/promise_test.c b/tests/promise_test.c deleted file mode 100644 index 02f55ec85..000000000 --- a/tests/promise_test.c +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0. - */ - -#include -#include -#include - -#include - -struct promise_test_work { - struct aws_allocator *allocator; - struct aws_promise *promise; - uint64_t work_time; - int error_code; - void *value; - void (*dtor)(void *); -}; - -static void s_promise_test_worker(void *data) { - struct promise_test_work *work = data; - aws_promise_acquire(work->promise); - aws_thread_current_sleep(work->work_time); - if (work->error_code) { - aws_promise_fail(work->promise, work->error_code); - } else { - aws_promise_complete(work->promise, work->value, work->dtor); - } - aws_promise_release(work->promise); -} - -static struct aws_thread s_promise_test_launch_worker(struct promise_test_work *work) { - const struct aws_thread_options *thread_options = aws_default_thread_options(); - AWS_FATAL_ASSERT(thread_options); - struct aws_thread worker_thread; - AWS_FATAL_ASSERT(aws_thread_init(&worker_thread, work->allocator) == AWS_OP_SUCCESS); - AWS_FATAL_ASSERT(aws_thread_launch(&worker_thread, s_promise_test_worker, work, thread_options) == AWS_OP_SUCCESS); - return worker_thread; -} - -struct pmr_payload { - struct aws_allocator *allocator; -}; - -void s_promise_test_free(void *ptr) { - struct pmr_payload *payload = ptr; - aws_mem_release(payload->allocator, payload); -} - -static int s_promise_test_wait_forever(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - struct aws_promise *promise = aws_promise_new(allocator); - ASSERT_NOT_NULL(promise); - - struct pmr_payload *payload = aws_mem_acquire(allocator, 42); - payload->allocator = allocator; - - struct promise_test_work work = { - .allocator = allocator, - .promise = promise, - .work_time = 2 * 1000 * 1000, - .value = payload, - .dtor = s_promise_test_free, - }; - struct aws_thread worker_thread = s_promise_test_launch_worker(&work); - - aws_promise_wait(promise); - ASSERT_SUCCESS(aws_thread_join(&worker_thread)); - aws_promise_release(promise); - - return 0; -} - -AWS_TEST_CASE(promise_test_wait_forever, s_promise_test_wait_forever) - -static int s_promise_test_wait_for_a_bit(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - struct aws_promise *promise = aws_promise_new(allocator); - ASSERT_NOT_NULL(promise); - - struct promise_test_work work = { - .allocator = allocator, - .promise = promise, - .work_time = 3 * 1000 * 1000, - }; - struct aws_thread worker_thread = s_promise_test_launch_worker(&work); - /* wait until the worker finishes, in 500ms intervals */ - while (!aws_promise_wait_for(promise, 500)) - ; - - ASSERT_TRUE(aws_promise_error_code(promise) == 0); - ASSERT_NULL(aws_promise_value(promise)); - - ASSERT_SUCCESS(aws_thread_join(&worker_thread)); - aws_promise_release(promise); - - return 0; -} - -AWS_TEST_CASE(promise_test_wait_for_a_bit, s_promise_test_wait_for_a_bit) - -static int s_promise_test_finish_immediately(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - struct aws_promise *promise = aws_promise_new(allocator); - ASSERT_NOT_NULL(promise); - - struct promise_test_work work = { - .allocator = allocator, - .promise = promise, - .work_time = 0, - }; - struct aws_thread worker_thread = s_promise_test_launch_worker(&work); - aws_promise_wait(promise); - ASSERT_TRUE(aws_promise_error_code(promise) == 0); - ASSERT_NULL(aws_promise_value(promise)); - aws_promise_release(promise); - ASSERT_SUCCESS(aws_thread_join(&worker_thread)); - - return 0; -} - -AWS_TEST_CASE(promise_test_finish_immediately, s_promise_test_finish_immediately) - -static int s_promise_test_finish_before_wait(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - struct aws_promise *promise = aws_promise_new(allocator); - ASSERT_NOT_NULL(promise); - - aws_promise_fail(promise, 1024); - aws_promise_wait(promise); - - ASSERT_TRUE(aws_promise_error_code(promise) == 1024); - ASSERT_NULL(aws_promise_value(promise)); - aws_promise_release(promise); - - return 0; -} - -AWS_TEST_CASE(promise_test_finish_before_wait, s_promise_test_finish_before_wait) - -void s_promise_test_waiter(void *data) { - struct promise_test_work *work = data; - aws_promise_acquire(work->promise); - /* sleep 0.2 seconds */ - aws_thread_current_sleep(1000 * 1000 * 2); - aws_promise_wait(work->promise); - AWS_FATAL_ASSERT(aws_promise_error_code(work->promise) == 0); - aws_promise_release(work->promise); -} - -static int s_promise_test_multiple_waiters(struct aws_allocator *allocator, void *ctx) { - (void)ctx; - struct aws_promise *promise = aws_promise_new(allocator); - ASSERT_NOT_NULL(promise); - - struct promise_test_work work = { - .allocator = allocator, - .promise = promise, - .work_time = 2 * 1000 * 1000, - .value = promise, - }; - struct aws_thread threads[8]; - const struct aws_thread_options *worker_options = aws_default_thread_options(); - for (int idx = 0; idx < AWS_ARRAY_SIZE(threads); ++idx) { - aws_thread_init(&threads[idx], allocator); - aws_thread_launch(&threads[idx], s_promise_test_waiter, &work, worker_options); - } - - aws_thread_current_sleep(1000 * 1000 * 4); - aws_promise_complete(promise, promise, NULL); - aws_promise_release(promise); - - for (int idx = 0; idx < AWS_ARRAY_SIZE(threads); ++idx) { - ASSERT_SUCCESS(aws_thread_join(&threads[idx])); - } - - return 0; -} - -AWS_TEST_CASE(promise_test_multiple_waiters, s_promise_test_multiple_waiters)