common/src/call_once.c (43 lines of code) (raw):
// Copyright (C) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <stdint.h>
#include <stdbool.h>
#include "macro_utils/macro_utils.h"
#include "c_logging/logger.h"
#include "c_pal/interlocked.h"
#include "c_pal/sync.h"
#include "c_pal/call_once.h"
MU_DEFINE_ENUM_STRINGS(CALL_ONCE_RESULT, CALL_ONCE_RESULT_VALUES);
/*a weak check to ensure that nobody has the idea to change call_once_t type to int8_t (or other type... to save some memory for example)*/
static int MU_UNUSED_VAR check_call_once_t_is_the_same_as_volatile_atomic_int32_t_because_we_are_going_to_pass_it_to_wait_on_address_or_wake_by_address_all[sizeof(volatile_atomic int32_t) == sizeof(call_once_t)];
#define CALL_ONCE_STATE_VALUES \
CALL_ONCE_STATE_NOT_CALLED = CALL_ONCE_NOT_CALLED, \
CALL_ONCE_STATE_CALLING, \
CALL_ONCE_STATE_CALLED
MU_DEFINE_ENUM_WITHOUT_INVALID(CALL_ONCE_STATE, CALL_ONCE_STATE_VALUES)
/*returns CALL_ONCE_PROCEED if calling code should do proceed to call the code, any other value = code already called*/
CALL_ONCE_RESULT call_once_begin(call_once_t* state)
{
CALL_ONCE_RESULT result = CALL_ONCE_ALREADY_CALLED; /*optimistically initialize*/
int32_t state_local;
/* 0 - (CALL_ONCE_NOT_CALLED) = not called*/
/* 1 - (CALL_ONCE_NOT_CALLED+1) calling... */
/* 2 - (CALL_ONCE_NOT_CALLED+2) already called*/
/*Codes_SRS_CALL_ONCE_02_001: [ call_once_begin shall use interlocked_compare_exchange(state, 1, 0) to determine if user has alredy indicated that the init code was executed with success. ]*/
/*Codes_SRS_CALL_ONCE_02_002: [ If interlocked_compare_exchange returns 2 then call_once_begin shall return CALL_ONCE_ALREADY_CALLED. ]*/
while ((state_local = interlocked_compare_exchange(state, CALL_ONCE_STATE_CALLING, CALL_ONCE_STATE_NOT_CALLED)) != CALL_ONCE_STATE_CALLED)
{
if (state_local == CALL_ONCE_STATE_NOT_CALLED) /*it was not initialized, now it is "1" (initializing)*/
{
/*Codes_SRS_CALL_ONCE_02_004: [ If interlocked_compare_exchange returns 0 then call_once_begin shall return CALL_ONCE_PROCEED. ]*/
/*only 1 thread can get here, (the current one) so letting it proceed to initialize*/
result = CALL_ONCE_PROCEED;
break;
}
else
{
/*Codes_SRS_CALL_ONCE_02_003: [ If interlocked_compare_exchange returns 1 then call_once_begin shall call wait_on_address(state) with timeout UINT32_MAX and call again interlocked_compare_exchange(state, 1, 0). ]*/
/*state cannot be "2" because while condition, so that means state is "1", and that means, need to wait until it is not 1*/
if (wait_on_address(state, CALL_ONCE_STATE_CALLING, UINT32_MAX) != WAIT_ON_ADDRESS_OK)
{
/*look the other way and retry*/
LogError("failure in wait_on_address, var=%p", state);
}
else
{
/*state is not 1 here, but it can be 0 or 2, so a reevaluation is needed*/
}
}
}
return result;
}
void call_once_end(call_once_t* state, bool success)
{
/*Codes_SRS_CALL_ONCE_02_005: [ If success is true then call_once_end shall call interlocked_exchange setting state to CALL_ONCE_CALLED and shall call wake_by_address_all(state). ]*/
/*Codes_SRS_CALL_ONCE_02_006: [ If success is false then call_once_end shall call interlocked_exchange setting state to CALL_ONCE_NOT_CALLED and shall call wake_by_address_all(state). ]*/
(void)interlocked_exchange(state, success ? CALL_ONCE_STATE_CALLED : CALL_ONCE_STATE_NOT_CALLED);
wake_by_address_all(state);
}