src/cancellation_token.c (167 lines of code) (raw):
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include "macro_utils/macro_utils.h"
#include "c_logging/logger.h"
#include "c_pal/thandle.h"
#include "c_pal/gballoc_hl.h"
#include "c_pal/gballoc_hl_redirect.h"
#include "c_pal/interlocked.h"
#include "c_util/tcall_dispatcher_cancellation_token_cancel_call.h"
#include "c_util/cancellation_token.h"
#define CANCELLATION_TOKEN_STATE_VALUES \
CANCELLATION_TOKEN_STATE_NOT_CANCELED, \
CANCELLATION_TOKEN_STATE_CANCELED
MU_DEFINE_ENUM(CANCELLATION_TOKEN_STATE, CANCELLATION_TOKEN_STATE_VALUES);
MU_DEFINE_ENUM_STRINGS(CANCELLATION_TOKEN_STATE, CANCELLATION_TOKEN_STATE_VALUES);
typedef union CANCELLATION_TOKEN_STATE_VALUE_TAG
{
volatile_atomic int32_t state;
volatile_atomic CANCELLATION_TOKEN_STATE state_enum;
} CANCELLATION_TOKEN_STATE_VALUE;
typedef struct CANCELLATION_TOKEN_TAG
{
CANCELLATION_TOKEN_STATE_VALUE state;
TCALL_DISPATCHER(CANCELLATION_TOKEN_CANCEL_CALL) cancel_notification_call_dispatcher;
} CANCELLATION_TOKEN;
THANDLE_TYPE_DEFINE(CANCELLATION_TOKEN);
typedef struct CANCELLATION_TOKEN_REGISTRATION_TAG
{
THANDLE(CANCELLATION_TOKEN) token;
TCALL_DISPATCHER_TARGET_HANDLE(CANCELLATION_TOKEN_CANCEL_CALL) cancel_notification_target_handle;
} CANCELLATION_TOKEN_REGISTRATION;
THANDLE_TYPE_DEFINE(CANCELLATION_TOKEN_REGISTRATION);
static void cancellation_token_dispose(CANCELLATION_TOKEN* token)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_025: [ cancellation_token_dispose shall free the TCALL_DISPATCHER by assigning NULL to the dispatcher handle by calling TCALL_DISPATCHER_ASSIGN(CANCELLATION_TOKEN_CANCEL_CALL). ]*/
TCALL_DISPATCHER_ASSIGN(CANCELLATION_TOKEN_CANCEL_CALL)(&token->cancel_notification_call_dispatcher, NULL);
}
static void cancellation_token_registration_dispose(CANCELLATION_TOKEN_REGISTRATION* registration)
{
CANCELLATION_TOKEN* token_ptr = THANDLE_GET_T(CANCELLATION_TOKEN)(registration->token);
/*Codes_SRS_CANCELLATION_TOKEN_04_023: [ cancellation_token_registration_dispose shall un-register the callback from TCALL_DISPATCHER by calling TCALL_DISPATCHER_UNREGISTER_TARGET(CANCELLATION_TOKEN_CANCEL_CALL). ]*/
TCALL_DISPATCHER_UNREGISTER_TARGET(CANCELLATION_TOKEN_CANCEL_CALL)(token_ptr->cancel_notification_call_dispatcher, registration->cancel_notification_target_handle);
/*Codes_SRS_CANCELLATION_TOKEN_04_024: [ cancellation_token_registration_dispose shall free resources. ]*/
THANDLE_ASSIGN(CANCELLATION_TOKEN)(®istration->token, NULL);
}
IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(CANCELLATION_TOKEN), cancellation_token_create, bool, canceled)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_001: [ cancellation_token_create shall allocate memory for a THANDLE(CANCELLATION_TOKEN). ]*/
THANDLE(CANCELLATION_TOKEN) result = NULL;
THANDLE(CANCELLATION_TOKEN) token = THANDLE_MALLOC(CANCELLATION_TOKEN)(cancellation_token_dispose);
if (token == NULL)
{
LogError("THANDLE_MALLOC(CANCELLATION_TOKEN)(cancellation_token_dispose) failed.");
}
else
{
CANCELLATION_TOKEN* token_ptr = THANDLE_GET_T(CANCELLATION_TOKEN)(token);
/*Codes_SRS_CANCELLATION_TOKEN_04_003: [ cancellation_token_create shall create a TCALL_DISPATCHER handle by calling TCALL_DISPATCHER_CREATE(CANCELLATION_TOKEN_CANCEL_CALL). ]*/
TCALL_DISPATCHER(CANCELLATION_TOKEN_CANCEL_CALL) dispatcher = TCALL_DISPATCHER_CREATE(CANCELLATION_TOKEN_CANCEL_CALL)();
if (dispatcher == NULL)
{
LogError("TCALL_DISPATCHER_CREATE(CANCELLATION_TOKEN_CANCEL_CALL)() failed.");
TCALL_DISPATCHER_INITIALIZE(CANCELLATION_TOKEN_CANCEL_CALL)(&token_ptr->cancel_notification_call_dispatcher, NULL);
}
else
{
if (canceled == false)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_004: [ cancellation_token_create shall set the initial state to be equal to canceled parameter. ]*/
(void)interlocked_exchange(&token_ptr->state.state, CANCELLATION_TOKEN_STATE_NOT_CANCELED);
}
else
{
/*Codes_SRS_CANCELLATION_TOKEN_04_004: [ cancellation_token_create shall set the initial state to be equal to canceled parameter. ]*/
(void)interlocked_exchange(&token_ptr->state.state, CANCELLATION_TOKEN_STATE_CANCELED);
}
TCALL_DISPATCHER_INITIALIZE_MOVE(CANCELLATION_TOKEN_CANCEL_CALL)(&token_ptr->cancel_notification_call_dispatcher, &dispatcher);
THANDLE_INITIALIZE_MOVE(CANCELLATION_TOKEN)(&result, &token);
/*Codes_SRS_CANCELLATION_TOKEN_04_005: [ cancellation_token_create shall return a valid THANDLE(CANCELLATION_TOKEN) when successful. ]*/
goto all_ok;
}
/*Codes_SRS_CANCELLATION_TOKEN_04_002: [ If any underlying error occurs cancellation_token_create shall fail and return NULL. ]*/
THANDLE_FREE(CANCELLATION_TOKEN)((void *)token);
}
all_ok:
return result;
}
IMPLEMENT_MOCKABLE_FUNCTION(, bool, cancellation_token_is_canceled, THANDLE(CANCELLATION_TOKEN), cancellation_token)
{
bool result;
if (cancellation_token == NULL)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_006: [ cancellation_token_is_canceled shall return false if cancellation_token is NULL. ]*/
LogError("Invalid args: THANDLE(CANCELLATION_TOKEN) cancellation_token=%p", cancellation_token);
result = false;
}
else
{
CANCELLATION_TOKEN* token_ptr = THANDLE_GET_T(CANCELLATION_TOKEN)(cancellation_token);
CANCELLATION_TOKEN_STATE state = interlocked_add(&token_ptr->state.state, 0);
/*Codes_SRS_CANCELLATION_TOKEN_04_008: [ cancellation_token_is_canceled shall return true if the token has been canceled and false otherwise. ]*/
if (state == CANCELLATION_TOKEN_STATE_CANCELED)
{
result = true;
}
else
{
result = false;
}
}
return result;
}
IMPLEMENT_MOCKABLE_FUNCTION(, THANDLE(CANCELLATION_TOKEN_REGISTRATION), cancellation_token_register_notify, THANDLE(CANCELLATION_TOKEN), cancellation_token, TCALL_DISPATCHER_TARGET_FUNC_TYPE_NAME(CANCELLATION_TOKEN_CANCEL_CALL), on_cancel, void*, context)
{
THANDLE(CANCELLATION_TOKEN_REGISTRATION) result = NULL;
if (cancellation_token == NULL || on_cancel == NULL)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_010: [ cancellation_token_register_notify shall fail and return NULL when cancellation_token is NULL. ]*/
/*Codes_SRS_CANCELLATION_TOKEN_04_011: [ cancellation_token_register_notify shall fail and return NULL when on_cancel is NULL. ]*/
LogError("Invalid args: THANDLE(CANCELLATION_TOKEN) cancellation_token=%p, TCALL_DISPATCHER_TARGET_FUNC_TYPE_NAME(CANCELLATION_TOKEN_CANCEL_CALL) on_cancel=%p, void* context=%p", cancellation_token, on_cancel, context);
}
else
{
CANCELLATION_TOKEN* token_ptr = THANDLE_GET_T(CANCELLATION_TOKEN)(cancellation_token);
/*Codes_SRS_CANCELLATION_TOKEN_04_022: [ If the cancellation token is already in canceled state, then cancellation_token_register_notify shall immediately call on_cancel and take no further action and return NULL. ]*/
CANCELLATION_TOKEN_STATE state = interlocked_add(&token_ptr->state.state, 0);
if (state == CANCELLATION_TOKEN_STATE_CANCELED)
{
on_cancel(context);
}
else
{
/*Codes_SRS_CANCELLATION_TOKEN_04_014: [ cancellation_token_register_notify shall call THANDLE_MALLOC(CANCELLATION_TOKEN_REGISTRATION) to allocate a CANCELLATION_TOKEN_REGISTRATION handle. ]*/
THANDLE(CANCELLATION_TOKEN_REGISTRATION) registration = THANDLE_MALLOC(CANCELLATION_TOKEN_REGISTRATION)(cancellation_token_registration_dispose);
if (registration == NULL)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_012: [ cancellation_token_register_notify shall fail and return NULL when any underlying call fails. ]*/
LogError("THANDLE_MALLOC(CANCELLATION_TOKEN)(cancellation_token_dispose) failed.");
}
else
{
/*Codes_SRS_CANCELLATION_TOKEN_04_013: [ cancellation_token_register_notify shall call TCALL_DISPATCHER_REGISTER_TARGET(CANCELLATION_TOKEN_CANCEL_CALL) to register on_cancel with the dispatcher. ]*/
TCALL_DISPATCHER_TARGET_HANDLE(CANCELLATION_TOKEN_CANCEL_CALL) cancel_notification_target_handle = TCALL_DISPATCHER_REGISTER_TARGET(CANCELLATION_TOKEN_CANCEL_CALL)(token_ptr->cancel_notification_call_dispatcher, on_cancel, context);
if (cancel_notification_target_handle == NULL)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_012: [ cancellation_token_register_notify shall fail and return NULL when any underlying call fails. ]*/
LogError("TCALL_DISPATCHER_REGISTER_TARGET(CANCELLATION_TOKEN_CANCEL_CALL)(token_ptr->cancel_notification_call_dispatcher=%p, on_cancel=%p, context=%p) failed.", token_ptr->cancel_notification_call_dispatcher, on_cancel, context);
}
else
{
CANCELLATION_TOKEN_REGISTRATION* registration_ptr = THANDLE_GET_T(CANCELLATION_TOKEN_REGISTRATION)(registration);
registration_ptr->cancel_notification_target_handle = cancel_notification_target_handle;
THANDLE_INITIALIZE(CANCELLATION_TOKEN)(®istration_ptr->token, cancellation_token);
/*Codes_SRS_CANCELLATION_TOKEN_04_015: [ cancellation_token_register_notify shall initialize and return a valid THANDLE(CANCELLATION_TOKEN_REGISTRATION) when successful. ]*/
THANDLE_INITIALIZE_MOVE(CANCELLATION_TOKEN_REGISTRATION)(&result, ®istration);
goto all_ok;
}
THANDLE_FREE(CANCELLATION_TOKEN_REGISTRATION)((void*)registration);
}
}
}
all_ok:
return result;
}
IMPLEMENT_MOCKABLE_FUNCTION(, int, cancellation_token_cancel, THANDLE(CANCELLATION_TOKEN), cancellation_token)
{
int result;
if (cancellation_token == NULL)
{
/*Codes_SRS_CANCELLATION_TOKEN_04_017: [ cancellation_token_cancel shall fail and return a non-zero value if cancellation_token is NULL. ]*/
LogError("Invalid args: THANDLE(CANCELLATION_TOKEN) cancellation_token=%p", cancellation_token);
result = MU_FAILURE;
}
else
{
CANCELLATION_TOKEN* token_ptr = THANDLE_GET_T(CANCELLATION_TOKEN)(cancellation_token);
/*Codes_SRS_CANCELLATION_TOKEN_04_019: [ cancellation_token_cancel shall set the state of the token to be "canceled". ]*/
if (interlocked_compare_exchange(&token_ptr->state.state, CANCELLATION_TOKEN_STATE_CANCELED, CANCELLATION_TOKEN_STATE_NOT_CANCELED) != CANCELLATION_TOKEN_STATE_NOT_CANCELED)
{
LogError("Cancellation token %p has been cancelled already.", cancellation_token);
/*Codes_SRS_CANCELLATION_TOKEN_04_018: [ cancellation_token_cancel shall fail and return a non-zero value if the state of the token is already "canceled". ]*/
result = MU_FAILURE;
}
else
{
/*Codes_SRS_CANCELLATION_TOKEN_04_021: [ cancellation_token_cancel shall invoke all callbacks that have been registered on the token via cancellation_token_register_notify by calling TCALL_DISPATCHER_DISPATCH_CALL(CANCELLATION_TOKEN_CANCEL_CALL). ]*/
TCALL_DISPATCHER_DISPATCH_CALL(CANCELLATION_TOKEN_CANCEL_CALL)(token_ptr->cancel_notification_call_dispatcher);
/*Codes_SRS_CANCELLATION_TOKEN_04_020: [ cancellation_token_cancel shall return 0 when successful. ]*/
result = 0;
}
}
return result;
}