inc/c_util/sync_wrapper.h (224 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
#ifndef SYNC_WRAPPER_H
#define SYNC_WRAPPER_H
#ifdef __cplusplus
#include <cstddef>
#include <cstdlib>
#include <cstdint>
#else
#include <stddef.h>
#include <stdlib.h>
#include <stdint.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/interlocked.h"
#include "c_pal/interlocked_hl.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 SYNC_WRAPPER_RESULT_VALUES \
SYNC_WRAPPER_OK, \
SYNC_WRAPPER_INVALID_ARGS, \
SYNC_WRAPPER_CALLBACK_COPY_ARG_ERROR, \
SYNC_WRAPPER_CALL_ERROR, \
SYNC_WRAPPER_OTHER_ERROR \
MU_DEFINE_ENUM(SYNC_WRAPPER_RESULT, SYNC_WRAPPER_RESULT_VALUES)
#define SYNC_WRAPPER_TEST_1 0
#define SYNC_WRAPPER_DO_NOT_USE_ASSIGN_COPY_OUT_ARG(arg_type, arg_name) ASYNC_TYPE_HELPER_DO_NOT_USE_ASSIGN_COPY(arg_type)
#define SYNC_WRAPPER_DO_NOT_USE_ASSIGN_COPY_OUT_ARG_EX(arg_type, arg_name, ...) SYNC_WRAPPER_DO_NOT_USE_ASSIGN_COPY_OUT_ARG(arg_type, arg_name)
// args in signature
#define SYNC_WRAPPER_ARG_IN_DECLARATION(arg_type, arg_name) , arg_type, arg_name
#define SYNC_WRAPPER_ARGS_IN_ARGS(...) \
MU_FOR_EACH_2(SYNC_WRAPPER_ARG_IN_DECLARATION, __VA_ARGS__)
#define SYNC_WRAPPER_ARGS_IN_DECLARATION(args) \
MU_C2A(SYNC_WRAPPER_ARGS_, args)
// const args in signature
#define SYNC_WRAPPER_CONST_ARG_IN_DECLARATION_OUT_ARG(arg_type, arg_name) , ASYNC_TYPE_HELPER_ADD_CONST_TYPE(arg_type) arg_name
#define SYNC_WRAPPER_CONST_ARG_IN_DECLARATION_OUT_ARG_EX(arg_type, arg_name, ...) SYNC_WRAPPER_CONST_ARG_IN_DECLARATION_OUT_ARG(arg_type, arg_name)
#define SYNC_WRAPPER_CONST_OUT_ARG_IN_DECLARATION_PROXY(out_arg) \
MU_C2A(SYNC_WRAPPER_CONST_ARG_IN_DECLARATION_,out_arg)
#define SYNC_WRAPPER_CONST_OUT_ARGS_IN_DECLARATION(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(SYNC_WRAPPER_CONST_OUT_ARG_IN_DECLARATION_PROXY, __VA_ARGS__)), MU_NOEXPAND()))
// arg pointers in signature
#define SYNC_WRAPPER_POINTER_IN_DECLARATION_OUT_ARG(arg_type, arg_name) \
, arg_type ASYNC_TYPE_HELPER_ADD_POINTER_IF_NEEDED(arg_type), arg_name
#define SYNC_WRAPPER_POINTER_IN_DECLARATION_OUT_ARG_EX(arg_type, arg_name, ...) SYNC_WRAPPER_POINTER_IN_DECLARATION_OUT_ARG(arg_type, arg_name)
#define SYNC_WRAPPER_OUT_ARG_POINTER_IN_DECLARATION(out_arg) \
MU_C2A(SYNC_WRAPPER_POINTER_IN_DECLARATION_, out_arg)
#define SYNC_WRAPPER_OUT_ARG_POINTERS_IN_DECLARATION(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(SYNC_WRAPPER_OUT_ARG_POINTER_IN_DECLARATION, __VA_ARGS__)), MU_NOEXPAND()))
// arg values in calls
#define SYNC_WRAPPER_ARG_VALUE(arg_type, arg_name) , arg_name
#define EXPAND_ARG_VALUES_IN_ARGS(...) \
MU_FOR_EACH_2(SYNC_WRAPPER_ARG_VALUE, __VA_ARGS__)
#define SYNC_WRAPPER_ARG_VALUES_IN_DECLARATION(in_args) \
MU_C2A(EXPAND_ARG_VALUES_, in_args)
// create fields in struct for out args values
#define FIELD_IN_STRUCT_VALUE_OUT_ARG(arg_type, arg_name) \
arg_type ASYNC_TYPE_HELPER_ADD_POINTER_IF_NEEDED(arg_type) arg_name;
#define FIELD_IN_STRUCT_VALUE_OUT_ARG_EX(arg_type, arg_name, ...) FIELD_IN_STRUCT_VALUE_OUT_ARG(arg_type, arg_name)
#define FIELD_IN_STRUCT_OUT_ARG_VALUE_PROXY(out_arg) \
MU_C2A(FIELD_IN_STRUCT_VALUE_, out_arg)
/* Codes_SRS_SYNC_WRAPPER_01_026: [ OUT_ARG shall be expanded to the appropriate argument wherever needed by using arg_type and arg_name. ]*/
#define SYNC_WRAPPER_FIELDS_IN_STRUCT_OUT_ARGS(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(FIELD_IN_STRUCT_OUT_ARG_VALUE_PROXY, __VA_ARGS__)), MU_NOEXPAND()))
/* Codes_SRS_SYNC_WRAPPER_01_019: [ If ASYNC_TYPE_HELPER_USE_ASSIGN_COPY_{type} is not defined, on_{async_function_name}_complete shall call a copy function with the following declaration: ]*/
#define EXECUTE_CUSTOM_SYNC_WRAPPER_COPY_OUT_ARG(arg_type, arg_name) \
bool MU_C2B(arg_name, _free_needed) = false; \
if (!async_call_context->error_copying_out_args) \
{ \
if (ASYNC_TYPE_HELPER_COPY_HANDLER(arg_type)(async_call_context->arg_name, arg_name) != 0) \
{ \
LogError("Copying argument " MU_TOSTRING(arg_name) " of type " MU_TOSTRING(arg_type)); \
async_call_context->error_copying_out_args = true; \
} else \
{ \
MU_C2B(arg_name, _free_needed) = true; \
} \
}
/* Codes_SRS_SYNC_WRAPPER_01_025: [ copy_function shall be used as a function with the following declaration ]*/
#define EXECUTE_CUSTOM_SYNC_WRAPPER_COPY_OUT_ARG_EX(arg_type, arg_name, copy_function, free_function, ...) \
bool MU_C2B(arg_name, _free_needed) = false; \
if (!async_call_context->error_copying_out_args) \
{ \
if (copy_function(async_call_context->arg_name, arg_name MU_IFCOMMALOGIC(MU_COUNT_ARG(__VA_ARGS__)) __VA_ARGS__) != 0) \
{ \
LogError("Copying argument " MU_TOSTRING(arg_name) " of type " MU_TOSTRING(arg_type)); \
async_call_context->error_copying_out_args = true; \
} else \
{ \
MU_C2B(arg_name, _free_needed) = true; \
} \
}
/* Codes_SRS_SYNC_WRAPPER_01_018: [ If ASYNC_TYPE_HELPER_USE_ASSIGN_COPY_{type} is defined as 1, on_{async_function_name}_complete shall copy the argument value by assigning it. ]*/
#define EXECUTE_DEFAULT_SYNC_WRAPPER_COPY_OUT_ARG(arg_type, arg_name) \
*async_call_context->arg_name = arg_name;
#define EXECUTE_DEFAULT_SYNC_WRAPPER_COPY_OUT_ARG_EX(arg_type, arg_name, ...) EXECUTE_DEFAULT_SYNC_WRAPPER_COPY_OUT_ARG(arg_type, arg_name)
#define SYNC_WRAPPER_CALL_COPY_OUT_ARG(out_arg) \
MU_EXPAND(MU_IF(MU_C2A(SYNC_WRAPPER_DO_NOT_USE_ASSIGN_COPY_,out_arg), MU_NOEXPAND(MU_C2A(EXECUTE_CUSTOM_SYNC_WRAPPER_COPY_,out_arg)), MU_NOEXPAND(MU_C2A(EXECUTE_DEFAULT_SYNC_WRAPPER_COPY_, out_arg))))
// copy callback out args values
#define SYNC_WRAPPER_COPY_CALLBACK_OUT_ARGS(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(SYNC_WRAPPER_CALL_COPY_OUT_ARG, __VA_ARGS__)), MU_NOEXPAND()))
#define EXECUTE_CUSTOM_SYNC_WRAPPER_FREE_FAILED_OUT_ARG(arg_type, arg_name) \
if (async_call_context->error_copying_out_args && (MU_C2B(arg_name, _free_needed))) \
{ \
ASYNC_TYPE_HELPER_FREE_HANDLER(arg_type)(ASYNC_TYPE_HELPER_ADD_POINTER_IF_NEEDED(arg_type) async_call_context->arg_name); \
}
/* Codes_SRS_SYNC_WRAPPER_01_027: [ free_function shall be used as a function with the following declaration ]*/
#define EXECUTE_CUSTOM_SYNC_WRAPPER_FREE_FAILED_OUT_ARG_EX(arg_type, arg_name, copy_function, free_function, ...) \
if (async_call_context->error_copying_out_args && (MU_C2B(arg_name, _free_needed))) \
{ \
free_function(*async_call_context->arg_name MU_IFCOMMALOGIC(MU_COUNT_ARG(__VA_ARGS__)) __VA_ARGS__); \
}
// do nothing
#define EXECUTE_DEFAULT_SYNC_WRAPPER_FREE_FAILED_OUT_ARG(arg_type, arg_name)
#define EXECUTE_DEFAULT_SYNC_WRAPPER_FREE_FAILED_OUT_ARG_EX(arg_type, arg_name, ...) EXECUTE_DEFAULT_SYNC_WRAPPER_FREE_FAILED_OUT_ARG(arg_type, arg_name)
#define SYNC_WRAPPER_CALL_FREE_FAILED_OUT_ARG(out_arg) \
MU_EXPAND(MU_IF(MU_C2A(SYNC_WRAPPER_DO_NOT_USE_ASSIGN_COPY_,out_arg), MU_NOEXPAND(MU_C2A(EXECUTE_CUSTOM_SYNC_WRAPPER_FREE_FAILED_,out_arg)), MU_NOEXPAND(MU_C2A(EXECUTE_DEFAULT_SYNC_WRAPPER_FREE_FAILED_,out_arg))))
// copy callback out args values
#define SYNC_WRAPPER_FREE_FAILED_CALLBACK_OUT_ARGS(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(SYNC_WRAPPER_CALL_FREE_FAILED_OUT_ARG, __VA_ARGS__)), MU_NOEXPAND()))
// store out arg pointers
#define STORE_OUT_ARG_PTR_OUT_ARG(arg_type, arg_name) \
async_call_context.arg_name = arg_name;
#define STORE_OUT_ARG_PTR_OUT_ARG_EX(arg_type, arg_name, ...) STORE_OUT_ARG_PTR_OUT_ARG(arg_type, arg_name)
#define STORE_OUT_ARG_PTR_PROXY(out_arg) \
MU_C2(STORE_OUT_ARG_PTR_, out_arg)
#define SYNC_WRAPPER_STORE_OUT_ARG_PTRS(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(STORE_OUT_ARG_PTR_PROXY, __VA_ARGS__)), MU_NOEXPAND()))
// check out arg pointers
/* Codes_SRS_SYNC_WRAPPER_01_004: [ If any of the out arguments pointers is NULL, the synchronous wrapper shall fail and return SYNC_WRAPPER_INVALID_ARGS. ]*/
#define CHECK_OUT_ARG_PTR_OUT_ARG(arg_type, arg_name) \
else if (arg_name == NULL) \
{ \
LogError("NULL " MU_TOSTRING(arg_name) " of type " MU_TOSTRING(arg_type)); \
sync_wrapper_result = SYNC_WRAPPER_INVALID_ARGS; \
}
#define CHECK_OUT_ARG_PTR_OUT_ARG_EX(arg_type, arg_name, ...) CHECK_OUT_ARG_PTR_OUT_ARG(arg_type, arg_name)
#define CHECK_OUT_ARG_PTR_PROXY(out_arg) \
MU_C2A(CHECK_OUT_ARG_PTR_, out_arg)
#define SYNC_WRAPPER_CHECK_OUT_ARG_PTRS(...) \
MU_EXPAND(MU_IF(MU_COUNT_ARG(__VA_ARGS__), MU_NOEXPAND(MU_FOR_EACH_1(CHECK_OUT_ARG_PTR_PROXY, __VA_ARGS__)), MU_NOEXPAND()))
/* Codes_SRS_SYNC_WRAPPER_01_016: [ SYNC_WRAPPER shall expand async_function_name to the name of the synchronous wrapper around async_function_name. ]*/
#define SYNC_WRAPPER(async_function_name) \
MU_C2(async_function_name, _sync_wrapper) \
/* Codes_SRS_SYNC_WRAPPER_01_001: [ DECLARE_SYNC_WRAPPER shall expand to the function declaration: ]*/
#define DECLARE_SYNC_WRAPPER(async_handle_type, async_function_name, return_type, in_args, ...) \
MOCKABLE_FUNCTION(, SYNC_WRAPPER_RESULT, SYNC_WRAPPER(async_function_name), async_handle_type, async_handle SYNC_WRAPPER_ARGS_IN_DECLARATION(in_args), return_type*, async_function_call_result SYNC_WRAPPER_OUT_ARG_POINTERS_IN_DECLARATION(__VA_ARGS__)); \
#define GENERATE_SYNC_WRAPPER_CONTEXT(async_handle_type, async_function_name, return_type, expected_return, in_args, ...) \
typedef struct MU_C2(SYNC_WRAPPER(async_function_name), _CONTEXT_TAG) \
{ \
volatile_atomic int32_t complete; \
bool error_copying_out_args; \
SYNC_WRAPPER_FIELDS_IN_STRUCT_OUT_ARGS(__VA_ARGS__) \
} MU_C2(SYNC_WRAPPER(async_function_name), _CONTEXT);
/* Codes_SRS_SYNC_WRAPPER_01_011: [ DEFINE_SYNC_WRAPPER shall generate a callback to be passed to the asynchronous function with the following declaration: ]*/
/* Codes_SRS_SYNC_WRAPPER_01_013: [ Otherwise, on_{async_function_name}_complete shall store the values of the out args into the context created in synchronous wrapper function. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_015: [ on_{async_function_name}_complete shall unblock the synchronous wrapper function call. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_012: [ If context is NULL, on_{async_function_name}_complete shall terminate the process. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_017: [ For each argument: ]*/
#define GENERATE_SYNC_WRAPPER_CALLBACK(async_handle_type, async_function_name, return_type, expected_return, in_args, ...) \
static void MU_C3(on_, async_function_name, _complete)(void* context SYNC_WRAPPER_CONST_OUT_ARGS_IN_DECLARATION(__VA_ARGS__)) \
{ \
if (context == NULL) \
{ \
LogCriticalAndTerminate("NULL context"); \
} \
else \
{ \
MU_C2(SYNC_WRAPPER(async_function_name), _CONTEXT)* async_call_context = (MU_C2(SYNC_WRAPPER(async_function_name), _CONTEXT)*)context; \
async_call_context->error_copying_out_args = false; \
SYNC_WRAPPER_COPY_CALLBACK_OUT_ARGS(__VA_ARGS__) \
SYNC_WRAPPER_FREE_FAILED_CALLBACK_OUT_ARGS(__VA_ARGS__) \
if (InterlockedHL_SetAndWake(&async_call_context->complete, 1) != INTERLOCKED_HL_OK) \
{ \
LogError("InterlockedHL_SetAndWake failed"); \
} \
} \
}
#ifdef _MSC_VER
#define SYNC_WRAPPER_WARNING_SUPPRESS(warn_no) \
__pragma(warning(suppress:warn_no))
#else
#define SYNC_WRAPPER_WARNING_SUPPRESS(warn_no)
#endif
/* Codes_SRS_SYNC_WRAPPER_01_002: [ The generated synchronous wrapper shall have the declaration: ]*/
/* Codes_SRS_SYNC_WRAPPER_01_003: [ If async_handle is NULL, the synchronous wrapper shall fail and return SYNC_WRAPPER_INVALID_ARGS. ]*/
/* Codes_SRS_SYNC_WRAPPER_42_002: [ If async_function_call_result is NULL then the synchronous wrapper shall fail and return SYNC_WRAPPER_INVALID_ARGS. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_006: [ The synchronous wrapper shall call the asynchronous function async_function_name, pass the in_args as arguments together with the generated completion function and a context used to store out argument pointers. ]*/
/* Codes_SRS_SYNC_WRAPPER_42_003: [ If the asynchronous function returns something other than expected_return then the synchronous wrapper shall return SYNC_WRAPPER_CALL_ERROR. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_007: [ The synchronous wrapper shall wait for the callback to be finished by using InterlockedHL_WaitForValue. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_008: [ On success, the synchronous wrapper shall return SYNC_WRAPPER_OK. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_009: [ On success, the synchronous wrapper shall return in the out args the values of the arguments received in the callback. ]*/
/* Codes_SRS_SYNC_WRAPPER_42_001: [ The synchronous wrapper shall store the result of the asynchronous function call in async_function_call_result. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_010: [ If any other error occurs, the synchronous wrapper shall fail and return SYNC_WRAPPER_OTHER_ERROR. ]*/
/* Codes_SRS_SYNC_WRAPPER_01_020: [ If the callback fails to copy any argument value, the synchronous wrapper shall return SYNC_WRAPPER_CALLBACK_COPY_ARG_ERROR. ]*/
#define GENERATE_SYNC_WRAPPER_FUNCTION(async_handle_type, async_function_name, return_type, expected_return, in_args, ...) \
IMPLEMENT_MOCKABLE_FUNCTION(, SYNC_WRAPPER_RESULT, SYNC_WRAPPER(async_function_name), async_handle_type, async_handle SYNC_WRAPPER_ARGS_IN_DECLARATION(in_args), return_type*, async_function_call_result SYNC_WRAPPER_OUT_ARG_POINTERS_IN_DECLARATION(__VA_ARGS__)) \
{ \
SYNC_WRAPPER_RESULT sync_wrapper_result; \
if (async_handle == NULL) \
{ \
LogError("NULL " MU_TOSTRING(async_handle_type) " async_handle for " MU_TOSTRING(SYNC_WRAPPER(async_function_name))); \
sync_wrapper_result = SYNC_WRAPPER_INVALID_ARGS; \
} \
else if (async_function_call_result == NULL) \
{ \
LogError("NULL " MU_TOSTRING(return_type) " async_function_call_result for " MU_TOSTRING(SYNC_WRAPPER(async_function_name))); \
sync_wrapper_result = SYNC_WRAPPER_INVALID_ARGS; \
} \
SYNC_WRAPPER_CHECK_OUT_ARG_PTRS(__VA_ARGS__) \
else \
{ \
MU_C2(SYNC_WRAPPER(async_function_name), _CONTEXT) async_call_context; \
(void)interlocked_exchange(&async_call_context.complete, 0); \
SYNC_WRAPPER_STORE_OUT_ARG_PTRS(__VA_ARGS__) \
*async_function_call_result = async_function_name(async_handle SYNC_WRAPPER_ARG_VALUES_IN_DECLARATION(in_args), MU_C3(on_, async_function_name, _complete), &async_call_context); \
if (*async_function_call_result != expected_return) \
{ \
LogError(MU_TOSTRING(SYNC_WRAPPER(async_function_name)) " failed"); \
sync_wrapper_result = SYNC_WRAPPER_CALL_ERROR; \
} \
else \
{ \
INTERLOCKED_HL_RESULT interlocked_hl_result = InterlockedHL_WaitForValue(&async_call_context.complete, 1, UINT32_MAX); \
if (interlocked_hl_result != INTERLOCKED_HL_OK) \
{ \
LogError("InterlockedHL_WaitForValue failed with interlocked_hl_result=%" PRI_MU_ENUM "", MU_ENUM_VALUE(INTERLOCKED_HL_RESULT, interlocked_hl_result)); \
sync_wrapper_result = SYNC_WRAPPER_OTHER_ERROR; \
} \
else \
{ \
if (async_call_context.error_copying_out_args) \
{ \
sync_wrapper_result = SYNC_WRAPPER_CALLBACK_COPY_ARG_ERROR; \
} \
else \
{ \
sync_wrapper_result = SYNC_WRAPPER_OK; \
} \
} \
} \
} \
return sync_wrapper_result; \
}
#define DEFINE_SYNC_WRAPPER(async_handle_type, async_function_name, return_type, expected_return, in_args, ...) \
GENERATE_SYNC_WRAPPER_CONTEXT(async_handle_type, async_function_name, return_type, expected_return, in_args, __VA_ARGS__) \
GENERATE_SYNC_WRAPPER_CALLBACK(async_handle_type, async_function_name, return_type, expected_return, in_args, __VA_ARGS__) \
GENERATE_SYNC_WRAPPER_FUNCTION(async_handle_type, async_function_name, return_type, expected_return, in_args, __VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif // SYNC_WRAPPER_H