inc/c_util/async_retry_wrapper.h (536 lines of code) (raw):

// Copyright (c) Microsoft. All rights reserved. #ifndef ASYNC_RETRY_WRAPPER_H #define ASYNC_RETRY_WRAPPER_H #ifdef __cplusplus #include <cinttypes> #include <cstddef> #include <cstdlib> #else #include <inttypes.h> #include <stddef.h> #include <stdlib.h> #include <stdbool.h> #endif #include "macro_utils/macro_utils.h" #include "c_logging/logger.h" #include "c_pal/gballoc_hl.h" #include "c_pal/gballoc_hl_redirect.h" #include "c_pal/threadapi.h" #include "c_pal/threadpool.h" #include "c_pal/timer.h" #include "c_pal/thandle.h" #include "c_pal/log_critical_and_terminate.h" #include "c_util/async_type_helper.h" #include "umock_c/umock_c_prod.h" #ifdef __cplusplus extern "C" { #endif #define ASYNC_RETRY_WRAPPER_RESULT_VALUES \ ASYNC_RETRY_WRAPPER_OK, \ ASYNC_RETRY_WRAPPER_ERROR, \ ASYNC_RETRY_WRAPPER_INVALID_ARGS, \ ASYNC_RETRY_WRAPPER_COPY_ARG_ERROR, \ ASYNC_RETRY_WRAPPER_CALL_ERROR, \ ASYNC_RETRY_WRAPPER_TIMEOUT \ MU_DEFINE_ENUM(ASYNC_RETRY_WRAPPER_RESULT, ASYNC_RETRY_WRAPPER_RESULT_VALUES) // Maximum back-off for retries is 10 seconds #define ASYNC_RETRY_WRAPPER_MAX_BACKOFF_MS 10000 /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_015: [ ASYNC_RETRY_WRAPPER shall expand async_function_name to the name of the asynchronous retry wrapper around async_function_name. ]*/ #define ASYNC_RETRY_WRAPPER(async_function_name) \ MU_C2A(async_function_name, _async_retry_wrapper) /*Codes_*/ #define ASYNC_RETRY_WRAPPER_WITH_TIMEOUT(async_function_name) \ MU_C2A(async_function_name, _async_retry_wrapper_with_timeout) #define ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name) \ MU_C2(ASYNC_RETRY_WRAPPER(async_function_name), _CONTEXT) #define ASYNC_RETRY_WRAPPER_CALLBACK(async_function_name) \ MU_C3(on_, async_function_name, _complete) #define ASYNC_RETRY_WRAPPER_RETRY_FUNC(async_function_name) \ MU_C2(async_function_name, _do_retry) // Context #define ASYNC_RETRY_WRAPPER_STRUCT_FIELD_ARG(arg_type, arg_name) ASYNC_TYPE_HELPER_STRIP_CONST_TYPE(arg_type) arg_name; #define ASYNC_RETRY_WRAPPER_STRUCT_FIELD_ARG_EX(arg_type, arg_name, ...) ASYNC_RETRY_WRAPPER_STRUCT_FIELD_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_STRUCT_FIELD_ARG_CB(arg_type, arg_name) arg_type user_captured_callback; #define ASYNC_RETRY_WRAPPER_STRUCT_FIELD_ARG_CONTEXT(arg_type, arg_name) arg_type user_captured_callback_context; #define ASYNC_RETRY_WRAPPER_STRUCT_FIELD_PROXY(arg) \ MU_C2B(ASYNC_RETRY_WRAPPER_STRUCT_FIELD_, arg) #define ASYNC_RETRY_WRAPPER_STRUCT_FIELD_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_STRUCT_FIELD_PROXY, __VA_ARGS__) #define GENERATE_ASYNC_RETRY_WRAPPER_CONTEXT(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ typedef struct MU_C2(ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name), _TAG) \ { \ async_handle_type handle; \ THANDLE(THREADPOOL) threadpool; \ double start_time; /*start_time records the time (as returned by timer_global_get_elapsed_ms) when the first call to async_function_name has been made*/ \ uint32_t timeout_ms; /*timeout_ms is the maximum time in milliseconds that the asynchronous function is allowed to run*/ \ unsigned int backoff; \ MU_C2A(ASYNC_RETRY_WRAPPER_STRUCT_FIELD_, in_args) \ } ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name); // Args for callback #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_DECLARATION_ARG(arg_type, arg_name, error_value, ...) , arg_type arg_name #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_DECLARATION_ENUM(arg_type, arg_name, error_value, ...) , arg_type arg_name #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_DECLARATION_PROXY(out_arg) \ MU_C2B(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_DECLARATION_, out_arg) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_DECLARATION_OUT_ARGS(...) \ MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_DECLARATION_PROXY, __VA_ARGS__)) , MU_NOEXPAND())) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_DECLARATION_PROXY(out_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_DECLARATION_, out_args) // Args for calling callback to user #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_ARG(arg_type, arg_name, error_value) , arg_name #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_ENUM(arg_type, arg_name, error_value, ...) ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_ARG(arg_type, arg_name, error_value) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_PROXY(out_arg) \ MU_C2(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_, out_arg) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_OUT_ARGS(...) \ MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_PROXY, __VA_ARGS__)), MU_NOEXPAND())) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_PROXY(out_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_, out_args) // Args for calling callback to user but override the enum value #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_ARG(arg_type, arg_name, error_value) , error_value #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_ENUM_0(arg_type, arg_name, error_value) , error_value #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_ENUM_1(arg_type, arg_name, error_value, timeout_value) , error_value #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_ENUM(arg_type, arg_name, error_value, ...) \ MU_C2C(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_ENUM_, MU_COUNT_ARG(__VA_ARGS__))(arg_type, arg_name, error_value, ##__VA_ARGS__) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_ARG(arg_type, arg_name, error_value) , error_value /*Codes_SRS_ASYNC_RETRY_WRAPPER_02_004: [ If there's no timeout_error_value specified by ENUM(...) macro then the error_value shall be used instead. ]*/ /*Codes_SRS_ASYNC_RETRY_WRAPPER_02_002: [ If there's no timeout_error_value for ENUM(...) argument then the error_value shall be instead passed. ]*/ #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_ENUM_0(arg_type, arg_name, error_value) , error_value #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_ENUM_1(arg_type, arg_name, error_value, timeout_value) , timeout_value /*Codes_SRS_ASYNC_RETRY_WRAPPER_02_002: [ If there's no timeout_error_value for ENUM(...) argument then the error_value shall be instead passed. ]*/ /*Codes_SRS_ASYNC_RETRY_WRAPPER_02_004: [ If there's no timeout_error_value specified by ENUM(...) macro then the error_value shall be used instead. ]*/ #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_ENUM(arg_type, arg_name, error_value, ...) \ MU_C2C(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_ENUM_, MU_COUNT_ARG(__VA_ARGS__))(arg_type, arg_name, error_value, ##__VA_ARGS__) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_PROXY(out_arg) \ MU_C2B(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_, out_arg) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_PROXY(out_arg) \ MU_C2B(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_, out_arg) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_ERROR_OUT_ARGS(...) \ MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_ERROR_PROXY, __VA_ARGS__)), MU_NOEXPAND())) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_ERROR_PROXY(out_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_ERROR_, out_args) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_TIMEOUT_PROXY(out_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_TIMEOUT_, out_args) #define ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_TIMEOUT_OUT_ARGS(...) \ MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALLBACK_ARG_CALL_WITH_TIMEOUT_PROXY, __VA_ARGS__)), MU_NOEXPAND())) // Retry test (sync) #define ASYNC_RETRY_WRAPPER_RETRY_CONDITION_SYNC(value) || temp_async_function_result == value #define ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_RETRY_ON_SYNC(...) \ false MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_RETRY_CONDITION_SYNC, __VA_ARGS__)), MU_NOEXPAND())) // Retry test (async) #define ASYNC_RETRY_WRAPPER_RETRY_CONDITION_ASYNC(value) || enum_to_check == value #define ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_RETRY_ON_ASYNC(...) \ false MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_RETRY_CONDITION_ASYNC, __VA_ARGS__)),MU_NOEXPAND())) #define ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_PROXY(retry_enums) \ MU_C2A(ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_, retry_enums) #define ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_OUT_ARGS_ENUM(arg_type, arg_name, error_value, ...) \ int enum_to_check_already_defined_only_one_ENUM_allowed_in_out_args; \ (void)enum_to_check_already_defined_only_one_ENUM_allowed_in_out_args; \ arg_type enum_to_check = arg_name; #define ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_OUT_ARGS_ARG(arg_type, arg_name, error_value) #define ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_OUT_ARG_PROXY(arg) \ MU_C2(ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_OUT_ARGS_, arg) #define ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_OUT_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_OUT_ARG_PROXY, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_PROXY(out_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_, out_args) #define ASYNC_RETRY_WRAPPER_COUNT_RETRY_ON_ASYNC(...) MU_COUNT_ARG(__VA_ARGS__) #define ASYNC_RETRY_WRAPPER_HAS_ASYNC_RETRY(retry_enums) \ MU_C2A(ASYNC_RETRY_WRAPPER_COUNT_, retry_enums) // In args #define ASYNC_RETRY_WRAPPER_ARG_ARG(arg_type, arg_name) , arg_type, arg_name #define ASYNC_RETRY_WRAPPER_ARG_ARG_EX(arg_type, arg_name, ...) ASYNC_RETRY_WRAPPER_ARG_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_ARG_CB(arg_type, arg_name) ASYNC_RETRY_WRAPPER_ARG_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_ARG_CONTEXT(arg_type, arg_name) ASYNC_RETRY_WRAPPER_ARG_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_PROXY(arg) MU_EXPAND(MU_NOEXPAND(MU_C2(ASYNC_RETRY_WRAPPER_ARG_, arg))) #define ASYNC_RETRY_WRAPPER_ARGS_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_ARG_PROXY, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_ARGS_IN_DECLARATION(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_ARGS_, in_args) // In args for static (no comma after type) #define ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG(arg_type, arg_name) , arg_type arg_name #define ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG_EX(arg_type, arg_name, ...) ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG_CB(arg_type, arg_name) ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG_CONTEXT(arg_type, arg_name) ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_PROXY(arg) MU_EXPAND(MU_NOEXPAND(MU_C2(ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_, arg))) #define ASYNC_RETRY_WRAPPER_ARGS_NO_COMMA_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_ARG_NO_COMMA_PROXY, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_ARGS_IN_STATIC_DECLARATION(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_ARGS_NO_COMMA_, in_args) // In args for calling static (forward all args as-is) #define ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG(arg_type, arg_name) , arg_name #define ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG_EX(arg_type, arg_name, ...) ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG_CB(arg_type, arg_name) ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG_CONTEXT(arg_type, arg_name) ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_PROXY(arg) MU_EXPAND(MU_NOEXPAND(MU_C2(ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_, arg))) #define ASYNC_RETRY_WRAPPER_ARGS_MAKE_CALL_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_ARG_MAKE_CALL_PROXY, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_ARGS_MAKE_STATIC_CALL(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_ARGS_MAKE_CALL_, in_args) // In args for function call #define ASYNC_RETRY_WRAPPER_CALL_ARG_ARG(arg_type, arg_name) , retry_context->arg_name #define ASYNC_RETRY_WRAPPER_CALL_ARG_ARG_EX(arg_type, arg_name, ...) ASYNC_RETRY_WRAPPER_CALL_ARG_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_CALL_ARG_ARG_CB(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_CALL_ARG_ARG_CONTEXT(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_CALL_ARG_PROXY(arg) MU_C2(ASYNC_RETRY_WRAPPER_CALL_ARG_, arg) #define ASYNC_RETRY_WRAPPER_CALL_ARGS_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALL_ARG_PROXY, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_ARGS_IN_CALL(args) \ MU_C2A(ASYNC_RETRY_WRAPPER_CALL_ARGS_, args) // In arg validation #define ASYNC_RETRY_WRAPPER_CHECK_ARG_ARG_CB(arg_type, arg_name) \ else if (arg_name == NULL) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_004: [ If the parameter specified in ARG_CB(type, name) is NULL, the asynchronous retry wrapper shall fail and return ASYNC_RETRY_WRAPPER_INVALID_ARGS. ]*/ \ LogError("NULL " MU_TOSTRING(arg_name) " of type " MU_TOSTRING(arg_type)); \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_INVALID_ARGS; \ } #define ASYNC_RETRY_WRAPPER_CHECK_ARG_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_CHECK_ARG_ARG_EX(arg_type, arg_name, ...) #define ASYNC_RETRY_WRAPPER_CHECK_ARG_ARG_CONTEXT(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_CHECK_ARG_PROXY(in_arg) \ MU_C2B(ASYNC_RETRY_WRAPPER_CHECK_ARG_, in_arg) #define ASYNC_RETRY_WRAPPER_CHECK_IN_ARGS(...) \ MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CHECK_ARG_PROXY, __VA_ARGS__)),MU_NOEXPAND())) #define ASYNC_RETRY_WRAPPER_CHECK_ARG_PTRS(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_CHECK_, in_args) // Copy in args for retry #define ASYNC_RETRY_WRAPPER_CUSTOM_HANDLER_EX_PASS_ARG_FROM_CONTEXT(arg) , retry_context->arg #define ASYNC_RETRY_WRAPPER_CUSTOM_HANDLER_EX_PASS_ARGS_FROM_CONTEXT(...) \ MU_FOR_EACH_1B(ASYNC_RETRY_WRAPPER_CUSTOM_HANDLER_EX_PASS_ARG_FROM_CONTEXT, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_ARG(arg_type, arg_name) \ bool MU_C2C(arg_name, _free_needed) = false; \ if (!error_copying_args) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_008: [ If an argument in IN_ARGS(...) has a type that does not define ASYNC_TYPE_HELPER_USE_ASSIGN_COPY_{type} as 1, then the asynchronous retry wrapper shall copy the argument by calling async_retry_wrapper_{type}_copy. ]*/ \ if (ASYNC_TYPE_HELPER_COPY_HANDLER(arg_type)(&retry_context->arg_name, arg_name) != 0) \ { \ LogError("Copying argument " MU_TOSTRING(arg_name) " of type " MU_TOSTRING(arg_type) " failed"); \ error_copying_args = true; \ } \ else \ { \ MU_C2C(arg_name, _free_needed) = true; \ } \ } /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_035: [ copy_function shall be used as a function with the following declaration: ]*/ #define ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_ARG_EX(arg_type, arg_name, copy_function, free_function, ...) \ bool MU_C2C(arg_name, _free_needed) = false; \ if (!error_copying_args) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_007: [ If an argument in IN_ARGS(...) is specified with ARG_EX(type, name, copy_function, free_function, ...) then the asynchronous retry wrapper shall call copy_function and pass the ... arguments in order to copy to the context. ]*/ \ if (copy_function(&retry_context->arg_name, arg_name, __VA_ARGS__) != 0) \ { \ LogError("Copying argument " MU_TOSTRING(arg_name) " of type " MU_TOSTRING(arg_type) " failed"); \ error_copying_args = true; \ } \ else \ { \ MU_C2C(arg_name, _free_needed) = true; \ } \ } #define ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_ARG_CB(arg_type, arg_name) ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_ARG(arg_type, arg_name) _error_undefined_variable_ = "Invalid IN_ARG marked as CB but using a custom copy"; #define ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_ARG_CONTEXT(arg_type, arg_name) ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_ARG(arg_type, arg_name) _error_undefined_variable_ = "Invalid IN_ARG marked as CONTEXT but using a custom copy"; #define ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_ARG(arg_type, arg_name) ASYNC_TYPE_HELPER_DO_NOT_USE_ASSIGN_COPY(arg_type) #define ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_ARG_EX(arg_type, arg_name, ...) ASYNC_TYPE_HELPER_DO_NOT_USE_ASSIGN_COPY(arg_type) #define ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_ARG_CB(arg_type, arg_name) 0 #define ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_ARG_CONTEXT(arg_type, arg_name) 0 /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_009: [ Otherwise, the asynchronous retry wrapper shall copy the argument to the context via assignment. ]*/ #define ASYNC_RETRY_WRAPPER_EXECUTE_DEFAULT_COPY_ARG(arg_type, arg_name) \ retry_context->arg_name = arg_name; #define ASYNC_RETRY_WRAPPER_EXECUTE_DEFAULT_COPY_ARG_EX(arg_type, arg_name, ...) ASYNC_RETRY_WRAPPER_EXECUTE_DEFAULT_COPY_ARG(arg_type, arg_name) #define ASYNC_RETRY_WRAPPER_EXECUTE_DEFAULT_COPY_ARG_CB(arg_type, arg_name) \ retry_context->user_captured_callback = arg_name; #define ASYNC_RETRY_WRAPPER_EXECUTE_DEFAULT_COPY_ARG_CONTEXT(arg_type, arg_name) \ retry_context->user_captured_callback_context = arg_name; #define ASYNC_RETRY_WRAPPER_CALL_COPY_ARG_PROXY(in_arg) \ MU_EXPAND(MU_IF(MU_C2B(ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_, in_arg), MU_NOEXPAND(MU_C2B(ASYNC_RETRY_WRAPPER_EXECUTE_CUSTOM_COPY_, in_arg)), MU_NOEXPAND(MU_C2B(ASYNC_RETRY_WRAPPER_EXECUTE_DEFAULT_COPY_, in_arg)))) #define ASYNC_RETRY_WRAPPER_COPY_ARGS_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALL_COPY_ARG_PROXY, __VA_ARGS__) /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_006: [ The asynchronous retry wrapper shall copy each argument from IN_ARGS(...) to the allocated context. ]*/ #define ASYNC_RETRY_WRAPPER_COPY_ARGS_PROXY(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_COPY_ARGS_, in_args) // Free in args on failure (check _free_needed because argument may not have been copied) #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FAILED_ARG(arg_type, arg_name) \ if (MU_C2C(arg_name, _free_needed)) \ { \ ASYNC_TYPE_HELPER_FREE_HANDLER(arg_type)(retry_context->arg_name); \ } /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_036: [ free_function shall be used as a function with the following declaration: ]*/ #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FAILED_ARG_EX(arg_type, arg_name, copy_function, free_function, ...) \ if (MU_C2C(arg_name, _free_needed)) \ { \ free_function(retry_context->arg_name, __VA_ARGS__); \ } #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FAILED_ARG_CB(arg_type, arg_name) _error_undefined_variable_ = "Invalid IN_ARG marked as CB but using a custom free"; #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FAILED_ARG_CONTEXT(arg_type, arg_name) _error_undefined_variable_ = "Invalid IN_ARG marked as CONTEXT but using a custom free"; #define ASYNC_RETRY_WRAPPER_CALL_FREE_FAILED_IN_ARG(in_arg) \ MU_EXPAND(MU_IF(MU_C2B(ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_, in_arg), MU_NOEXPAND(MU_C2B(ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FAILED_,in_arg)), MU_NOEXPAND())) \ #define ASYNC_RETRY_WRAPPER_FREE_FAILED_IN_ARGS(...) \ MU_FOR_EACH_1(ASYNC_RETRY_WRAPPER_CALL_FREE_FAILED_IN_ARG, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_FREE_FAILED_PROXY(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_FREE_FAILED_, in_args) // Free in args for normal cleanup, cleanup all fields /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_020: [ on_{async_function_name}_complete shall free each argument in IN_ARGS(...) that has a type that does not define ASYNC_TYPE_HELPER_USE_ASSIGN_COPY_{type} as 1 by calling async_retry_wrapper_{type}_free. ]*/ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_027: [ {async_function_name}_do_retry shall free each argument in IN_ARGS(...) that has a type that does not define ASYNC_TYPE_HELPER_USE_ASSIGN_COPY_{type} as 1 by calling async_retry_wrapper_{type}_free. ]*/ #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FIELD_ARG(arg_type, arg_name) \ ASYNC_TYPE_HELPER_FREE_HANDLER(arg_type)(retry_context->arg_name); /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_019: [ on_{async_function_name}_complete shall free each argument in IN_ARGS(...) that is specified with ARG_EX(type, name, copy_function, free_function, ...) by calling free_function and passing the ... arguments. ]*/ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_026: [ {async_function_name}_do_retry shall free each argument in IN_ARGS(...) that is specified with ARG_EX(type, name, copy_function, free_function, ...) by calling free_function and passing the ... arguments. ]*/ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_036: [ free_function shall be used as a function with the following declaration: ]*/ #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FIELD_ARG_EX(arg_type, arg_name, copy_function, free_function, ...) \ free_function(retry_context->arg_name ASYNC_RETRY_WRAPPER_CUSTOM_HANDLER_EX_PASS_ARGS_FROM_CONTEXT(__VA_ARGS__)); #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FIELD_ARG_CB(arg_type, arg_name) _error_undefined_variable_ = "Invalid IN_ARG marked as CB but using a custom free"; #define ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FIELD_ARG_CONTEXT(arg_type, arg_name) _error_undefined_variable_ = "Invalid IN_ARG marked as CONTEXT but using a custom free"; #define ASYNC_RETRY_WRAPPER_CALL_FREE_FIELDS_IN_ARG(in_arg) \ MU_EXPAND(MU_IF(MU_C2B(ASYNC_RETRY_WRAPPER_DO_NOT_USE_ASSIGN_COPY_, in_arg), MU_NOEXPAND(MU_C2B(ASYNC_RETRY_WRAPPER_CUSTOM_FREE_FIELD_, in_arg)), MU_NOEXPAND())) \ #define ASYNC_RETRY_WRAPPER_FREE_FIELDS_IN_ARGS(...) \ MU_FOR_EACH_1A(ASYNC_RETRY_WRAPPER_CALL_FREE_FIELDS_IN_ARG, __VA_ARGS__) #define ASYNC_RETRY_WRAPPER_FREE_FIELDS_PROXY(in_args) \ MU_C2A(ASYNC_RETRY_WRAPPER_FREE_FIELDS_, in_args) // Execute retry /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_022: [ DEFINE_ASYNC_RETRY_WRAPPER shall generate a function to retry the asynchronous function with the following declaration: ]*/ #define GENERATE_ASYNC_RETRY_WRAPPER_EXECUTE_RETRY_FUNCTION(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ static void ASYNC_RETRY_WRAPPER_RETRY_FUNC(async_function_name)(void* context) \ { \ if (context == NULL) \ { \ LogCriticalAndTerminate("NULL context for " MU_TOSTRING(async_function_name) " do retry function"); \ } \ else \ { \ ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name)* retry_context = context; \ return_type temp_async_function_result; \ bool timed_out = false; \ do \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_023: [ {async_function_name}_do_retry shall call the function async_function_name, passing the in_args that have been copied to the retry context with the exception of ARG_CB(...) and ARG_CONTEXT(...) which are instead passed as the generated callback handler and the allocated context. ]*/ \ temp_async_function_result = async_function_name(retry_context->handle ASYNC_RETRY_WRAPPER_ARGS_IN_CALL(in_args), ASYNC_RETRY_WRAPPER_CALLBACK(async_function_name), retry_context); \ if (ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_PROXY(retry_sync_enums)) \ { \ if (retry_context->timeout_ms < UINT32_MAX) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_02_001: [ Before each retry of the function, if timeout_ms milliseconds have elapsed since the initial call to ASYNC_RETRY_WRAPPER(async_function_name) then {async_function_name}_async_retry_wrapper_with_timeout shall call shall call the user callback specified in ARG_CB(...), passing the context from ARG_CONTEXT(...), the error_value from all of the ARG(type, name, error_value)'s in out_args. and the timeout_error_value for the ENUM(...) argument. ]*/ \ double elapsed_time_ms = timer_global_get_elapsed_ms() - retry_context->start_time; \ LogInfo("elapsed_time_ms=%lf + retry_context->backoff=%" PRIu32 " > retry_context->timeout_ms=%" PRIu32 "", \ elapsed_time_ms, retry_context->backoff, retry_context->timeout_ms); \ if (elapsed_time_ms + retry_context->backoff > retry_context->timeout_ms) \ { \ timed_out = true; \ LogError("Retries for " MU_TOSTRING(async_function_name) " timed out after %lf ms (including %" PRIu32 " ms of backoff) (timeout time was %" PRIu32 " ms)", \ elapsed_time_ms, retry_context->backoff, retry_context->timeout_ms); \ break; \ } \ } \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_045: [ Before each retry of the function, {async_function_name}_do_retry shall yield execution by a call to ThreadAPI_Sleep. ]*/ \ ThreadAPI_Sleep(retry_context->backoff); \ retry_context->backoff *= 2; \ if (retry_context->backoff > ASYNC_RETRY_WRAPPER_MAX_BACKOFF_MS) \ { \ retry_context->backoff = ASYNC_RETRY_WRAPPER_MAX_BACKOFF_MS; \ } \ } \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_040: [ While async_function_name returns one of the values from RETRY_ON_SYNC(...), it shall be called again in a loop. ]*/ \ } while (ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_PROXY(retry_sync_enums)); \ if (timed_out) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_041: [ If async_function_name returns a value other than expected_return then: ]*/ \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_025: [ {async_function_name}_do_retry shall call the user callback specified in ARG_CB(...), passing the context from ARG_CONTEXT(...), and the error_value from all of the ARG(type, name, error_value)'s in out_args. ]*/ \ retry_context->user_captured_callback(retry_context->user_captured_callback_context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_TIMEOUT_PROXY(out_args)); \ ASYNC_RETRY_WRAPPER_FREE_FIELDS_PROXY(in_args); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_11_001: [ on_{async_function_name}_complete shall assign the threadpool to NULL. ]*/ \ THANDLE_ASSIGN(THREADPOOL)(&retry_context->threadpool, NULL); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_028: [ {async_function_name}_do_retry shall free the allocated context. ]*/ \ free(retry_context); \ } \ else \ { \ if (temp_async_function_result != expected_return) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_041: [ If async_function_name returns a value other than expected_return then: ]*/ \ LogError(MU_TOSTRING(async_function_name) " failed during retry"); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_025: [ {async_function_name}_do_retry shall call the user callback specified in ARG_CB(...), passing the context from ARG_CONTEXT(...), and the error_value from all of the ARG(type, name, error_value)'s in out_args. ]*/ \ retry_context->user_captured_callback(retry_context->user_captured_callback_context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_ERROR_PROXY(out_args)); \ ASYNC_RETRY_WRAPPER_FREE_FIELDS_PROXY(in_args); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_11_001: [ on_{async_function_name}_complete shall assign the threadpool to NULL. ]*/ \ THANDLE_ASSIGN(THREADPOOL)(&retry_context->threadpool, NULL); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_028: [ {async_function_name}_do_retry shall free the allocated context. ]*/ \ free(retry_context); \ } \ else \ { \ /* Retry attempted, callback will be called */ \ } \ } \ } \ } // Callback implementation #define DECLARE_ASYNC_RETRY_WRAPPER_CALLBACK(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ static void ASYNC_RETRY_WRAPPER_CALLBACK(async_function_name)(void* context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_DECLARATION_PROXY(out_args)); /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_014: [ DEFINE_ASYNC_RETRY_WRAPPER shall generate a callback to be passed to the asynchronous function with the following declaration: ]*/ #define GENERATE_ASYNC_RETRY_WRAPPER_CALLBACK(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ static void ASYNC_RETRY_WRAPPER_CALLBACK(async_function_name)(void* context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_DECLARATION_PROXY(out_args)) \ { \ if (context == NULL) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_016: [ If context is NULL, on_{async_function_name}_complete shall terminate the process. ]*/ \ LogCriticalAndTerminate("NULL context for " MU_TOSTRING(async_function_name) " callback"); \ } \ else \ { \ ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name)* retry_context = context; \ ASYNC_RETRY_WRAPPER_GET_ENUM_TO_CHECK_PROXY(out_args) \ if (ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_PROXY(retry_async_enums)) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_02_003: [ If the out_arg specified as ENUM(type, name, error_value) has one of the values from RETRY_ON_ASYNC(...) and the the time measured from the initial call to ASYNC_RETRY_WRAPPER() exceeded timeout_ms then the user callback specified in ARG_CB(...) shall be called, passing the context from ARG_CONTEXT(...), and the out_args as they were received by this callback handler with the exception of the ENUM(...) which will have the value specified for timeout_error_value. ]*/ \ bool timed_out = false; \ if (retry_context->timeout_ms < UINT32_MAX) \ { \ double elapsed_time_ms = timer_global_get_elapsed_ms() - retry_context->start_time; \ if (elapsed_time_ms + retry_context->backoff > retry_context->timeout_ms) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_049: [ Before each retry of the function, If timeout_ms milliseconds have elapsed then {async_function_name}_async_retry_wrapper_with_timeout shall fail and return ASYNC_RETRY_WRAPPER_TIMEOUT. ]*/ \ LogError("Retries for " MU_TOSTRING(async_function_name) " timed out after %lf ms (including %" PRIu32 " ms of backoff) (timeout time was %" PRIu32 " ms)", \ elapsed_time_ms, retry_context->backoff, retry_context->timeout_ms); \ timed_out = true; \ /* Codes_SRS_ASYNC_RETRY_WRAPPER_01_001: [ If any error occurs, on_{async_function_name}_complete shall call the user callback specified in ARG_CB(...), passing the context from ARG_CONTEXT(...), and the error_value from all of the ARG(type, name, error_value)'s in out_args. ]*/ \ retry_context->user_captured_callback(retry_context->user_captured_callback_context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_TIMEOUT_PROXY(out_args));\ ASYNC_RETRY_WRAPPER_FREE_FIELDS_PROXY(in_args); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_11_001: [ on_{async_function_name}_complete shall assign the threadpool to NULL. ]*/ \ THANDLE_ASSIGN(THREADPOOL)(&retry_context->threadpool, NULL); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_021: [ on_{async_function_name}_complete shall free the allocated context. ]*/ \ free(retry_context); \ } \ } \ if(!timed_out) \ { \ LogVerbose("Scheduling retry of " MU_TOSTRING(async_function_name)); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_017: [ If the out_arg specified as ENUM(type, name, error_value) has one of the values from RETRY_ON(...) then on_{async_function_name}_complete shall call threadpool_schedule_work with {async_function_name}_do_retry as the work_function to retry the asynchronous call and return. ]*/ \ if (threadpool_schedule_work(retry_context->threadpool, ASYNC_RETRY_WRAPPER_RETRY_FUNC(async_function_name), retry_context) != 0) \ { \ LogError("threadpool_schedule_work failed for " MU_TOSTRING(async_function_name)); \ /* Codes_SRS_ASYNC_RETRY_WRAPPER_01_001: [ If any error occurs, on_{async_function_name}_complete shall call the user callback specified in ARG_CB(...), passing the context from ARG_CONTEXT(...), and the error_value from all of the ARG(type, name, error_value)'s in out_args. ]*/ \ retry_context->user_captured_callback(retry_context->user_captured_callback_context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_WITH_ERROR_PROXY(out_args)); \ ASYNC_RETRY_WRAPPER_FREE_FIELDS_PROXY(in_args); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_11_001: [ on_{async_function_name}_complete shall assign the threadpool to NULL. ]*/ \ THANDLE_ASSIGN(THREADPOOL)(&retry_context->threadpool, NULL); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_021: [ on_{async_function_name}_complete shall free the allocated context. ]*/ \ free(retry_context); \ } \ else \ { \ /* All OK, will execute on threadpool callback */ \ } \ } \ } \ else \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_018: [ on_{async_function_name}_complete shall call the user callback specified in ARG_CB(...), passing the context from ARG_CONTEXT(...), and the out_args as they were received by this callback handler. ]*/ \ retry_context->user_captured_callback(retry_context->user_captured_callback_context ASYNC_RETRY_WRAPPER_CALLBACK_ARGS_FOR_CALL_PROXY(out_args)); \ ASYNC_RETRY_WRAPPER_FREE_FIELDS_PROXY(in_args); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_11_001: [ on_{async_function_name}_complete shall assign the threadpool to NULL. ]*/ \ THANDLE_ASSIGN(THREADPOOL)(&retry_context->threadpool, NULL); \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_021: [ on_{async_function_name}_complete shall free the allocated context. ]*/ \ free(retry_context); \ } \ } \ } // Call implementation #define GENERATE_ASYNC_RETRY_WRAPPER_FUNCTION(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ static ASYNC_RETRY_WRAPPER_RESULT MU_C2(ASYNC_RETRY_WRAPPER(async_function_name), _internal)(async_handle_type async_handle, THANDLE(THREADPOOL) threadpool, uint32_t timeout_ms ASYNC_RETRY_WRAPPER_ARGS_IN_STATIC_DECLARATION(in_args), return_type* async_function_result) \ { \ ASYNC_RETRY_WRAPPER_RESULT async_retry_wrapper_result; \ if (async_handle == NULL) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_003: [ If async_handle is NULL, the asynchronous retry wrapper shall fail and return ASYNC_RETRY_WRAPPER_INVALID_ARGS. ]*/ \ LogError("NULL " MU_TOSTRING(async_handle_type) " async_handle for " MU_TOSTRING(async_function_name)); \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_INVALID_ARGS; \ } \ else if (async_function_result == NULL) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_042: [ If async_function_result is NULL, the asynchronous retry wrapper shall fail and return ASYNC_RETRY_WRAPPER_INVALID_ARGS. ]*/ \ LogError("NULL " MU_TOSTRING(return_type) "* async_function_result for " MU_TOSTRING(async_function_name)); \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_INVALID_ARGS; \ } \ else if (threadpool == NULL) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_037: [ If threadpool is NULL, the asynchronous retry wrapper shall fail and return ASYNC_RETRY_WRAPPER_INVALID_ARGS. ]*/ \ LogError("NULL THANDLE(THREADPOOL) threadpool for " MU_TOSTRING(async_function_name)); \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_INVALID_ARGS; \ } \ ASYNC_RETRY_WRAPPER_CHECK_ARG_PTRS(in_args) \ else \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_005: [ The asynchronous retry wrapper shall allocate a context for the asynchronous call. ]*/ \ ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name)* retry_context = malloc(sizeof(ASYNC_RETRY_WRAPPER_CONTEXT(async_function_name))); \ if (retry_context == NULL) \ { \ LogError("Failed to create context for " MU_TOSTRING(async_function_name)); \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_ERROR; \ } \ else \ { \ if (timeout_ms < UINT32_MAX) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_048: [ {async_function_name}_async_retry_wrapper_with_timeout shall get the current time by calling timer_global_get_elapsed_ms. ]*/ \ retry_context->start_time = timer_global_get_elapsed_ms(); \ } \ retry_context->timeout_ms = timeout_ms; \ bool error_copying_args = false; \ ASYNC_RETRY_WRAPPER_COPY_ARGS_PROXY(in_args) \ if (error_copying_args) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_010: [ If there are any failures when copying the input arguments then the asynchronous retry wrapper shall fail and return ASYNC_RETRY_WRAPPER_COPY_ARG_ERROR. ]*/ \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_COPY_ARG_ERROR; \ } \ else \ { \ retry_context->handle = async_handle; \ THANDLE_INITIALIZE(THREADPOOL)(&retry_context->threadpool, threadpool); \ return_type temp_async_function_result; \ retry_context->backoff = 1; \ bool timed_out = false; \ do \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_011: [ The asynchronous retry wrapper shall call the function async_function_name, passing the in_args that have been copied to the retry context with the exception of ARG_CB(...) and ARG_CONTEXT(...) which are instead passed as the generated callback handler and the allocated context. ]*/ \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_043: [ The asynchronous retry wrapper shall store the result of async_function_name in async_function_result. ]*/ \ temp_async_function_result = async_function_name(retry_context->handle ASYNC_RETRY_WRAPPER_ARGS_IN_CALL(in_args), ASYNC_RETRY_WRAPPER_CALLBACK(async_function_name), retry_context); \ if (ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_PROXY(retry_sync_enums)) \ { \ if (retry_context->timeout_ms < UINT32_MAX) \ { \ double elapsed_time_ms = timer_global_get_elapsed_ms() - retry_context->start_time; \ if (elapsed_time_ms + retry_context->backoff > retry_context->timeout_ms) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_049: [ Before each retry of the function, If timeout_ms milliseconds have elapsed then {async_function_name}_async_retry_wrapper_with_timeout shall fail and return ASYNC_RETRY_WRAPPER_TIMEOUT. ]*/ \ timed_out = true; \ LogWarning("Retries for " MU_TOSTRING(async_function_name) " timed out after %lf ms (including %" PRIu32 " ms of backoff) (timeout time was %" PRIu32 " ms)", \ elapsed_time_ms, retry_context->backoff, retry_context->timeout_ms); \ break; \ } \ } \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_044: [ Before each retry of the function, the asynchronous retry wrapper shall yield execution by a call to ThreadAPI_Sleep. ]*/ \ ThreadAPI_Sleep(retry_context->backoff); \ retry_context->backoff *= 2; \ if (retry_context->backoff > ASYNC_RETRY_WRAPPER_MAX_BACKOFF_MS) \ { \ retry_context->backoff = ASYNC_RETRY_WRAPPER_MAX_BACKOFF_MS; \ } \ } \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_038: [ While async_function_name returns one of the values from RETRY_ON_SYNC(...), it shall be called again in a loop. ]*/ \ } while (ASYNC_RETRY_WRAPPER_RETRY_CONDITIONS_PROXY(retry_sync_enums)); \ *async_function_result = temp_async_function_result; \ if (timed_out) \ { \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_TIMEOUT; \ } \ else \ { \ if (temp_async_function_result != expected_return) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_039: [ If async_function_name returns a value other than expected_return then the asynchronous retry wrapper shall fail and return ASYNC_RETRY_WRAPPER_CALL_ERROR. ]*/ \ LogInfo(MU_TOSTRING(async_function_name) " failed"); \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_CALL_ERROR; \ } \ else \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_013: [ On success, the asynchronous retry wrapper shall return ASYNC_RETRY_WRAPPER_OK. ]*/ \ async_retry_wrapper_result = ASYNC_RETRY_WRAPPER_OK; \ goto all_ok; \ } \ } \ THANDLE_ASSIGN(THREADPOOL)(&retry_context->threadpool, NULL); \ } \ ASYNC_RETRY_WRAPPER_FREE_FAILED_PROXY(in_args); \ free(retry_context); \ } \ } \ all_ok: \ return async_retry_wrapper_result; \ } \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_002: [ DEFINE_ASYNC_RETRY_WRAPPER shall generate an asynchronous retry wrapper with the declaration: ]*/ \ IMPLEMENT_MOCKABLE_FUNCTION(, ASYNC_RETRY_WRAPPER_RESULT, ASYNC_RETRY_WRAPPER(async_function_name), async_handle_type, async_handle, THANDLE(THREADPOOL), threadpool ASYNC_RETRY_WRAPPER_ARGS_IN_DECLARATION(in_args), return_type*, async_function_result) \ { \ return MU_C2(ASYNC_RETRY_WRAPPER(async_function_name), _internal)(async_handle, threadpool, UINT32_MAX ASYNC_RETRY_WRAPPER_ARGS_MAKE_STATIC_CALL(in_args), async_function_result); \ } \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_047: [ DEFINE_ASYNC_RETRY_WRAPPER shall generate an asynchronous retry wrapper that supports timeouts with the declaration: ]*/ \ IMPLEMENT_MOCKABLE_FUNCTION(, ASYNC_RETRY_WRAPPER_RESULT, ASYNC_RETRY_WRAPPER_WITH_TIMEOUT(async_function_name), async_handle_type, async_handle, THANDLE(THREADPOOL), threadpool, uint32_t, timeout_ms ASYNC_RETRY_WRAPPER_ARGS_IN_DECLARATION(in_args), return_type*, async_function_result) \ { \ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_050: [ {async_function_name}_async_retry_wrapper_with_timeout shall otherwise behave the same as {async_function_name}_async_retry_wrapper. ]*/ \ return MU_C2(ASYNC_RETRY_WRAPPER(async_function_name), _internal)(async_handle, threadpool, timeout_ms ASYNC_RETRY_WRAPPER_ARGS_MAKE_STATIC_CALL(in_args), async_function_result); \ } /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_001: [ DECLARE_ASYNC_RETRY_WRAPPER shall expand to the function declaration: ]*/ /*Codes_SRS_ASYNC_RETRY_WRAPPER_42_046: [ DECLARE_ASYNC_RETRY_WRAPPER shall also expand to the function declaration: ]*/ #define DECLARE_ASYNC_RETRY_WRAPPER(async_handle_type, async_function_name, return_type, in_args) \ MOCKABLE_FUNCTION(, ASYNC_RETRY_WRAPPER_RESULT, ASYNC_RETRY_WRAPPER(async_function_name), async_handle_type, async_handle, THANDLE(THREADPOOL), threadpool ASYNC_RETRY_WRAPPER_ARGS_IN_DECLARATION(in_args), return_type*, async_function_result) \ MOCKABLE_FUNCTION(, ASYNC_RETRY_WRAPPER_RESULT, ASYNC_RETRY_WRAPPER_WITH_TIMEOUT(async_function_name), async_handle_type, async_handle, THANDLE(THREADPOOL), threadpool, uint32_t, timeout_ms ASYNC_RETRY_WRAPPER_ARGS_IN_DECLARATION(in_args), return_type*, async_function_result) \ #define DEFINE_ASYNC_RETRY_WRAPPER(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ GENERATE_ASYNC_RETRY_WRAPPER_CONTEXT(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ DECLARE_ASYNC_RETRY_WRAPPER_CALLBACK(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ GENERATE_ASYNC_RETRY_WRAPPER_EXECUTE_RETRY_FUNCTION(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ GENERATE_ASYNC_RETRY_WRAPPER_CALLBACK(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) \ GENERATE_ASYNC_RETRY_WRAPPER_FUNCTION(async_handle_type, async_function_name, return_type, expected_return, in_args, out_args, retry_async_enums, retry_sync_enums) #ifdef __cplusplus } #endif #endif // ASYNC_RETRY_WRAPPER_H