diff --git a/.clang-format b/.clang-format index 2b2089a4..a28b22d6 100644 --- a/.clang-format +++ b/.clang-format @@ -88,7 +88,7 @@ PenaltyExcessCharacter: 4 PenaltyReturnTypeOnItsOwnLine: 100 PointerAlignment: Left ReflowComments: true -SortIncludes: false +SortIncludes: Never SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true @@ -101,7 +101,7 @@ SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 +Standard: c++11 TabWidth: 4 UseTab: Never ... diff --git a/CMakeLists.txt b/CMakeLists.txt index 9453ea82..ff94af13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,8 +60,9 @@ num_option(ONLY_PUBSUB_API "Only pubsub API" OFF) num_option(USE_PROXY "Use proxy" ON) num_option(USE_GZIP_COMPRESSION "Use gzip compression" ON) num_option(RECEIVE_GZIP_RESPONSE "Use gzip decompression" ON) +num_option(USE_RETRY_CONFIGURATION "Use requests retry" ON) num_option(USE_SUBSCRIBE_V2 "Use subscribe v2" ON) -num_option(USE_SUBSCRIBE_EVENT_ENGINE "Use subscribe event engine" ON) +num_option(USE_SUBSCRIBE_EVENT_ENGINE "Use Subscribe Event Engine" ON) num_option(USE_ADVANCED_HISTORY "Use advanced history" ON) num_option(USE_OBJECTS_API "Use objects API" ON) num_option(USE_AUTO_HEARTBEAT "Use auto heartbeat" ON) @@ -97,6 +98,7 @@ set(FLAGS "\ -D PUBNUB_PROXY_API=${USE_PROXY} \ -D PUBNUB_USE_GZIP_COMPRESSION=${USE_GZIP_COMPRESSION} \ -D PUBNUB_RECEIVE_GZIP_RESPONSE=${RECEIVE_GZIP_RESPONSE} \ + -D PUBNUB_USE_RETRY_CONFIGURATION=${USE_RETRY_CONFIGURATION} \ -D PUBNUB_USE_SUBSCRIBE_V2=${USE_SUBSCRIBE_V2} \ -D PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=${USE_SUBSCRIBE_EVENT_ENGINE} \ -D PUBNUB_USE_OBJECTS_API=${USE_OBJECTS_API} \ @@ -318,8 +320,15 @@ if(${RECEIVE_GZIP_RESPONSE}) ${CMAKE_CURRENT_LIST_DIR}/core/pbgzip_decompress.c) endif() -if(${USE_SUBSCRIBE_V2}) +if (${USE_RETRY_CONFIGURATION}) set(FEATURE_SOURCEFILES + ${FEATURE_SOURCEFILES} + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_request_retry_timer.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_retry_configuration.c) +endif () + +if(${USE_SUBSCRIBE_V2}) +set(FEATURE_SOURCEFILES ${FEATURE_SOURCEFILES} ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_v2.c ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_v2.c) @@ -327,12 +336,25 @@ endif() if(${USE_SUBSCRIBE_EVENT_ENGINE}) message(STATUS "Using subscribe event engine API") + set(LIB_SOURCEFILES + ${LIB_SOURCEFILES} + ${CMAKE_CURRENT_LIST_DIR}/lib/pbarray.c + ${CMAKE_CURRENT_LIST_DIR}/lib/pbhash_set.c + ${CMAKE_CURRENT_LIST_DIR}/lib/pbref_counter.c + ${CMAKE_CURRENT_LIST_DIR}/lib/pbstrdup.c) set(FEATURE_SOURCEFILES ${FEATURE_SOURCEFILES} - ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_entities.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_memory_utils.c ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_event_engine.c ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine.c - ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_event_engine.c) + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_states.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_events.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_effects.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_engine_transitions.c + ${CMAKE_CURRENT_LIST_DIR}/core/pbcc_subscribe_event_listener.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_entities.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_event_engine.c + ${CMAKE_CURRENT_LIST_DIR}/core/pubnub_subscribe_event_listener.c) endif() if(${USE_ADVANCED_HISTORY}) @@ -491,29 +513,7 @@ set(SOURCEFILES ${OS_SOURCEFILES} ${FEATURE_SOURCEFILES} ${INTF_SOURCEFILES} - core/pubnub_subscribe_event_engine.h - core/pubnub_entities.c - core/pubnub_entities.h - core/pubnub_entities_internal.h - lib/pbhash_set.c - lib/pbhash_set.h - lib/pbarray.c - lib/pbarray.h - core/pubnub_subscribe_event_engine_internal.h - lib/pbstrdup.c - lib/pbstrdup.h - lib/pbref_counter.c - lib/pbref_counter.h - core/pubnub_subscribe_event_listener.c - core/pubnub_subscribe_event_listener.h - core/pubnub_subscribe_event_listener_internal.h - core/pbcc_subscribe_event_listener.c - core/pbcc_subscribe_event_listener.h - core/pubnub_subscribe_event_listener_types.h - core/pubnub_subscribe_event_engine_types.h - core/pbcc_memory_utils.c - core/pbcc_memory_utils.h -) + core/samples/subscribe_event_engine_sample.c) if(NOT ESP_PLATFORM) if(${SHARED_LIB}) @@ -687,6 +687,7 @@ if(${EXAMPLES}) pubnub_callback_subloop_sample subscribe_publish_callback_sample subscribe_publish_from_callback + subscribe_event_engine_sample publish_callback_subloop_sample publish_queue_callback_subloop) if (WITH_CPP) diff --git a/core/pbauto_heartbeat.c b/core/pbauto_heartbeat.c index d5059acc..2f860991 100644 --- a/core/pbauto_heartbeat.c +++ b/core/pbauto_heartbeat.c @@ -560,7 +560,7 @@ bool pubnub_is_auto_heartbeat_enabled(pubnub_t* pb) } -void pbauto_heartbeat_free_channelInfo(pubnub_t* pb) +void pbauto_heartbeat_free_channelInfo(pubnub_t* pb) { PUBNUB_ASSERT_OPT(pb_valid_ctx_ptr(pb)); diff --git a/core/pbcc_event_engine.c b/core/pbcc_event_engine.c index afbfd0b5..851b4127 100644 --- a/core/pbcc_event_engine.c +++ b/core/pbcc_event_engine.c @@ -1,10 +1,11 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pbcc_event_engine.h" -#include #include -#include "pbcc_memory_utils.h" +#include "core/pbcc_memory_utils.h" #include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" #include "lib/pbref_counter.h" #include "core/pubnub_log.h" @@ -13,85 +14,183 @@ // Types // ---------------------------------------------- -// Sharable data type definition. +/** Sharable data type definition. */ struct pbcc_ee_data { - // Object references counter. + /** Object references counter. */ pbref_counter_t* counter; /** * @brief Pointer to the data which should be associated with any of * Event Engine objects: event, state, transition, or invocation. */ - const void* data; - // Data destruct function. + void* data; + /** Data destruct function. */ pbcc_ee_data_free_function_t data_free; }; -// Event Engine definition. -struct pbcc_event_engine { - // Pointer to the current Event Engine state. - pbcc_ee_state_t* current_state; +/** Event Engine state definition. */ +struct pbcc_ee_state { + /** Object references counter. */ + pbref_counter_t* counter; + /** A specific Event Engine state type. */ + int type; /** - * @brief Pointer to the FIFO list of invocations. + * @brief Pointer to the additional information associated with a specific + * Event Engine state. + */ + pbcc_ee_data_t* data; + /** + * @brief Pointer to the invocations, which should be called when the + * Event Engine is about to enter a specific state. * - * @see pbcc_ee_invocation_t + * \b Important: `pbarray_t` should be created without elements destructor + * to let `pbcc_ee_transition_t` manage invocation objects' life-cycle. */ - pbarray_t* invocations; - // Shared resources access lock. - pubnub_mutex_t mutw; -}; - -// State transition instruction definition. -struct pbcc_ee_transition { + pbarray_t* on_enter_invocations; /** - * @brief Pointer to the next Event Engine state, which is based on current - * state and event passed into `transition` function. + * @brief Pointer to the invocations, which should be called when the + * Event Engine is about to leave a specific state. + * + * \b Important: `pbarray_t` should be created without elements destructor + * to let `pbcc_ee_transition_t` manage invocation objects' life-cycle. */ - pbcc_ee_state_t* target_state; + pbarray_t* on_exit_invocations; /** - * @brief Pointer to the list of effect invocations which should be called - * during the current Event Engine state change. + * @brief State-specific transition function to compute target state and + * invocations to call. */ - pbarray_t* invocations; + pbcc_ee_transition_function_t transition; }; -// Business logic invocation definition. +/** Event Engine effect invocation status definition */ +typedef enum { + /** Effect invocation just has been created and not processed. */ + PBCC_EE_INVOCATION_CREATED, + /** Invocation running is running effect implementation. */ + PBCC_EE_INVOCATION_RUNNING, + /** Invocation completed effect execution. */ + PBCC_EE_INVOCATION_COMPLETED +} pbcc_ee_invocation_status; + +/** Business logic invocation definition. */ struct pbcc_ee_invocation { /** * @brief Whether invocation should be processed immediately without * addition to the queue or not. */ bool immediate; - // Object references counter. + /** Current effect invocation status. */ + pbcc_ee_invocation_status status; + /** Object references counter. */ pbref_counter_t* counter; /** * @brief Pointer to the additional information associated with a specific * the Event Engine dispatcher into the effect execution function. */ pbcc_ee_data_t* data; - // State-specific effect function. + /** State-specific effect function. */ pbcc_ee_effect_function_t effect; }; +/** State transition instruction definition. */ +struct pbcc_ee_transition { + /** + * @brief Pointer to the next Event Engine state, which is based on current + * state and event passed into `transition` function. + */ + pbcc_ee_state_t* target_state; + /** + * @brief Pointer to the list of effect invocations which should be called + * during the current Event Engine state change. + * + * \b Important: Array allocated with `pbcc_ee_invocation_free` elements + * destructor. + */ + pbarray_t* invocations; +}; + +/** Event Engine definition. */ +struct pbcc_event_engine { + /** Pointer to the current Event Engine state. */ + pbcc_ee_state_t* current_state; + /** + * @brief Pointer to the FIFO list of invocations. + * + * \b Important: Array allocated with `pbcc_ee_invocation_free` elements + * destructor. + * + * @see pbcc_ee_invocation_t + */ + pbarray_t* invocations; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + // ---------------------------------------------- // Function prototypes // ---------------------------------------------- /** - * @brief Create a copy of existing invocation. + * @brief Update current Event Engine state. + * + * @param ee Pointer to the event engine for which state should be changed. + * @param state Pointer to the state which should be set as current Event Engine + * state. + */ +static void pbcc_ee_current_state_set_( + pbcc_event_engine_t* ee, + pbcc_ee_state_t* state); + +/** + * @brief Create a copy of existing state. + * + * @note Copies share state and update reference counter to reflect active + * entities. + * + * @param state Pointer to the state object from which copy should be created. + * @return Pointer to the ready to use Event Engine state or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_state_free` to avoid a memory leak. * - * Copies share data and update reference counter to reflect active entities. + * @see pbcc_ee_state_free + */ +static pbcc_ee_state_t* pbcc_ee_state_copy_(pbcc_ee_state_t* state); + +/** + * @brief Add invocation into invocations list. * - * @param invocation Pointer to the invocation object from which copy should be - * created. - * @return Pointer to the read to use effect invocation intention object or - * `NULL` in case of insufficient memory error. The returned pointer - * must be passed to the `pbcc_ee_invocation_free` to avoid a memory - * leak. + * @param invocations Pointer to the array where new invocation should be added. + * @param invocation Pointer to the invocation which should be called when + * Event Engine will leave `state` as current state before + * entering new state. + *
\b Important: Event Engine will manage `invocation` + * memory after successful addition. + * @return Result of invocations list modification operation. */ -static pbcc_ee_invocation_t* _pbcc_ee_invocation_copy( +static enum pubnub_res pbcc_ee_state_add_invocation_( + pbarray_t** invocations, pbcc_ee_invocation_t* invocation); +/** + * @brief Update Event Engine state transition invocations list. + * + * @param transition Pointer to the Event Engine state transition object for + * which invocations list should be updated. + * @param invocations Pointer to the list with invocation objects for + * transition. + * @return `false` if transition's invocations list modification failed. + */ +static bool pbcc_ee_transition_add_invocations_( + const pbcc_ee_transition_t* transition, + pbarray_t* invocations); + +/** + * @brief Execute effect invocation. + * + * @param invocation Pointer to the invocation which should execute effect. + */ +static void pbcc_ee_invocation_exec_(pbcc_ee_invocation_t* invocation); + /** * @brief Handle effect invocation completion. * @@ -100,9 +199,9 @@ static pbcc_ee_invocation_t* _pbcc_ee_invocation_copy( * @param invocation Pointer to the invocation which has been used to execute * effect. */ -static void _pbcc_ee_effect_completion( - pbcc_event_engine_t* ee, - const pbcc_ee_invocation_t* invocation); +static void pbcc_ee_effect_completion_( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation); // ---------------------------------------------- @@ -114,26 +213,41 @@ pbcc_event_engine_t* pbcc_ee_alloc(pbcc_ee_state_t* initial_state) PUBNUB_ASSERT_OPT(NULL != initial_state); PBCC_ALLOCATE_TYPE(ee, pbcc_event_engine_t, true, NULL); + ee->current_state = initial_state; + pubnub_mutex_init(ee->mutw); + ee->invocations = pbarray_alloc(5, PBARRAY_RESIZE_BALANCED, PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free) pbcc_ee_invocation_free); if (NULL == ee->invocations) { PUBNUB_LOG_ERROR("pbcc_ee_alloc: failed to allocate memory\n"); - pbcc_ee_free(ee); + pbcc_ee_free(&ee); return NULL; } - ee->current_state = initial_state; - pubnub_mutex_init(ee->mutw); - return ee; } -pbcc_ee_state_t* pbcc_ee_current_state(const pbcc_event_engine_t* ee) +void pbcc_ee_free(pbcc_event_engine_t** ee) +{ + if (NULL == ee || NULL == *ee) { return; } + + pubnub_mutex_lock((*ee)->mutw); + if (NULL != (*ee)->invocations) { pbarray_free(&(*ee)->invocations); } + if (NULL != (*ee)->current_state) + pbcc_ee_state_free(&(*ee)->current_state); + pubnub_mutex_unlock((*ee)->mutw); + pubnub_mutex_destroy((*ee)->mutw); + free(*ee); + *ee = NULL; +} + +pbcc_ee_state_t* pbcc_ee_current_state(pbcc_event_engine_t* ee) { pubnub_mutex_lock(ee->mutw); - pbcc_ee_state_t* state = ee->current_state; + pbcc_ee_state_t* state = pbcc_ee_state_copy_(ee->current_state); pubnub_mutex_unlock(ee->mutw); return state; @@ -143,16 +257,16 @@ enum pubnub_res pbcc_ee_handle_event( pbcc_event_engine_t* ee, pbcc_ee_event_t* event) { - pubnub_mutex_lock(ee->mutw); PUBNUB_ASSERT_OPT(NULL != ee->current_state); - const pbcc_ee_transition_t* transition = ee->current_state->transition( + pubnub_mutex_lock(ee->mutw); + pbcc_ee_state_t* state = pbcc_ee_state_copy_(ee->current_state); + pbcc_ee_transition_t* transition = ee->current_state->transition( ee, - ee->current_state, + state, event); - - // Event not needed anymore. - pbcc_ee_event_free(event); + pbcc_ee_state_free(&state); + pbcc_ee_event_free(&event); if (NULL == transition) { PUBNUB_LOG_ERROR("pbcc_ee_handle_event: failed to allocate memory\n"); @@ -160,49 +274,61 @@ enum pubnub_res pbcc_ee_handle_event( return PNR_OUT_OF_MEMORY; } - // Check whether current state should be changed in response to `event` - // or not. + /** + * Check whether current state should be changed in response to `event` + * or not. This is special case of transition to mark that there were no + * error during `transition` function call. + */ if (NULL == transition->target_state) { + pbcc_ee_transition_free(&transition); pubnub_mutex_unlock(ee->mutw); return PNR_OK; } - pbcc_ee_state_t* previous_state = ee->current_state; - ee->current_state = transition->target_state; - // Check whether any invocations expected to be called during transition - // or not. + /** + * Check whether any invocations expected to be called during transition + * or not. + */ if (NULL == transition->invocations) { + pbcc_ee_current_state_set_(ee, transition->target_state); + pbcc_ee_transition_free(&transition); pubnub_mutex_unlock(ee->mutw); return PNR_OK; } - enum pubnub_res rslt = PNR_OK; - const pbarray_t* invocations = transition->invocations; - const size_t count = pbarray_count(invocations); - for (size_t i = 0; i < count; ++i) { - pbcc_ee_invocation_t* inv = _pbcc_ee_invocation_copy( - (pbcc_ee_invocation_t*)pbarray_element_at(invocations, i)); - if (PBAR_OK != pbarray_add(ee->invocations,inv)) { - pbcc_ee_invocation_free(inv); - rslt = PNR_OUT_OF_MEMORY; - } + /** + * Merging transition invocations into Event Engine invocations queue. + */ + enum pubnub_res rslt = PNR_OK; + pbarray_t* invocations = transition->invocations; + const size_t count = pbarray_count(invocations); + const pbarray_res merge_rslt = pbarray_merge(ee->invocations, invocations); + if (PBAR_OK != merge_rslt) { + rslt = PBTT_ADD_MEMBERS == merge_rslt + ? PNR_OUT_OF_MEMORY + : PNR_INVALID_PARAMETERS; } - // Restore previous state if new invocations schedule failed. + /** Restore previous state if new invocations schedule failed. */ if (PNR_OK != rslt) { - for (size_t i = 0; i < count; ++i) { - const pbcc_ee_invocation_t* inv = - pbarray_element_at(invocations, i); - pbarray_remove(ee->invocations, inv, false); - } - ee->current_state = previous_state; - } else { pbcc_ee_state_free(previous_state); } + pbarray_subtract(ee->invocations, invocations, true); + } + else { pbcc_ee_current_state_set_(ee, transition->target_state); } for (size_t i = 0; i < count; ++i) { - const pbcc_ee_invocation_t* inv = pbarray_element_at(invocations, i); - if (inv->immediate) - inv->effect(inv, inv->data->data, _pbcc_ee_effect_completion); + pbcc_ee_invocation_t* inv = (pbcc_ee_invocation_t*) + pbarray_element_at(invocations, i); + /** + * Make sure that invocation has proper ref count after addition to the + * Event Engine invocations queue from transition. + */ + pbref_counter_increment(inv->counter); + + if (!inv->immediate) { continue; } + + pbcc_ee_invocation_exec_(inv); } + pbcc_ee_transition_free(&transition); pubnub_mutex_unlock(ee->mutw); return rslt; @@ -213,44 +339,48 @@ void pbcc_ee_process_next_invocation( const bool remove_previous) { pubnub_mutex_lock(ee->mutw); - if (remove_previous) { pbarray_remove_element_at(ee->invocations, 0); } - - const pbcc_ee_invocation_t* inv = pbarray_first(ee->invocations); - if (NULL != inv) - inv->effect(inv, inv->data->data, _pbcc_ee_effect_completion); - pubnub_mutex_unlock(ee->mutw); -} + if (remove_previous && pbarray_count(ee->invocations) > 0) { + pbcc_ee_invocation_t* inv = (pbcc_ee_invocation_t*) + pbarray_first(ee->invocations); + /** Remove invocation only if it has running state. */ + if (PBCC_EE_INVOCATION_RUNNING == inv->status) { + inv->status = PBCC_EE_INVOCATION_COMPLETED; + pbarray_remove_element_at(ee->invocations, 0); + } + } -void pbcc_ee_free(pbcc_event_engine_t* ee) -{ - if (NULL == ee) { return; } + if (0 == pbarray_count(ee->invocations)) { + pubnub_mutex_unlock(ee->mutw); + return; + } - pubnub_mutex_lock(ee->mutw); - if (NULL != ee->current_state) { pbcc_ee_state_free(ee->current_state); } + pbcc_ee_invocation_exec_( + (pbcc_ee_invocation_t*)pbarray_first(ee->invocations)); pubnub_mutex_unlock(ee->mutw); - pubnub_mutex_destroy(ee->mutw); - free(ee); } pbcc_ee_data_t* pbcc_ee_data_alloc( - const void* data, + void* data, const pbcc_ee_data_free_function_t data_free) { PBCC_ALLOCATE_TYPE(ee_data, pbcc_ee_data_t, true, NULL); - ee_data->counter = pbref_counter_alloc(); - if (NULL == ee_data->counter) { - PUBNUB_LOG_ERROR("pbcc_ee_data_alloc: failed to allocate memory for " - "reference counter\n"); - free(ee_data); - return NULL; - } - + ee_data->counter = pbref_counter_alloc(); ee_data->data = data; ee_data->data_free = data_free; return ee_data; } +void pbcc_ee_data_free(pbcc_ee_data_t* data) +{ + if (NULL == data) { return; } + + if (0 == pbref_counter_free(data->counter)) { + if (NULL != data->data_free) { data->data_free(data->data); } + free(data); + } +} + const void* pbcc_ee_data_value(const pbcc_ee_data_t* data) { return data->data; @@ -259,141 +389,104 @@ const void* pbcc_ee_data_value(const pbcc_ee_data_t* data) pbcc_ee_data_t* pbcc_ee_data_copy(pbcc_ee_data_t* data) { if (NULL == data) { return NULL; } + pbref_counter_increment(data->counter); return data; } -void pbcc_ee_data_free(pbcc_ee_data_t* data) -{ - if (NULL == data) { return; } - - if (0 == pbref_counter_free(data->counter)) { - if (NULL != data->data_free) { data->data_free((void*)data->data); } - free(data); - } -} - -pbcc_ee_state_t* pbcc_ee_state_alloc(const int type, pbcc_ee_data_t* data) +pbcc_ee_state_t* pbcc_ee_state_alloc( + const int type, + pbcc_ee_data_t* data, + const pbcc_ee_transition_function_t transition) { PBCC_ALLOCATE_TYPE(state, pbcc_ee_state_t, true, NULL); + state->counter = pbref_counter_alloc(); state->type = type; state->data = pbcc_ee_data_copy(data); state->on_enter_invocations = NULL; state->on_exit_invocations = NULL; - state->transition = NULL; + state->transition = transition; return state; } -void pbcc_ee_state_free(pbcc_ee_state_t* state) +void pbcc_ee_state_free(pbcc_ee_state_t** state) { - if (NULL == state) { return; } + if (NULL == state || NULL == *state) { return; } - if (NULL != state->on_enter_invocations) - pbarray_free(state->on_enter_invocations); - if (NULL != state->on_exit_invocations) - pbarray_free(state->on_exit_invocations); - if (NULL != state->data) { pbcc_ee_data_free(state->data); } + if (0 == pbref_counter_free((*state)->counter)) { + if (NULL != (*state)->on_enter_invocations) + pbarray_free(&(*state)->on_enter_invocations); + if (NULL != (*state)->on_exit_invocations) + pbarray_free(&(*state)->on_exit_invocations); + if (NULL != (*state)->data) { pbcc_ee_data_free((*state)->data); } - free(state); + free(*state); + *state = NULL; + } } -enum pubnub_res pbcc_ee_state_add_on_enter_invocation( - pbcc_ee_state_t* state, - const pbcc_ee_invocation_t* invocation) +int pbcc_ee_state_type(const pbcc_ee_state_t* state) { - if (NULL == state->on_enter_invocations) { - state->on_enter_invocations = - pbarray_alloc(1, - PBARRAY_RESIZE_CONSERVATIVE, - PBARRAY_GENERIC_CONTENT_TYPE, - pbcc_ee_invocation_free); - if (NULL == state->on_enter_invocations) { - PUBNUB_LOG_ERROR("pbcc_ee_state_add_on_enter_invocation: failed to " - "allocate memory for `on enter` invocations\n"); - return PNR_OUT_OF_MEMORY; - } - } + return state->type; +} - const pbarray_res result = pbarray_add(state->on_enter_invocations, - invocation); - if (PBAR_OUT_OF_MEMORY == result) { return PNR_OUT_OF_MEMORY; } - if (PBAR_INDEX_OUT_OF_RANGE == result) { return PNR_INVALID_PARAMETERS; } +const pbcc_ee_data_t* pbcc_ee_state_data(const pbcc_ee_state_t* state) +{ + if (NULL == state->data) { return NULL; } + return pbcc_ee_data_copy(state->data); +} - return PNR_OK; +enum pubnub_res pbcc_ee_state_add_on_enter_invocation( + pbcc_ee_state_t* state, + pbcc_ee_invocation_t* invocation) +{ + return pbcc_ee_state_add_invocation_(&state->on_enter_invocations, + invocation); } enum pubnub_res pbcc_ee_state_add_on_exit_invocation( - pbcc_ee_state_t* state, - const pbcc_ee_invocation_t* invocation) + pbcc_ee_state_t* state, + pbcc_ee_invocation_t* invocation) { - if (NULL == state->on_exit_invocations) { - state->on_exit_invocations = - pbarray_alloc(1, - PBARRAY_RESIZE_CONSERVATIVE, - PBARRAY_GENERIC_CONTENT_TYPE, - pbcc_ee_invocation_free); - if (NULL == state->on_enter_invocations) { - PUBNUB_LOG_ERROR("pbcc_ee_state_add_on_exit_invocation: failed to " - "allocate memory for `on exit` invocations\n"); - return PNR_OUT_OF_MEMORY; - } - } - - const pbarray_res result = pbarray_add(state->on_exit_invocations, - invocation); - if (PBAR_OUT_OF_MEMORY == result) { return PNR_OUT_OF_MEMORY; } - if (PBAR_INDEX_OUT_OF_RANGE == result) { return PNR_INVALID_PARAMETERS; } - - return PNR_OK; + return pbcc_ee_state_add_invocation_(&state->on_exit_invocations, + invocation); } pbcc_ee_event_t* pbcc_ee_event_alloc(const int type, pbcc_ee_data_t* data) { PBCC_ALLOCATE_TYPE(event, pbcc_ee_event_t, true, NULL); event->type = type; - event->data = pbcc_ee_data_copy(data); + event->data = data; return event; } -void pbcc_ee_event_free(pbcc_ee_event_t* event) +void pbcc_ee_event_free(pbcc_ee_event_t** event) { - if (NULL == event) { return; } - if (NULL != event->data) { pbcc_ee_data_free(event->data); } - free(event); + if (NULL == event || NULL == *event) { return; } + + if (NULL != (*event)->data) { pbcc_ee_data_free((*event)->data); } + free(*event); + *event = NULL; } pbcc_ee_invocation_t* pbcc_ee_invocation_alloc( const pbcc_ee_effect_function_t effect, - pbcc_ee_data_t* data) + pbcc_ee_data_t* data, + const bool immediate) { PBCC_ALLOCATE_TYPE(invocation, pbcc_ee_invocation_t, true, NULL); - invocation->counter = pbref_counter_alloc(); - if (NULL == invocation->counter) { - PUBNUB_LOG_ERROR("pbcc_ee_invocation_alloc: failed to allocate memory " - "for reference counter\n"); - free(invocation); - return NULL; - } - pbref_counter_increment(invocation->counter); - invocation->immediate = false; + invocation->immediate = immediate; + invocation->counter = pbref_counter_alloc(); + invocation->status = PBCC_EE_INVOCATION_CREATED; invocation->effect = effect; invocation->data = pbcc_ee_data_copy(data); return invocation; } -pbcc_ee_invocation_t* _pbcc_ee_invocation_copy( - pbcc_ee_invocation_t* invocation) -{ - if (NULL == invocation) { return NULL; } - pbref_counter_increment(invocation->counter); - - return invocation; -} - void pbcc_ee_invocation_free(pbcc_ee_invocation_t* invocation) { if (NULL == invocation) { return; } @@ -405,17 +498,18 @@ void pbcc_ee_invocation_free(pbcc_ee_invocation_t* invocation) } pbcc_ee_transition_t* pbcc_ee_transition_alloc( - const pbcc_event_engine_t* ee, - pbcc_ee_state_t* state, - const pbarray_t* invocations) + pbcc_event_engine_t* ee, + pbcc_ee_state_t* state, + pbarray_t* invocations) { PUBNUB_ASSERT_OPT(NULL != ee->current_state); + pubnub_mutex_lock(ee->mutw); const pbcc_ee_state_t* current_state = ee->current_state; PBCC_ALLOCATE_TYPE(transition, pbcc_ee_transition_t, true, NULL); - transition->target_state = state; + transition->target_state = pbcc_ee_state_copy_(state); - // Gather transition invocations list. + /** Gather transition invocations list. */ size_t invocations_count = 0; if (NULL != current_state->on_exit_invocations) invocations_count += pbarray_count(current_state->on_exit_invocations); @@ -428,41 +522,125 @@ pbcc_ee_transition_t* pbcc_ee_transition_alloc( transition->invocations = pbarray_alloc(invocations_count, PBARRAY_RESIZE_NONE, PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free) pbcc_ee_invocation_free); - if (NULL == transition->invocations) { + pbarray_t* on_exit = current_state->on_exit_invocations; + pbarray_t* on_enter = state->on_enter_invocations; + + if (NULL == transition->invocations || + !pbcc_ee_transition_add_invocations_(transition, on_exit) || + !pbcc_ee_transition_add_invocations_(transition, invocations) || + !pbcc_ee_transition_add_invocations_(transition, on_enter)) { PUBNUB_LOG_ERROR( "pbcc_ee_transition_alloc: failed to allocate memory " "for transition invocations\n"); - pbcc_ee_transition_free(transition); + if (NULL != invocations) { pbarray_free(&invocations); } + pbcc_ee_transition_free(&transition); + pubnub_mutex_unlock(ee->mutw); return NULL; } - - pbarray_merge(transition->invocations, - current_state->on_exit_invocations); - pbarray_merge(transition->invocations, invocations); - pbarray_merge(transition->invocations, state->on_enter_invocations); } else { transition->invocations = NULL; } + if (NULL != invocations) { pbarray_free(&invocations); } + pubnub_mutex_lock(ee->mutw); return transition; } -void pbcc_ee_transition_free(pbcc_ee_transition_t* transition) +void pbcc_ee_transition_free(pbcc_ee_transition_t** transition) +{ + if (NULL == transition || NULL == *transition) { return; } + + pbcc_ee_state_free(&(*transition)->target_state); + pbarray_free(&(*transition)->invocations); + free(*transition); + *transition = NULL; +} + +void pbcc_ee_current_state_set_(pbcc_event_engine_t* ee, pbcc_ee_state_t* state) +{ + pbcc_ee_state_free(&ee->current_state); + ee->current_state = pbcc_ee_state_copy_(state); +} + +pbcc_ee_state_t* pbcc_ee_state_copy_(pbcc_ee_state_t* state) +{ + if (NULL == state) { return NULL; } + + /** + * Copying is done by making sure that an instance won't be freed until last + * reference to it will be freed. + */ + pbref_counter_increment(state->counter); + + return state; +} + +enum pubnub_res pbcc_ee_state_add_invocation_( + pbarray_t** invocations, + pbcc_ee_invocation_t* invocation) +{ + if (NULL == *invocations) { + *invocations = pbarray_alloc(1, + PBARRAY_RESIZE_CONSERVATIVE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free) + pbcc_ee_invocation_free); + if (NULL == *invocations) { + PUBNUB_LOG_ERROR("pbcc_ee_state_add_invocation: failed to allocate " + "memory for invocations\n"); + return PNR_OUT_OF_MEMORY; + } + } + + const pbarray_res result = pbarray_add(*invocations, invocation); + if (PBAR_OUT_OF_MEMORY == result) { return PNR_OUT_OF_MEMORY; } + if (PBAR_INDEX_OUT_OF_RANGE == result) { return PNR_INVALID_PARAMETERS; } + + return PNR_OK; +} + +bool pbcc_ee_transition_add_invocations_( + const pbcc_ee_transition_t* transition, + pbarray_t* invocations) { - if (NULL == transition) { return; } + if (NULL == invocations) { return true; } + if (PBAR_OK != pbarray_merge(transition->invocations, invocations)) + return false; + + const size_t count = pbarray_count(invocations); + for (size_t i = 0; i < count; i++) { + const pbcc_ee_invocation_t* invocation = + pbarray_element_at(invocations, i); + pbref_counter_increment(invocation->counter); + } + + return true; +} - pbcc_ee_state_free(transition->target_state); - pbarray_free(transition->invocations); - free(transition); +void pbcc_ee_invocation_exec_(pbcc_ee_invocation_t* invocation) +{ + /** Check whether invocation still runnable or not. */ + if (PBCC_EE_INVOCATION_CREATED != invocation->status) { return; } + + invocation->status = PBCC_EE_INVOCATION_RUNNING; + invocation->effect( + invocation, + invocation->data, + (pbcc_ee_effect_completion_function_t)pbcc_ee_effect_completion_); } -void _pbcc_ee_effect_completion( - pbcc_event_engine_t* ee, - const pbcc_ee_invocation_t* invocation) +void pbcc_ee_effect_completion_( + pbcc_event_engine_t* ee, + pbcc_ee_invocation_t* invocation) { + /** Check whether invocation is running or not. */ + if (PBCC_EE_INVOCATION_RUNNING != invocation->status) { return; } + pubnub_mutex_lock(ee->mutw); - pbarray_remove(ee->invocations, invocation, true); + invocation->status = PBCC_EE_INVOCATION_COMPLETED; + pbarray_remove(ee->invocations, (void**)&invocation, true); pubnub_mutex_unlock(ee->mutw); pbcc_ee_process_next_invocation(ee, false); } \ No newline at end of file diff --git a/core/pbcc_event_engine.h b/core/pbcc_event_engine.h index 4f7404e6..111ad898 100644 --- a/core/pbcc_event_engine.h +++ b/core/pbcc_event_engine.h @@ -1,9 +1,10 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PBCC_EVENT_ENGINE_H #define PBCC_EVENT_ENGINE_H /** - * @file pbcc_event_engine.h + * @file pbcc_event_engine.h * @brief Event Engine core implementation with base type definitions and * functions. */ @@ -16,7 +17,7 @@ // Types forwarding // ---------------------------------------------- -// Event Engine definition. +/** Event Engine definition. */ typedef struct pbcc_event_engine pbcc_event_engine_t; /** @@ -35,23 +36,23 @@ typedef struct pbcc_ee_transition pbcc_ee_transition_t; */ typedef struct pbcc_ee_invocation pbcc_ee_invocation_t; -// Event Engine state event forward declaration. +/** Event Engine state event forward declaration. */ typedef struct pbcc_ee_event pbcc_ee_event_t; -// Event Engine state forward declaration. +/** Event Engine state forward declaration. */ typedef struct pbcc_ee_state pbcc_ee_state_t; -// User-provided data forward declaration. +/** User-provided data forward declaration. */ typedef struct pbcc_ee_data pbcc_ee_data_t; -// ---------------------------------- -// Types -// ---------------------------------- +// ---------------------------------------------- +// Types +// ---------------------------------------------- /** * @brief Transition computation function definition. -* + * * @param ee Pointer to the Event Engine which will be requesting * for transition details in response to event. * @param current_state Pointer to the current Event Engine state. @@ -64,7 +65,7 @@ typedef struct pbcc_ee_data pbcc_ee_data_t; * avoid a memory leak. */ typedef pbcc_ee_transition_t* (*pbcc_ee_transition_function_t)( - const pbcc_event_engine_t* ee, + pbcc_event_engine_t* ee, const pbcc_ee_state_t* current_state, const pbcc_ee_event_t* event); @@ -72,8 +73,8 @@ typedef pbcc_ee_transition_t* (*pbcc_ee_transition_function_t)( * @brief User-provided data (`pbcc_ee_data_t`) destruction function * definition. * - * @param data Pointer to the user-provided data, which should free up used - * resources. + * @param data Pointer to the user-provided data pointer, which should free up + * used resources. */ typedef void (*pbcc_ee_data_free_function_t)(void* data); @@ -107,13 +108,13 @@ typedef void (*pbcc_ee_effect_completion_function_t)( */ typedef void (*pbcc_ee_effect_function_t)( const pbcc_ee_invocation_t* invocation, - const void* data, + pbcc_ee_data_t* data, pbcc_ee_effect_completion_function_t cb); -// Event Engine state event definition. +/** Event Engine state event definition. */ struct pbcc_ee_event { - // Specific Event Engine event type. + /** Specific Event Engine event type. */ int type; /** * @brief Pointer to the additional information associated with a specific @@ -122,80 +123,66 @@ struct pbcc_ee_event pbcc_ee_data_t* data; }; -// Event Engine state definition. -struct pbcc_ee_state -{ - // A specific Event Engine state type. - int type; - /** - * @brief Pointer to the additional information associated with a specific - * Event Engine state. - */ - void* data; - /** - * @brief Pointer to the invocations, which should be called when the - * Event Engine is about to enter a specific state. - * - * \b Important: `pbarray_t` should be created without elements destructor - * to let `pbcc_ee_transition_t` manage invocation objects' life-cycle. - */ - pbarray_t* on_enter_invocations; - /** - * @brief Pointer to the invocations, which should be called when the - * Event Engine is about to leave a specific state. - * - * \b Important: `pbarray_t` should be created without elements destructor - * to let `pbcc_ee_transition_t` manage invocation objects' life-cycle. - */ - pbarray_t* on_exit_invocations; - /** - * @brief State-specific transition function to compute target state and - * invocations to call. - */ - pbcc_ee_transition_function_t transition; -}; - -// ---------------------------------- -// Functions -// ---------------------------------- +// ---------------------------------------------- +// Functions +// ---------------------------------------------- /** * @brief Create Event Engine with initial state. * * @param initial_state Pointer to the state from which all events handling and * transition should start. -* @return Pointer to the ready to use Event Engine or `NULL` in case of -* insufficient memory error. The returned pointer must be passed to -* the `pbcc_ee_free` to avoid a memory leak. + * @return Pointer to the ready to use Event Engine or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to + * the `pbcc_ee_free` to avoid a memory leak. */ pbcc_event_engine_t* pbcc_ee_alloc(pbcc_ee_state_t* initial_state); /** - * @brief Retrieve current Event Engine state. + * @brief Clean up resources used by the Event Engine object. + * + * \b Important: Provided event engine pointer will be NULLified. + * + * @param ee Pointer to the Event Engine, which should free up used resources. + */ +void pbcc_ee_free(pbcc_event_engine_t** ee); + +/** + * @brief Retrieve copy of the current Event Engine state. + * + * @note Copies share state and update reference counter to reflect active + * entities. * * @param ee Pointer to the Event Engine for which current state should be * returned. - * @return The current state in which the Event Engine is at this moment. + * @return The current state in which the Event Engine is at this moment. The + * returned pointer must be passed to the `pbcc_ee_state_free` to avoid + * a memory leak. + * + * @see pbcc_ee_state_free */ -pbcc_ee_state_t* pbcc_ee_current_state(const pbcc_event_engine_t* ee); +pbcc_ee_state_t* pbcc_ee_current_state(pbcc_event_engine_t* ee); /** * @brief Handle new event for transition computation. * + * @note Memory allocated for `event` will be managed by the Event Engine. + * * @param ee Pointer to the Event Engine which should process event. - * @param event Event Engine state changing which should be processed. + * @param event Pointer to the Event Engine state changing event object pointer + * which should be processed. * @return Result of event handling operation. */ enum pubnub_res pbcc_ee_handle_event( pbcc_event_engine_t* ee, - const pbcc_ee_event_t* event); + pbcc_ee_event_t* event); /** * @brief Process any scheduled invocations. * - * @param ee Pointer to the Event Engine which should dequeue and process - * next invocation. + * @param ee Pointer to the Event Engine which should dequeue + * and process next invocation. * @param remove_previous Whether current first element should be removed from * the list or not.
\b Important: This flag can be * in case of asynchronous operations when effect @@ -205,16 +192,12 @@ void pbcc_ee_process_next_invocation( pbcc_event_engine_t* ee, bool remove_previous); -/** - * @brief Clean up resources used by the Event Engine object. - * - * @param ee Pointer to the Event Engine, which should free up used resources. - */ -void pbcc_ee_free(pbcc_event_engine_t* ee); - /** * @brief Create sharable user-provided data object. * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * * @param data Pointer to the data which will be shared between * different objects. * @param data_free User-provided data destruction function. @@ -223,9 +206,17 @@ void pbcc_ee_free(pbcc_event_engine_t* ee); * passed to the `pbcc_ee_data_free` to avoid a memory leak. */ pbcc_ee_data_t* pbcc_ee_data_alloc( - const void* data, + void* data, pbcc_ee_data_free_function_t data_free); +/** + * @brief Clean up resources used by the Event Engine shared data object. + * + * @param data Pointer to the Event Engine data, which should free up used + * resources. + */ +void pbcc_ee_data_free(pbcc_ee_data_t* data); + /** * @brief User-provided value. * @@ -238,72 +229,119 @@ const void* pbcc_ee_data_value(const pbcc_ee_data_t* data); /** * @brief Create a copy of existing data. * - * Copies share data and update reference counter to reflect active entities. + * @note Copies share data and update reference counter to reflect active + * entities. * * @param data Pointer to the data object from which copy should be created. - * @return Pointer to the read to use Event Engine data or `NULL` in case of + * @return Pointer to the ready to use Event Engine data or `NULL` in case of * insufficient memory error. The returned pointer must be passed to * the `pbcc_ee_data_free` to avoid a memory leak. */ pbcc_ee_data_t* pbcc_ee_data_copy(pbcc_ee_data_t* data); -/** - * @brief Clean up resources used by the Event Engine shared data object. - * - * @param data Pointer to the Event Engine data, which should free up used - * resources. - */ -void pbcc_ee_data_free(pbcc_ee_data_t* data); - /** * @brief Create Event Engine state object. * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * * @param type Specific Event Engine event type. * @param data Pointer to the additional information associated with a - * specific Event Engine event by event emitting code or `NULL` in - * case of insufficient memory error. The returned pointer must be - * passed to the `pbcc_ee_state_free` to avoid a memory leak. + * specific Event Engine event by event emitting code. + * @param transition State-specific transition function to compute target state + * and invocations to call. * @return Pointer to the Event Engine state. + * + * @see pbcc_ee_state_free + */ +pbcc_ee_state_t* pbcc_ee_state_alloc( + int type, + pbcc_ee_data_t* data, + pbcc_ee_transition_function_t transition); + +/** + * @brief Clean up resources used by event engine state object. + * + * \b Important: Provided event engine state pointer will be NULLified. + * + * @param state Pointer to the state, which should free up resources. */ -pbcc_ee_state_t* pbcc_ee_state_alloc(int type, pbcc_ee_data_t* data); +void pbcc_ee_state_free(pbcc_ee_state_t** state); + +/** + * @brief Get user-specified Event Engine state type. + * + * @note Safely, this function can be used only with state objects received from + * `pbcc_ee_current_state` function because the method returns shallow + * copy and there is no risk of segmentation fault error because of + * attempt to access freed memory. + * + * @param state Pointer to the state for which type should be retrieved. + * @return User-specified state type. + * + * @see pbcc_ee_current_state + */ +int pbcc_ee_state_type(const pbcc_ee_state_t* state); + +/** + * @brief Get data associated with Event Engine state. + * + * @note Safely, this function can be used only with state objects received from + * `pbcc_ee_current_state` function because the method returns shallow + * copy and there is no risk of segmentation fault error because of + * attempt to access freed memory. + * + * @param state Pointer to the state for which associated data should be + * retrieved. + * @return Data associated with state object or `NULL` in case if there is no + * data. The returned pointer must be passed to the `pbcc_ee_data_free` + * to avoid a memory leak. + * + * @see pbcc_ee_current_state + */ +const pbcc_ee_data_t* pbcc_ee_state_data(const pbcc_ee_state_t* state); /** * @brief Add on state \b enter invocation. * + * @note Memory allocated for `invocation` will be managed by the Event Engine. + * * @param state Pointer to the state, for which `on enter` invocation * will be added. * @param invocation Pointer to the invocation which should be called when - * Event Engine will enter `state` as current state. + * Event Engine will enter `state` as current state.
+ * \b Important: Event Engine will manage `invocation` + * memory after successful addition. * @return Result of `on enter` invocations list modification operation. */ enum pubnub_res pbcc_ee_state_add_on_enter_invocation( pbcc_ee_state_t* state, - const pbcc_ee_invocation_t* invocation); + pbcc_ee_invocation_t* invocation); /** * @brief Add on state \b exit invocation. * + * @note Memory allocated for `invocation` will be managed by the Event Engine. + * * @param state Pointer to the state, for which `on exit` invocation * will be added. * @param invocation Pointer to the invocation which should be called when * Event Engine will leave `state` as current state before - * entering new state. + * entering new state.
+ * \b Important: Event Engine will manage `invocation` memory + * after successful addition. * @return Result of `on exit` invocations list modification operation. */ enum pubnub_res pbcc_ee_state_add_on_exit_invocation( pbcc_ee_state_t* state, pbcc_ee_invocation_t* invocation); -/** - * @brief Clean up resources used by event engine state object. - * - * @param state Pointer to the state, which should free up resources. - */ -void pbcc_ee_state_free(pbcc_ee_state_t* state); - /** * @brief Create Event Engine state changing event. * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * * @param type Specific Event Engine event type. * @param data Pointer to the additional information associated with a * specific Event Engine event by the event emitting code. @@ -316,14 +354,19 @@ pbcc_ee_event_t* pbcc_ee_event_alloc(int type, pbcc_ee_data_t* data); /** * @brief Clean up resources used by the event object. * + * \b Important: Provided event pointer will be NULLified. + * * @param event Pointer to the event object which, should free up used * resources. */ -void pbcc_ee_event_free(pbcc_ee_event_t* event); +void pbcc_ee_event_free(pbcc_ee_event_t** event); /** * @brief Create business logic invocation intent object. * + * @note Memory allocated for `data` will be managed by the Event Engine if + * `data` destructor provided (`data_free`). + * * @param effect Function which will be called by dispatcher during * invocation handling. * @param data Pointer to the data which has been associated by business @@ -351,6 +394,9 @@ void pbcc_ee_invocation_free(pbcc_ee_invocation_t* invocation); /** * @brief Create state transition instruction object. * + * @note Memory allocated for `state` and `invocations` will be managed by + * the Event Engine. + * * @param ee Pointer to the Event Engine which will perform * transition. * @param state Pointer to the target Event Engine state. @@ -362,15 +408,17 @@ void pbcc_ee_invocation_free(pbcc_ee_invocation_t* invocation); * leak. */ pbcc_ee_transition_t* pbcc_ee_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* state, - const pbarray_t* invocations); + pbcc_event_engine_t* ee, + pbcc_ee_state_t* state, + pbarray_t* invocations); /** * @brief Clean up resources used by the transition object. * + * @note Function will `NULL`ify provided transition pointer. + * * @param transition Pointer to the transition, which should free up * resources. */ -void pbcc_ee_transition_free(pbcc_ee_transition_t* transition); -#endif //PBCC_EVENT_ENGINE_H +void pbcc_ee_transition_free(pbcc_ee_transition_t** transition); +#endif // #ifndef PBCC_EVENT_ENGINE_H diff --git a/core/pbcc_memory_utils.c b/core/pbcc_memory_utils.c new file mode 100644 index 00000000..36f384aa --- /dev/null +++ b/core/pbcc_memory_utils.c @@ -0,0 +1,17 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_memory_utils.h" + +#include + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +void pbcc_free_ptr(void** ptr) +{ + if (NULL == ptr || NULL == *ptr) return; + + free(*ptr); + *ptr = NULL; +} \ No newline at end of file diff --git a/core/pbcc_memory_utils.h b/core/pbcc_memory_utils.h index 63445d79..57f5cdc1 100644 --- a/core/pbcc_memory_utils.h +++ b/core/pbcc_memory_utils.h @@ -11,6 +11,11 @@ #include "core/pubnub_log.h" +// ---------------------------------------------- +// Macros +// ---------------------------------------------- + + /** * @brief Macro for type allocation and error handling. * @code @@ -41,4 +46,16 @@ } \ return return_value; \ } + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Clean up resources allocated for pointer and set it to `NULL`. + * + * @param ptr Pointer with dynamically allocated resources for clean up. + */ +void pbcc_free_ptr(void** ptr); #endif //PBCC_MEMORY_UTILS_H diff --git a/core/pbcc_request_retry_timer.c b/core/pbcc_request_retry_timer.c new file mode 100644 index 00000000..4a4aec86 --- /dev/null +++ b/core/pbcc_request_retry_timer.c @@ -0,0 +1,202 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_request_retry_timer.h" + +#include +#include +#include + +#include "lib/msstopwatch/msstopwatch.h" +#include "core/pbcc_memory_utils.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" +#include "core/pb_sleep_ms.h" +#include "pubnub_internal.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** + * @brief Polling timeout. + * + * Timeout used with sleep function inside of `while` loop to break constant + * polling and give other processes some CPU time. + */ +#define POLLING_TIMEOUT 10 + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +#if defined _WIN32 +typedef DWORD pubnub_thread_t; +typedef FILETIME pubnub_timespec_t; +typedef void pubnub_watcher_t; +#define thread_handle_field() HANDLE thread_handle; +#define UNIT_IN_MILLI 1000 +#else +typedef pthread_t pubnub_thread_t; +typedef struct timespec pubnub_timespec_t; +typedef void* pubnub_watcher_t; +#define thread_handle_field() +#endif + +/** Failed requests retry timer definition. */ +struct pbcc_request_retry_timer { + /** PubNub context for which timer will restart requests. */ + pubnub_t* pb; +#if defined(PUBNUB_CALLBACK_API) + /** Timer's run thread. */ + pubnub_thread_t timer_thread; +#endif // #if defined(PUBNUB_CALLBACK_API) + /** Whether timer is running at this moment or not. */ + volatile bool running; + /** Active timer delay value in milliseconds. */ + uint16_t delay; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + + +/** + * @brief Run request retry timer. + * + * Launch timer which will await till timeout and try to restart request if + * possible. + * + * @param timer Pointer to the timer which should run. + */ +static void* pbcc_request_retry_timer_run_(pbcc_request_retry_timer_t* timer); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_request_retry_timer_t* pbcc_request_retry_timer_alloc(pubnub_t* pb) +{ + PBCC_ALLOCATE_TYPE(timer, pbcc_request_retry_timer_t, true, NULL); + pubnub_mutex_init(timer->mutw); +#if defined(PUBNUB_CALLBACK_API) + timer->timer_thread = NULL; +#endif // #if defined(PUBNUB_CALLBACK_API) + timer->running = false; + timer->pb = pb; + + return timer; +} + +void pbcc_request_retry_timer_free(pbcc_request_retry_timer_t** timer) +{ + if (NULL == timer || NULL == *timer) { return; } + + pbcc_request_retry_timer_stop(*timer); + pubnub_mutex_destroy((*timer)->mutw); + free(*timer); + *timer = NULL; +} + +void pbcc_request_retry_timer_start( + pbcc_request_retry_timer_t* timer, + const uint16_t delay) +{ + if (NULL == timer || timer->running) { return; } + PUBNUB_ASSERT(pb_valid_ctx_ptr(timer->pb)); + + pubnub_mutex_lock(timer->mutw); + timer->running = true; + timer->delay = delay; + pubnub_mutex_unlock(timer->mutw); + pubnub_mutex_lock(timer->pb->monitor); + timer->pb->state = PBS_WAIT_RETRY; + timer->pb->core.http_retry_count++; + pubnub_mutex_unlock(timer->pb->monitor); + +#if defined(PUBNUB_CALLBACK_API) + if (pthread_create(&timer->timer_thread, + NULL, + (void*(*)(void*))pbcc_request_retry_timer_run_, + timer)) { + PUBNUB_LOG_ERROR( + "pbcc_request_retry_timer_start: unable to create thread"); + + /** + * To not stall FSM we let it retry request right away if thread can't + * be created. + */ + pubnub_mutex_lock(timer->mutw); + timer->running = false; + pubnub_mutex_unlock(timer->mutw); + pubnub_mutex_lock(timer->pb->monitor); + if (PBS_WAIT_RETRY == timer->pb->state) { + timer->pb->state = PBS_RETRY; + pbnc_fsm(timer->pb); + } + pubnub_mutex_unlock(timer->pb->monitor); + } +#else + pbcc_request_retry_timer_run_(timer); +#endif // #if defined(PUBNUB_CALLBACK_API) +} + +void pbcc_request_retry_timer_stop(pbcc_request_retry_timer_t* timer) +{ + if (NULL == timer) { return; } + + pubnub_mutex_lock(timer->mutw); +#if defined(PUBNUB_CALLBACK_API) + if (NULL != timer->timer_thread) { + pthread_join(timer->timer_thread, NULL); + timer->timer_thread = NULL; + } +#endif // #if defined(PUBNUB_CALLBACK_API) + if (!timer->running) { + pubnub_mutex_unlock(timer->mutw); + return; + } + timer->running = false; + pubnub_mutex_unlock(timer->mutw); +} + +void* pbcc_request_retry_timer_run_(pbcc_request_retry_timer_t* timer) +{ + const pbmsref_t t0 = pbms_start(); + pbms_t delta = 0; + bool stopped = false; + + pubnub_mutex_lock(timer->mutw); + const uint16_t delay = timer->delay; + pubnub_mutex_unlock(timer->mutw); + while (delta < delay) { + pubnub_mutex_lock(timer->mutw); + if (!timer->running) { + pubnub_mutex_unlock(timer->mutw); + stopped = true; + break; + } + pubnub_mutex_unlock(timer->mutw); + + delta = pbms_elapsed(t0); + pb_sleep_ms(POLLING_TIMEOUT); + } + + pubnub_mutex_lock(timer->pb->monitor); + if (!stopped && PBS_WAIT_RETRY == timer->pb->state) { + timer->pb->state = PBS_RETRY; + pbnc_fsm(timer->pb); + } + pubnub_mutex_unlock(timer->pb->monitor); + + pubnub_mutex_lock(timer->mutw); + timer->running = false; + pubnub_mutex_unlock(timer->mutw); + + return NULL; +} \ No newline at end of file diff --git a/core/pbcc_request_retry_timer.h b/core/pbcc_request_retry_timer.h new file mode 100644 index 00000000..159bf94d --- /dev/null +++ b/core/pbcc_request_retry_timer.h @@ -0,0 +1,75 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_REQUEST_RETRY_TIMER_H +#define PBCC_REQUEST_RETRY_TIMER_H +#if PUBNUB_USE_RETRY_CONFIGURATION + + +/** + * @file pbcc_request_retry_timer.h + * @brief PubNub request retry timer implementation. + */ + +#include + +#include "core/pubnub_ntf_callback.h" +#include "core/pubnub_api_types.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** + * @brief Failed requests retry timer definition. + * + * Timer basically holds further transactions processing by putting FSM into new + * state for the time of delay. + */ +typedef struct pbcc_request_retry_timer pbcc_request_retry_timer_t; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create a request retry timer. + * + * @param pb Pointer to the PubNub context for which timer will restart request. + * @return Pointer to the ready to use request retry timer or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `pbcc_request_retry_timer_free` to avoid a memory leak. + */ +pbcc_request_retry_timer_t* pbcc_request_retry_timer_alloc(pubnub_t* pb); + +/** + * @brief Release resources used by request retry timer object. + * + * @note Function will NULLify provided request retry timer pointer. + * + * @param timer Pointer to the timer object, which should free up resources. + */ +void pbcc_request_retry_timer_free(pbcc_request_retry_timer_t** timer); + +/** + * @brief Start request retry timer. + * + * This function will be blocking if built without PUBNUB_CALLBACK_API + * precompile macro. + * + * @param timer Pointer to the timer which will start and call completion + * handler on timeout. + * @param delay Timer timeout in milliseconds. + */ +void pbcc_request_retry_timer_start( + pbcc_request_retry_timer_t* timer, + uint16_t delay); + +/** + * @brief Stop request retry timer. + * + * @param timer Pointer to the timer which should stop earlier that timeout. + */ +void pbcc_request_retry_timer_stop(pbcc_request_retry_timer_t* timer); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION +#endif // #ifndef PBCC_REQUEST_RETRY_TIMER_H diff --git a/core/pbcc_subscribe_event_engine.c b/core/pbcc_subscribe_event_engine.c index 36bde0a6..199896a3 100644 --- a/core/pbcc_subscribe_event_engine.c +++ b/core/pbcc_subscribe_event_engine.c @@ -1,3 +1,4 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pbcc_subscribe_event_engine.h" #include @@ -5,15 +6,18 @@ #include #include -#include "pbcc_memory_utils.h" -#include "pbcc_subscribe_event_listener.h" #include "core/pubnub_subscribe_event_engine_internal.h" -#include "core/pubnub_internal_common.h" +#include "core/pbcc_subscribe_event_engine_states.h" +#include "core/pbcc_subscribe_event_engine_events.h" +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pbcc_subscribe_event_listener.h" #include "core/pubnub_server_limits.h" #include "core/pbcc_event_engine.h" +#include "core/pbcc_memory_utils.h" #include "core/pubnub_mutex.h" #include "core/pubnub_log.h" #include "pubnub_callback.h" +#include "pubnub_internal.h" #include "lib/pbhash_set.h" #include "lib/pbstrdup.h" @@ -22,139 +26,12 @@ // Constants // ---------------------------------------------- -// How many subscribable objects `pbcc_subscribe_ee_t` can hold by default. +/** How many subscribable objects `pbcc_subscribe_ee_t` can hold by default. */ #define SUBSCRIBABLE_LENGTH 10 // ---------------------------------------------- -// Types -// ---------------------------------------------- - -// Subscribe Event Engine events. -typedef enum { - // Subscription list of channels / groups change event. - SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED, - // Subscription restore / catchup event. - SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED, - // Initial subscription handshake success event. - SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS, - // Initial subscription handshake failure event. - SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE, - // Real-time updates receive success event. - SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS, - // Real-time updates receive failure event. - SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE, - // Disconnect from real-time updates event. - SUBSCRIBE_EE_EVENT_DISCONNECT, - // Reconnect for real-time updates event. - SUBSCRIBE_EE_EVENT_RECONNECT, - // Unsubscribe from all channel / groups. - SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL, -} pbcc_subscribe_ee_event; - -// Subscribe Event Engine states. -typedef enum { - /** - * @brief Unknown state. - * - * Placeholder state type without actual implementation which won't be - * treated as insufficient memory error. - */ - SUBSCRIBE_EE_STATE_NONE, - // Inactive Subscribe Event Engine state. - SUBSCRIBE_EE_STATE_UNSUBSCRIBED, - // Initial subscription state. - SUBSCRIBE_EE_STATE_HANDSHAKING, - // Initial subscription failed state. - SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED, - // Initial subscription stopped state. - SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED, - // Real-time updates receiving state. - SUBSCRIBE_EE_STATE_RECEIVING, - // Real-time updates receive failed state. - SUBSCRIBE_EE_STATE_RECEIVE_FAILED, - // Real-time updates receive stopped state. - SUBSCRIBE_EE_STATE_RECEIVE_STOPPED, -} pbcc_subscribe_ee_state; - -// Subscribe event engine structure. -struct pbcc_subscribe_ee { - /** - * @brief Pointer to the set of subscribable objects which should be used in - * subscription loop. - */ - pbhash_set_t* subscribables; - /** - * @brief Pointer to the list of active subscription sets. - * - * List of subscription sets which should be used in subscription loop and - * listen for real-time updates. - */ - pbarray_t* subscription_sets; - /** - * @brief List of active subscriptions. - * - * List of subscriptions which should be used in subscription loop and - * listen for real-time updates. - */ - pbarray_t* subscriptions; - /** - * @brief How long (in seconds) the server will consider the client alive - * for presence. - */ - unsigned heartbeat; - // Pointer to the real-time updates filtering expression. - char* filter_expr; - // Recent subscription status. - pubnub_subscription_status status; - // Subscribe Event Listener. - pbcc_event_listener_t* event_listener; - /** - * @brief Pointer to the Event Engine which handles all states and - * transitions on events. - */ - pbcc_event_engine_t* ee; - /** - * @brief Pointer to the PubNub context, which should be used for effects - * execution by Subscribe Event Engine effects dispatcher. - */ - pubnub_t* pb; - // Shared resources access lock. - pubnub_mutex_t mutw; -}; - -/** - * @brief Events and state context object. - * - * Context object contains information related required for proper - * Subscribe Event Engine operation. States and events contain event engine - * context 'snapshot' at the moment when they have been created. - */ -typedef struct { - /** - * @brief Pointer to the comma-separated channels which are or will be used - * in subscription loop. - */ - pbcc_ee_data_t* channels; - /** - * @brief Pointer to the comma-separated channel groups which are or will be - * used in subscription loop. - */ - pbcc_ee_data_t* channel_groups; - // Subscription cursor which is or will be used in subscription loop. - const pubnub_subscribe_cursor_t cursor; - // Previous request failure reason. - enum pubnub_res reason; - /** - * @brief Pointer to the PubNub context, which should be used for effects - * execution by Subscribe Event Engine effects dispatcher. - */ - pubnub_t* pb; -} pbcc_subscribe_ee_context_t; - - -// ---------------------------------------------- -// Function prototypes: Event Engine +// Function prototypes // ---------------------------------------------- /** @@ -165,22 +42,19 @@ typedef struct { * Updated list of subscribables will be used to trigger proper event for * subscription loop. * - * @param ee Pointer to the Subscribe Event Engine, which - * should transit to `receiving` state. - * @param cursor Pointer to the subscription cursor to be used - * with subscribe REST API. The SDK will try to - * catch up on missed messages if a cursor with - * older PubNub high-precision timetoken has been - * provided. Pass `NULL` to keep using cursor - * received from the previous subscribe REST API - * response. - * @param update Whether list of subscribable objects should be - * updated or not (for unsubscribe, it updated - * beforehand). + * @param ee Pointer to the Subscribe Event Engine, which should transit to + * `receiving` state. + * @param cursor Pointer to the subscription cursor to be used with subscribe + * REST API. The SDK will try to catch up on missed messages if a + * cursor with older PubNub high-precision timetoken has been + * provided. Pass `NULL` to keep using cursor received from the + * previous subscribe REST API response. + * @param update Whether list of subscribable objects should be updated or not + * (for unsubscribe, it updated beforehand). * @return Result of subscribe enqueue transaction. */ -static enum pubnub_res _pbcc_subscribe_ee_subscribe( - const pbcc_subscribe_ee_t* ee, +static enum pubnub_res pbcc_subscribe_ee_subscribe_( + pbcc_subscribe_ee_t* ee, const pubnub_subscribe_cursor_t* cursor, bool update); @@ -188,8 +62,8 @@ static enum pubnub_res _pbcc_subscribe_ee_subscribe( * @brief Transit to `handshaking` / `receiving` or `unsubscribed` state. * * Depending on from whether there are any channels left for the next - * subscription loop can be created, `SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL` event instead of - * `EVENT_SUBSCRIPTION_CHANGE`. + * subscription loop can be created, `SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL` event + * instead of `EVENT_SUBSCRIPTION_CHANGE`. * * @param ee Pointer to the Subscribe Event Engine, which should * transit to `handshaking` / `receiving` or `unsubscribed` @@ -198,20 +72,16 @@ static enum pubnub_res _pbcc_subscribe_ee_subscribe( * requested to unsubscribed. * @return Result of unsubscribe enqueue transaction. */ -static enum pubnub_res _pbcc_subscribe_ee_unsubscribe( - const pbcc_subscribe_ee_t* ee, - pbhash_set_t* subscribables); +static enum pubnub_res pbcc_subscribe_ee_unsubscribe_( + pbcc_subscribe_ee_t* ee, + pbhash_set_t* subscribables); /** - * @berief Actualize list of subscribable objects. + * @brief Actualize list of subscribable objects. * * Update list of subscribable objects using `active` subscriptions and * subscription sets. * - * @param ee Pointer to the Subscribe Event Engine for which list of - * subscribable objects should be updated. - * @return Result of subscribable list update. - * * @note Because of subscription / subscription set unsubscribe process, it is * impossible to implement subscribable update in * `_subscribe` / `_unsubscribe` methods. Subscribable addition, won't be @@ -220,11 +90,19 @@ static enum pubnub_res _pbcc_subscribe_ee_unsubscribe( * another subscription object from subscription set tried to do the same. * As a result, the object may be removed from the subscription loop. * + * @param ee Pointer to the Subscribe Event Engine for which + * list of subscribable objects should be updated. + * @param excluded_subscribables Pointer to the set of subscribables which + * shouldn't be part of resulting subscribables + * list. + * @return Result of subscribable list update. + * * @see pubnub_subscription_t * @see pubnub_subscription_set_t */ -static enum pubnub_res _pbcc_subscribe_ee_update_subscribables( - const pbcc_subscribe_ee_t* ee); +static enum pubnub_res pbcc_subscribe_ee_update_subscribables_( + const pbcc_subscribe_ee_t* ee, + pbhash_set_t* excluded_subscribables); /** * @brief Update Subscribe Event Engine list of subscribable objects. @@ -236,9 +114,9 @@ static enum pubnub_res _pbcc_subscribe_ee_update_subscribables( * for subscription loop. * @return Result of subscribables addition operation. */ -static enum pubnub_res _pbcc_subscribe_ee_add_subscribables( +static enum pubnub_res pbcc_subscribe_ee_add_subscribables_( const pbcc_subscribe_ee_t* ee, - const pbhash_set_t* subscribables); + pbhash_set_t* subscribables); /** * @brief Get list of channel and group identifiers from provided subscribables. @@ -258,78 +136,12 @@ static enum pubnub_res _pbcc_subscribe_ee_add_subscribables( * included into `channels` and `channel_groups`. * @return Result of subscribables computation operation. */ -static enum pubnub_res _pbcc_subscribe_ee_subscribables( +static enum pubnub_res pbcc_subscribe_ee_subscribables_( pbhash_set_t* subscribables, char** channels, char** channel_groups, bool include_presence); -/** - * @brief Create Subscribe Event Engine context object. - * - * @param pb PubNub context, which should be used for - * effects execution by Subscribe Event Engine - * effects dispatcher. - * @param [in,out] channels Pointer to the byte string with - * comma-separated of channels which should be - * passed between states in context object. - * @param [in,out] channel_groups Pointer to the byte string with - * comma-separated of channel groups which should - * be passed between states in context object. - * @return Pointer to the ready to use Subscribe Event Engine context object, or - * `NULL` in case of insufficient memory error. The returned pointer - * must be passed to the `_pbcc_subscribe_ee_context_free` to avoid a - * memory leak. - */ -static pbcc_subscribe_ee_context_t* _pbcc_subscribe_ee_context_alloc( - pubnub_t* pb, - char** channels, - char** channel_groups); - -/** - * @brief Copy source context with `channels` and `channel_groups` override. -* - * @param pb PubNub context, which should be used for - * effects execution by Subscribe Event Engine - * effects dispatcher. - * @param ctx Pointer to the context, from which data will - * be copied to the next Subscribe Event Engine - * context. - * @param [in,out] channels Pointer to the byte string with - * comma-separated channels which should be used - * instead of `channels` from provided context. - * @param [in,out] channel_groups Pointer to the byte string with - * comma-separated channel groups which should be - * used instead of `channel_groups` from provided - * context. - * @return Pointer to the ready to use Subscribe Event Engine context created - * from the data of source context, or `NULL` in case of insufficient - * memory error. The returned pointer must be passed to the - * `_pbcc_subscribe_ee_context_free` to avoid a memory leak. - */ -static pbcc_subscribe_ee_context_t* _pbcc_subscribe_ee_context_copy( - pubnub_t* pb, - const pbcc_subscribe_ee_context_t* ctx, - char** channels, - char** channel_groups); - -/** - * @brief Get current state context. - * - * @param ee Pointer to the Subscribe Event Engine, which should provide current - * state object. - * @return Current Subscribe Event Engine context. - */ -static pbcc_subscribe_ee_context_t* _pbcc_subscribe_ee_current_state_context( - const pbcc_subscribe_ee_t* ee); - -/** - * @brief Clean up resources used by subscription context. - * - * @param ctx Pointer to the context, which should free up resources. - */ -static void _pbcc_subscribe_ee_context_free(pbcc_subscribe_ee_context_t* ctx); - /** * @brief Handle real-time updates from subscription loop. * @@ -339,601 +151,13 @@ static void _pbcc_subscribe_ee_context_free(pbcc_subscribe_ee_context_t* ctx); * @param result Result of `trans` processing * @param user_data Pointer to the Subscribe Event Engine object. */ -static void _pbcc_subscribe_callback( +static void pbcc_subscribe_callback_( pubnub_t* pb, enum pubnub_trans trans, enum pubnub_res result, const void* user_data); -// ---------------------------------------------- -// Function prototypes: State -// ---------------------------------------------- - -/** - * @brief Inactive Subscribe Event Engine state. - * - * State with which Subscribe Event Engine will be initialized and end up - * when there will be no channels or groups to subscribe to. - * - * @return Pointer to the `Unsubscribed` state. - */ -static pbcc_ee_state_t* _pbcc_unsubscribed_state_alloc(void); - -/** - * @brief Initial subscription state. - * - * State, which is used to perform initial subscription to receive the next - * subscription loop time token (cursor) and notify channels / groups subscribes - * about change in subscriber's presence. - * - * @param context Pointer to the context with updated Subscribe Event Engine - * information which should be used for the next subscription - * loop. - * @return Pointer to the `Handshaking state`, which should be used as - * Subscribe Event Engine state transition target state. - */ -static pbcc_ee_state_t* _pbcc_handshaking_state_alloc(pbcc_ee_data_t* context); - -/** - * @brief Initial subscription failed state. - * - * State, which is used by the Subscribe Event Engine when the last - * subscription loop can't be retried anymore. - * - * @param context Pointer to the context with list of channels / groups and time - * token (cursor) which has been used during last initial - * subscription before receiving operation failure result. - * @return Pointer to the `Handshake failed state`, which should be used as - * Subscribe Event Engine state transition target state. - */ -static pbcc_ee_state_t* _pbcc_handshake_failed_state_alloc( - pbcc_ee_data_t* context); - -/** - * @brief Initial subscription stopped state. - * - * State, which is used by the Subscribe Event Engine to preserve its last - * context while disconnected, and awaiting its reconnection. - * - * @param context Pointer to the context with list of channels / groups and time - * token (cursor) which has been used during last initial - * subscription. - * @return Pointer to the `Handshake stopped state`, which should be used as - * Subscribe Event Engine state transition target state. - */ -static pbcc_ee_state_t* _pbcc_handshake_stopped_state_alloc( - pbcc_ee_data_t* context); - -/** - * @brief Real-time updates receiving state. - * - * State, which is used by the Subscribe Event Engine to perform a long-poll - * subscription loop to receive real-time updates. - * - * @param context Pointer to the context with list of channels / groups and time - * token (cursor) received from previous subscription loop to - * receive next real-time updates. - * @return Pointer to the `Receiving real-time updates state`, which should be - * used as Subscribe Event Engine state transition target state. - */ -static pbcc_ee_state_t* _pbcc_receiving_state_alloc(pbcc_ee_data_t* context); - -/** - * @brief Real-time updates receive failed state. - * - * State, which is used by the Subscribe Event Engine when the last long-poll - * subscription loop can't be retried anymore. - * - * @param context Pointer to the context with list of channels / groups and time - * token (cursor) received from previous subscription loop to - * receive next real-time updates before receiving operation - * failure result. - * @return Pointer to the `Receive real-time updates failed state`, which should - * be used as Subscribe Event Engine state transition target state. - */ -static pbcc_ee_state_t* _pbcc_receive_failed_state_alloc( - pbcc_ee_data_t* context); - -/** - * @brief Real-time updates receive stopped state. - * - * State, which is used by the Subscribe Event Engine to preserve its last - * context used for long-poll subscription loop while disconnected, and awaiting - * its reconnection. - * - * @param context Pointer to the context with list of channels / groups and time - * token (cursor) received from previous subscription loop to - * receive next real-time updates. - * @return Pointer to the `Receive real-time updates stopped state`, which - * should be used as Subscribe Event Engine state transition target - * state. - */ -static pbcc_ee_state_t* _pbcc_receive_stopped_state_alloc( - pbcc_ee_data_t* context); - - -// ---------------------------------------------- -// Function prototypes: Event -// ---------------------------------------------- - -/** - * @brief Subscription list of channels / groups change event. - * - * This event will be emitted each time when user would like to add new or - * remove already active channels / channel groups. - * - * @note State and subscription context objects will handle further memory - * management of the provided channels and channel groups byte string - * pointers. - * - * @param ee Pointer to the Subscribe Event Engine for - * which will process the event. - * @param [in,out] channels Pointer to the byte sting of comma-separated - * channels from which PubNub client should - * receive real-time updates. - * @param [in,out] channel_groups Pointer to the byte sting of comma-separated - * channel groups from which PubNub client should - * receive real-time updates. - * @return Pointer to the `Subscription change event`, which will be processed - * by the Subscribe Event Engine to get transition instructions from the - * current state. - */ -static pbcc_ee_event_t* _pbcc_subscription_changed_event_alloc( - const pbcc_subscribe_ee_t* ee, - char** channels, - char** channel_groups); - -/** - * @brief Subscription restore / catchup event. - * - * This event will be emitted each time when user would like to add new or - * remove already active channels / channel groups with specific time token - * (cursor) to catch up messages from. - * - * @note State and subscription context objects will handle further memory - * management of the provided channels and channel groups byte string - * pointers. - * - * @param ee Pointer to the Subscribe Event Engine for - * which will process the event. - * @param [in,out] channels Pointer to the byte string of comma-separated - * channels from which PubNub client should - * receive real-time updates. - * @param [in,out] channel_groups Pointer to the byte string of comma-separated - * channel groups from which PubNub client should - * receive real-time updates. - * @param cursor Time token to which PubNub client should try - * to restore subscription (catch up on missing - * messages) loop. - * @return Pointer to the `Subscription restore event`, which will be processed - * by the Subscribe Event Engine to get transition instructions from the - * current state. - */ -static pbcc_ee_event_t* _pbcc_subscription_restored_event_alloc( - const pbcc_subscribe_ee_t* ee, - char** channels, - char** channel_groups, - pubnub_subscribe_cursor_t cursor); - -/** - * @brief Initial subscription handshake success event. - * - * This event will be emitted each time when initial subscription to the set of - * channels / groups will complete with a service response which contains the - * time token (cursor) for the next subscription loop. - * - * @param ee Pointer to the Subscribe Event Engine for which will process - * the event. - * @param cursor Time token, which should be used with next subscription loop. - * @return Pointer to the `Subscription handshake success event`, which will be - * processed by the Subscribe Event Engine to get transition - * instructions from the current state. - */ -static pbcc_ee_event_t* _pbcc_handshake_success_event_alloc( - const pbcc_subscribe_ee_t* ee, - pubnub_subscribe_cursor_t cursor); - -/** - * @brief Initial subscription handshake failure event. - * - * This event will be emitted each time when initial subscription to the set of - * channels / groups fails. Failure in most of the cases caused by permissions - * and network issues. - * - * @param ee Pointer to the Subscribe Event Engine for which will process - * the event. - * @param reason Subscription processing result code which explains failure - * reason. - * @return Pointer to the `Subscription handshake failure event`, which will be - * processed by the Subscribe Event Engine to get transition - * instructions from the current state. - */ -static pbcc_ee_event_t* _pbcc_handshake_failure_event_alloc( - const pbcc_subscribe_ee_t* ee, - enum pubnub_res reason); - -/** - * @default Real-time updates receive success event. - * - * This event will be emitted each time when subscription receives a response - * from PubNub service. - * - * @param ee Pointer to the Subscribe Event Engine for which will process - * the event. - * @param cursor Time token, which should be used with next subscription loop. - * @return Pointer to the `Subscription receive success event`, which will be - * processed by the Subscribe Event Engine to get transition - * instructions from the current state. - */ -static pbcc_ee_event_t* _pbcc_receive_success_event_alloc( - const pbcc_subscribe_ee_t* ee, - pubnub_subscribe_cursor_t cursor); - -/** -* @brief Real-time updates receive failure event. - * - * @param ee Pointer to the Subscribe Event Engine for which will process - * the event. - * @param reason Subscription processing result code which explain failure - * reason. - * @return Pointer to the `Subscription receive failure event`, which will be - * processed by the Subscribe Event Engine to get transition - * instructions from the current state. - */ -static pbcc_ee_event_t* _pbcc_receive_failure_event_alloc( - const pbcc_subscribe_ee_t* ee, - enum pubnub_res reason); - -/** - * @brief Disconnect from real-time updates event. - * - * @note List of channels / groups and active time token (cursor) will be - * saved for subscription restore. - * - * @param ee Pointer to the Subscribe Event Engine for which will process the - * event. - * @return Pointer to the `Disconnect event`, which will be processed by the - * Subscribe Event Engine to get transition instructions from the - * current state. - */ -static pbcc_ee_event_t* _pbcc_disconnect_event_alloc( - const pbcc_subscribe_ee_t* ee); - -/** - * @brief Reconnect for real-time updates event. - * - * @note Subscription loop will use time token (cursor) which has been in - * use before disconnection / failure. - * - * @param ee Pointer to the Subscribe Event Engine for which will process - * the event. - * @param cursor Time token, which should be used with next subscription - * REST API loop call. - * @return Pointer to the `Reconnect event`, which will be processed by the - * Subscribe Event Engine to get transition instructions from the - * current state. - */ -static pbcc_ee_event_t* _pbcc_reconnect_event_alloc( - const pbcc_subscribe_ee_t* ee, - pubnub_subscribe_cursor_t cursor); - -/** - * @brief Unsubscribe from all channel / groups event. - * - * Unsubscribe from all real-time updates and notify other channel / groups - * subscribers about client presence change (`leave`). - * - * @note The list of channels / groups and used time token (cursor) will be - * reset, and further real-time updates can't be received without a - * `subscribe` call. - * @note State and subscription context objects will handle further memory - * management of the provided channels and channel groups byte string - * pointers. - * - * @param ee Pointer to the Subscribe Event Engine for - * which will process the event. - * @param [in,out] channels Pointer to the byte sting of comma-separated - * channels from which PubNub client should - * receive real-time updates. - * @param [in,out] channel_groups Pointer to the byte string of comma-separated - * channel groups from which PubNub client should - * receive real-time updates. - * @return Pointer to the `Unsubscribe all event`, which will be processed by - * the Subscribe Event Engine to get transition instructions from the - * current state. - */ -static pbcc_ee_event_t* _pbcc_unsubscribe_all_event_alloc( - const pbcc_subscribe_ee_t* ee, - char** channels, - char** channel_groups); - -/** - * @brief Create event for underlying Event Engine. - * - * @param ee Pointer to the Subscribe Event Engine for - * which will process the event. - * @param event Type of Subscribe Event Engine event. - * @param [in,out] channels Pointer to the byte sting of comma-separated - * channels from which PubNub client should - * receive real-time updates. - * @param [in,out] channel_groups Pointer to the byte string of comma-separated - * channel groups from which PubNub client should - * receive real-time updates. - * @param cursor Time token, which should be used with next - * subscription REST API loop call. - * @param reason Subscription processing result code which - * explain failure reason. - * @return Pointer to requested `event` type, which will be processed by the - * Subscribe Event Engine to get transition instructions from the - * current state. - */ -static pbcc_ee_event_t* _pbcc_subscribe_ee_event_alloc( - const pbcc_subscribe_ee_t* ee, - pbcc_subscribe_ee_event event, - char** channels, - char** channel_groups, - const pubnub_subscribe_cursor_t* cursor, - const enum pubnub_res* reason); - - -// ---------------------------------------------- -// Function prototypes: Transition -// ---------------------------------------------- - -/** - * @brief Create transition from `Unsubscribed` state basing on received - * `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_unsubscribed_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition from `Handshaking` state basing on received `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_handshaking_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition from `Handshake Failed` state basing on received - * `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_handshake_failed_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition from `Handshake Stopped` state basing on received - * `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_handshake_stopped_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition from `Receiving` state basing on received `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_receiving_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition from `Receiving Failed` state basing on received - * `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_receiving_failed_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition from `Receiving Stopped` state basing on received - * `event`. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param current_state Pointer to the current Event Engine state from which - * transition should be decided. - * @param event Pointer to the Event Engine state changing event with - * information required for transition. - * @return Pointer to the state transition object or `NULL` in case of - * insufficient memory error. - */ -static pbcc_ee_transition_t* _pbcc_receiving_stopped_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event); - -/** - * @brief Create transition details for Event Engine. - * - * @param ee Pointer to the Event Engine which requested for - * transition details. - * @param target_state_type Type of target Subscribe Event Engine state. - * @param context Subscribe Event Engine data which should be passed - * to the next state and transition invocations. - * @param invocations Pointer to the list of invocations which should be - * called before Event Engine will enter new state. - * @return Pointer to the ready to use transition details or `NULL` in case of - * insufficient memory error. The returned pointer must be passed to the - * `pbcc_ee_transition_free` to avoid a memory leak. - */ -static pbcc_ee_transition_t* _pbcc_transition_alloc( - const pbcc_event_engine_t* ee, - pbcc_subscribe_ee_state target_state_type, - pbcc_ee_data_t* context, - const pbarray_t* invocations); - - -// ---------------------------------------------- -// Function prototypes: Effect -// ---------------------------------------------- - -/** - * @brief Execute (schedule) initial subscribe to the channels / groups. - * - * Effect scheduled because PubNub context uses a callback interface and status - * will be known only with callback call. - * - * @param invocation Pointer to the invocation which has been used to execute - * effect. - * @param context Pointer to the Subscription context object with information - * for initial subscribe (tt=0). - * @param cb Pointer to the effect execution completion callback - * function. - * @return Handshake effect execution (scheduling) operation result. - */ -static enum pubnub_res _handshake_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - pbcc_ee_effect_completion_function_t cb); - -/** - * @brief Execute (schedule) next subscription loop. - * - * Effect scheduled because PubNub context uses a callback interface and status - * will be known only with callback call. - * - * @param invocation Pointer to the invocation which has been used to execute - * effect. - * @param context Pointer to the Subscription context object with information - * for next subscription loop (tt!=0). - * @param cb Pointer to the effect execution completion callback - * function. - * @return Receive effect execution (scheduling) operation result. - */ -static enum pubnub_res _receive_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - pbcc_ee_effect_completion_function_t cb); - -/** - * @brief Notify status change listeners. - * - * @param invocation Pointer to the invocation which has been used to execute - * effect. - * @param context Pointer to the Subscription context with information which - * is required by the Event Listener to notify all status - * change listeners. - * @param cb Pointer to the effect execution completion callback - * function. - * @return Status change emit operation result. - */ -static enum pubnub_res _emit_status_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - pbcc_ee_effect_completion_function_t cb); - -/** - * @brief Notify real-time updates listeners. - * - * @param invocation Pointer to the invocation which has been used to execute - * effect. - * @param context Pointer to the Subscription context with information which - * is required by the Event Listener to notify all real-time - * update listeners. - * @param cb Pointer to the effect execution completion callback - * function. - * @return Real-time updates emit operation result. - */ -static enum pubnub_res _emit_messages_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - pbcc_ee_effect_completion_function_t cb); - -/** - * @brief Cancel previously started HTTP operation. - * - * @param invocation Pointer to the invocation which has been used to execute - * effect. - * @param context Pointer to the Subscription context with information which - * is required to cancel a previously started HTTP operation. - * @param cb Pointer to the effect execution completion callback - * function. - */ -static void _cancel_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - pbcc_ee_effect_completion_function_t cb); - -/** - * @brief Make actual call to the Subscribe v2. - * - * @param invocation Pointer to the invocation which has been used to execute - * effect. - * @param context Pointer to the Subscription context with all required - * information to perform Subscribe v2 call. - * @param cb Pointer to the effect execution completion callback - * function. - * @return Subscribe v2 call operation result. - */ -static enum pubnub_res _make_subscribe_request( - pbcc_ee_invocation_t* invocation, - const pbcc_subscribe_ee_context_t* context, - pbcc_ee_effect_completion_function_t cb); - - // ---------------------------------------------- // Functions // ---------------------------------------------- @@ -947,40 +171,36 @@ pbcc_subscribe_ee_t* pbcc_subscribe_ee_alloc(pubnub_t* pb) SUBSCRIBABLE_LENGTH, PBARRAY_RESIZE_BALANCED, PBARRAY_GENERIC_CONTENT_TYPE, - pubnub_subscription_set_free); + (pbarray_element_free)pubnub_subscription_set_free_); ee->subscriptions = pbarray_alloc( SUBSCRIBABLE_LENGTH, PBARRAY_RESIZE_BALANCED, PBARRAY_GENERIC_CONTENT_TYPE, - pubnub_subscription_free); - + (pbarray_element_free)pubnub_subscription_free_); pubnub_mutex_init(ee->mutw); - pubnub_mutex_lock(pb->monitor); - ee->last_result = pb->core.last_result; - pubnub_mutex_unlock(pb->monitor); if (NULL == ee->subscriptions) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " "for subscriptions list\n"); - pbcc_subscribe_ee_free(ee); + pbcc_subscribe_ee_free(&ee); return NULL; } if (NULL == ee->subscription_sets) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " "for subscription sets list\n"); - pbcc_subscribe_ee_free(ee); + pbcc_subscribe_ee_free(&ee); return NULL; } - // Prepare storage for computed list of subscribable objects. + /** Prepare storage for computed list of subscribable objects. */ ee->subscribables = pbhash_set_alloc( SUBSCRIBABLE_LENGTH, PBHASH_SET_CHAR_CONTENT_TYPE, - _pubnub_subscribable_free); + (pbhash_set_element_free)pubnub_subscribable_free_); if (NULL == ee->subscribables) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " "for subscribable objects\n"); - pbcc_subscribe_ee_free(ee); + pbcc_subscribe_ee_free(&ee); return NULL; } ee->filter_expr = NULL; @@ -988,35 +208,69 @@ pbcc_subscribe_ee_t* pbcc_subscribe_ee_alloc(pubnub_t* pb) ee->status = SUBSCRIPTION_STATUS_DISCONNECTED; ee->pb = pb; - // Setup event engine - ee->ee = pbcc_ee_alloc(_pbcc_unsubscribed_state_alloc()); + /** Setup event engine */ + ee->ee = pbcc_ee_alloc(pbcc_unsubscribed_state_alloc()); if (NULL == ee->ee) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " "for event engine with initial state\n"); - pbcc_subscribe_ee_free(ee); + pbcc_subscribe_ee_free(&ee); return NULL; } - // Setup event listener + /** Setup event listener */ ee->event_listener = pbcc_event_listener_alloc(pb); if (NULL == ee->ee) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_alloc: failed to allocate memory " "for event listener\n"); - pbcc_subscribe_ee_free(ee); + pbcc_subscribe_ee_free(&ee); return NULL; } - pubnub_register_callback(pb, _pbcc_subscribe_callback, ee); + pubnub_register_callback(pb, + (pubnub_callback_t)pbcc_subscribe_callback_, + ee); return ee; } +void pbcc_subscribe_ee_free(pbcc_subscribe_ee_t** ee) +{ + if (NULL == ee || NULL == *ee) { return; } + + pubnub_mutex_lock((*ee)->mutw); + if (NULL != (*ee)->subscription_sets) + pbarray_free(&(*ee)->subscription_sets); + if (NULL != (*ee)->subscriptions) { pbarray_free(&(*ee)->subscriptions); } + if (NULL != (*ee)->subscribables) + pbhash_set_free(&(*ee)->subscribables); + if (NULL != (*ee)->filter_expr) { free((*ee)->filter_expr); } + if (NULL != (*ee)->ee) { pbcc_ee_free(&(*ee)->ee); } + if (NULL != (*ee)->event_listener) + pbcc_event_listener_free(&(*ee)->event_listener); + pubnub_mutex_unlock((*ee)->mutw); + pubnub_mutex_destroy((*ee)->mutw); + free(*ee); + *ee = NULL; +} + pbcc_event_listener_t* pbcc_subscribe_ee_event_listener( const pbcc_subscribe_ee_t* ee) { return ee->event_listener; } +pbcc_ee_data_t* pbcc_subscribe_ee_current_state_context( + const pbcc_subscribe_ee_t* ee) +{ + pbcc_ee_state_t* current_state = pbcc_ee_current_state(ee->ee); + const pbcc_ee_data_t* data = NULL; + + if (NULL != current_state) { data = pbcc_ee_state_data(current_state); } + pbcc_ee_state_free(¤t_state); + + return (pbcc_ee_data_t*)data; +} + void pbcc_subscribe_ee_set_filter_expression( pbcc_subscribe_ee_t* ee, const char* filter_expr) @@ -1043,24 +297,9 @@ void pbcc_subscribe_ee_set_heartbeat( pubnub_mutex_unlock(ee->mutw); } -void pbcc_subscribe_ee_free(pbcc_subscribe_ee_t* ee) -{ - if (NULL == ee) { return; } - - pubnub_mutex_lock(ee->mutw); - if (NULL != ee->subscription_sets) { pbarray_free(ee->subscription_sets); } - if (NULL != ee->subscriptions) { pbarray_free(ee->subscriptions); } - if (NULL != ee->subscribables) { pbhash_set_free(ee->subscribables); } - if (NULL != ee->filter_expr) { free(ee->filter_expr); } - if (NULL != ee->ee) { pbcc_ee_free(ee->ee); } - pubnub_mutex_unlock(ee->mutw); - pubnub_mutex_destroy(ee->mutw); - free(ee); -} - enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription( pbcc_subscribe_ee_t* ee, - const pubnub_subscription_t* sub, + pubnub_subscription_t* sub, const pubnub_subscribe_cursor_t* cursor) { PUBNUB_ASSERT_OPT(NULL != ee); @@ -1073,7 +312,7 @@ enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription( return PNR_OUT_OF_MEMORY; } - const enum pubnub_res rslt = _pbcc_subscribe_ee_subscribe(ee, cursor, true); + const enum pubnub_res rslt = pbcc_subscribe_ee_subscribe_(ee, cursor, true); pubnub_mutex_unlock(ee->mutw); return rslt; @@ -1086,7 +325,12 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription( PUBNUB_ASSERT_OPT(NULL != ee); pubnub_mutex_lock(ee->mutw); - pbhash_set_t* subs = _pubnub_subscription_subscribables(sub, NULL); + if (!pbarray_contains(ee->subscriptions, sub)) { + pubnub_mutex_unlock(ee->mutw); + return PNR_SUB_NOT_FOUND; + } + + pbhash_set_t* subs = pubnub_subscription_subscribables_(sub, NULL); if (NULL == subs) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_unsubscribe_with_subscription: " @@ -1095,12 +339,17 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription( return PNR_OUT_OF_MEMORY; } - pbarray_remove(ee->subscriptions, sub, true); - const enum pubnub_res rslt = _pbcc_subscribe_ee_unsubscribe(ee, subs); - // Subscribables list not needed anymore because channels / groups list - // already composed for presence leave REST API in - // `_pbcc_subscribe_ee_unsubscribe`. - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + pbarray_remove(ee->subscriptions, (void**)&sub, true); + const enum pubnub_res rslt = pbcc_subscribe_ee_unsubscribe_(ee, subs); + + /** + * Subscribables list not needed anymore because channels / groups list + * already composed for presence leave REST API in + * `pbcc_subscribe_ee_unsubscribe_`. + */ + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); pubnub_mutex_unlock(ee->mutw); return rslt; @@ -1108,7 +357,7 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription( enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription_set( pbcc_subscribe_ee_t* ee, - const pubnub_subscription_set_t* set, + pubnub_subscription_set_t* set, const pubnub_subscribe_cursor_t* cursor) { PUBNUB_ASSERT_OPT(NULL != ee); @@ -1122,7 +371,7 @@ enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription_set( return PNR_OUT_OF_MEMORY; } - const enum pubnub_res rslt = _pbcc_subscribe_ee_subscribe(ee, cursor, true); + const enum pubnub_res rslt = pbcc_subscribe_ee_subscribe_(ee, cursor, true); pubnub_mutex_unlock(ee->mutw); return rslt; @@ -1135,7 +384,12 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription_set( PUBNUB_ASSERT_OPT(NULL != ee); pubnub_mutex_lock(ee->mutw); - pbhash_set_t* subs = _pubnub_subscription_set_subscribables(set); + if (!pbarray_contains(ee->subscription_sets, set)) { + pubnub_mutex_unlock(ee->mutw); + return PNR_SUB_NOT_FOUND; + } + + pbhash_set_t* subs = pubnub_subscription_set_subscribables_(set); if (NULL == subs) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_unsubscribe_with_subscription_set: " @@ -1144,12 +398,17 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription_set( return PNR_OUT_OF_MEMORY; } - pbarray_remove(ee->subscription_sets, set, true); - const enum pubnub_res rslt = _pbcc_subscribe_ee_unsubscribe(ee, subs); - // Subscribables list not needed anymore because channels / groups list - // already composed for presence leave REST API in - // `_pbcc_subscribe_ee_unsubscribe`. - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + pbarray_remove(ee->subscription_sets, (void**)&set, true); + const enum pubnub_res rslt = pbcc_subscribe_ee_unsubscribe_(ee, subs); + + /** + * Subscribables list not needed anymore because channels / groups list + * already composed for presence leave REST API in + * `pbcc_subscribe_ee_unsubscribe_`. + */ + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); pubnub_mutex_unlock(ee->mutw); return rslt; @@ -1166,11 +425,11 @@ enum pubnub_res pbcc_subscribe_ee_change_subscription_with_subscription_set( pubnub_mutex_lock(ee->mutw); enum pubnub_res rslt; - if (added) { rslt = _pbcc_subscribe_ee_subscribe(ee, NULL, true); } + if (added) { rslt = pbcc_subscribe_ee_subscribe_(ee, NULL, true); } else { const pubnub_subscription_options_t options = *(pubnub_subscription_options_t*)set; - pbhash_set_t* subs = _pubnub_subscription_subscribables(sub, &options); + pbhash_set_t* subs = pubnub_subscription_subscribables_(sub, &options); if (NULL == subs) { PUBNUB_LOG_ERROR( @@ -1180,11 +439,15 @@ enum pubnub_res pbcc_subscribe_ee_change_subscription_with_subscription_set( return PNR_OUT_OF_MEMORY; } - rslt = _pbcc_subscribe_ee_unsubscribe(ee, subs); - // Subscribables list not needed anymore because channels / groups list - // already composed for presence leave REST API in - // `_pbcc_subscribe_ee_unsubscribe`. - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + rslt = pbcc_subscribe_ee_unsubscribe_(ee, subs); + /** + * Subscribables list not needed anymore because channels / groups list + * already composed for presence leave REST API in + * `pbcc_subscribe_ee_unsubscribe_`. + */ + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); } pubnub_mutex_unlock(ee->mutw); @@ -1196,7 +459,7 @@ enum pubnub_res pbcc_subscribe_ee_disconnect(pbcc_subscribe_ee_t* ee) PUBNUB_ASSERT_OPT(NULL != ee); pubnub_mutex_lock(ee->mutw); - const pbcc_ee_event_t* event = _pbcc_disconnect_event_alloc(ee); + pbcc_ee_event_t* event = pbcc_disconnect_event_alloc(ee); if (NULL == event) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_disconnect: failed to allocate " "memory for event\n"); @@ -1215,7 +478,7 @@ enum pubnub_res pbcc_subscribe_ee_reconnect( PUBNUB_ASSERT_OPT(NULL != ee); pubnub_mutex_lock(ee->mutw); - const pbcc_ee_event_t* event = _pbcc_reconnect_event_alloc(ee, cursor); + pbcc_ee_event_t* event = pbcc_reconnect_event_alloc(ee, cursor); if (NULL == event) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_reconnect: failed to allocate " "memory for event\n"); @@ -1233,19 +496,22 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_all(pbcc_subscribe_ee_t* ee) pubnub_mutex_lock(ee->mutw); char* ch,* cg; - enum pubnub_res rslt = _pbcc_subscribe_ee_subscribables(ee->subscribables, + enum pubnub_res rslt = pbcc_subscribe_ee_subscribables_(ee->subscribables, &ch, &cg, false); if (PNR_OK == rslt) { - const pbcc_ee_event_t* event = - _pbcc_unsubscribe_all_event_alloc(ee, &ch, &cg); + pbcc_ee_event_t* event = + pbcc_unsubscribe_all_event_alloc(ee, &ch, &cg); if (NULL == event) { PUBNUB_LOG_ERROR("pbcc_subscribe_ee_reconnect: failed to allocate " "memory for event\n"); rslt = PNR_OUT_OF_MEMORY; - } + + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } + } else { pbarray_remove_all(ee->subscription_sets); pbarray_remove_all(ee->subscriptions); @@ -1253,14 +519,9 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_all(pbcc_subscribe_ee_t* ee) rslt = pbcc_ee_handle_event(ee->ee, event); } } - - if (PNR_OUT_OF_MEMORY == rslt) { - if (NULL != ch) { free(ch); } - if (NULL != cg) { free(cg); } - } pubnub_mutex_unlock(ee->mutw); - // Looks like there is nothing to unsubscribe from. + /** Looks like there is nothing to unsubscribe from. */ if (PNR_INVALID_PARAMETERS == rslt) { rslt = PNR_OK; } return rslt; @@ -1294,38 +555,35 @@ pubnub_subscription_set_t** pbcc_subscribe_ee_subscription_sets( return subs; } - -// ---------------------------------------------- -// Subscription handler -// ---------------------------------------------- - -void _pbcc_subscribe_callback( +void pbcc_subscribe_callback_( pubnub_t* pb, const enum pubnub_trans trans, const enum pubnub_res result, const void* user_data ) { - const pbcc_subscribe_ee_t* ee = user_data; + pbcc_subscribe_ee_t* ee = (pbcc_subscribe_ee_t*)user_data; - // Asynchronously cancelled subscription and presence leave shouldn't - // trigger any events. + /** + * Asynchronously cancelled subscription and presence leave shouldn't + * trigger any events. + */ if ((PBTT_SUBSCRIBE_V2 == trans && PNR_CANCELLED == result) || PBTT_LEAVE == trans) { - // Asynchronous cancellation mean that + /** Asynchronous cancellation mean that */ pbcc_ee_process_next_invocation(ee->ee, PBTT_LEAVE != trans); return; } PUBNUB_ASSERT_OPT(trans == PBTT_SUBSCRIBE_V2); const bool error = PNR_OK != result; - const pbcc_ee_state_t* state_object = pbcc_ee_current_state(ee->ee); - const pbcc_ee_event_t* event = NULL; + pbcc_ee_state_t* state_object = pbcc_ee_current_state(ee->ee); + pbcc_ee_event_t* event = NULL; pubnub_subscribe_cursor_t cursor; cursor.region = 0; if (!error) { - // Retrieve parsed timetoken stored for next subscription loop. + /** Retrieve parsed timetoken stored for next subscription loop. */ pubnub_mutex_lock(pb->monitor); size_t token_len = strlen(pb->core.timetoken); memcpy(cursor.timetoken, pb->core.timetoken, token_len); @@ -1334,832 +592,21 @@ void _pbcc_subscribe_callback( pbpal_mutex_unlock(pb->monitor); } - if (SUBSCRIBE_EE_STATE_HANDSHAKING == state_object->type) { - if (!error) { event = _pbcc_handshake_success_event_alloc(ee, cursor); } - else { event = _pbcc_handshake_failure_event_alloc(ee, result); } + if (SUBSCRIBE_EE_STATE_HANDSHAKING == pbcc_ee_state_type(state_object)) { + if (!error) { event = pbcc_handshake_success_event_alloc(ee, cursor); } + else { event = pbcc_handshake_failure_event_alloc(ee, result); } } - else if (SUBSCRIBE_EE_STATE_RECEIVING == state_object->type) { - if (!error) { event = _pbcc_receive_success_event_alloc(ee, cursor); } - else { event = _pbcc_receive_failure_event_alloc(ee, result); } + else if (SUBSCRIBE_EE_STATE_RECEIVING == pbcc_ee_state_type(state_object)) { + if (!error) { event = pbcc_receive_success_event_alloc(ee, cursor); } + else { event = pbcc_receive_failure_event_alloc(ee, result); } } if (NULL != event) { pbcc_ee_handle_event(ee->ee, event); } + if (NULL != state_object) { pbcc_ee_state_free(&state_object); } } - -// ---------------------------------------------- -// Functions: State -// ---------------------------------------------- - -pbcc_ee_state_t* _pbcc_unsubscribed_state_alloc() -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_UNSUBSCRIBED, - NULL); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_unsubscribed_state_transition_alloc; - - return state; -} - -pbcc_ee_state_t* _pbcc_handshaking_state_alloc(pbcc_ee_data_t* context) -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_HANDSHAKING, - context); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_handshaking_state_transition_alloc; - pbcc_ee_invocation_t* on_enter = - pbcc_ee_invocation_alloc(_handshake_effect, context, false); - - if (NULL == on_enter || - PNR_OK != pbcc_ee_state_add_on_enter_invocation(state, on_enter)) { - if (NULL != on_enter) { pbcc_ee_invocation_free(on_enter); } - pbcc_ee_state_free(state); - return NULL; - } - - pbcc_ee_invocation_t* on_exit = - pbcc_ee_invocation_alloc(_cancel_effect, context, true); - if (NULL == on_exit || - PNR_OK != pbcc_ee_state_add_on_exit_invocation(state, on_exit)) { - if (NULL != on_exit) { pbcc_ee_invocation_free(on_exit); } - pbcc_ee_state_free(state); - return NULL; - } - - return state; -} - -pbcc_ee_state_t* _pbcc_handshake_failed_state_alloc(pbcc_ee_data_t* context) -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED, - context); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_handshake_failed_state_transition_alloc; - - return state; -} - -pbcc_ee_state_t* _pbcc_handshake_stopped_state_alloc(pbcc_ee_data_t* context) -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED, - context); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_handshake_stopped_state_transition_alloc; - - return state; -} - -pbcc_ee_state_t* _pbcc_receiving_state_alloc(pbcc_ee_data_t* context) -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_RECEIVING, - context); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_receiving_state_transition_alloc; - pbcc_ee_invocation_t* on_enter = - pbcc_ee_invocation_alloc(_receive_effect, context, false); - - if (NULL == on_enter || - PNR_OK != pbcc_ee_state_add_on_enter_invocation(state, on_enter)) { - if (NULL != on_enter) { pbcc_ee_invocation_free(on_enter); } - pbcc_ee_state_free(state); - return NULL; - } - - pbcc_ee_invocation_t* on_exit = - pbcc_ee_invocation_alloc(_cancel_effect, context, true); - if (NULL == on_exit || - PNR_OK != pbcc_ee_state_add_on_exit_invocation(state, on_exit)) { - if (NULL != on_exit) { pbcc_ee_invocation_free(on_exit); } - pbcc_ee_state_free(state); - return NULL; - } - - return state; -} - -pbcc_ee_state_t* _pbcc_receive_failed_state_alloc(pbcc_ee_data_t* context) -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_RECEIVE_FAILED, - context); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_receiving_failed_state_transition_alloc; - - return state; -} - -pbcc_ee_state_t* _pbcc_receive_stopped_state_alloc(pbcc_ee_data_t* context) -{ - pbcc_ee_state_t* state = pbcc_ee_state_alloc( - SUBSCRIBE_EE_STATE_RECEIVE_STOPPED, - context); - if (NULL == state) { return NULL; } - - state->transition = _pbcc_receiving_stopped_state_transition_alloc; - - return state; -} - - -// ---------------------------------------------- -// Functions: Event -// ---------------------------------------------- - -pbcc_ee_event_t* _pbcc_subscription_changed_event_alloc( - const pbcc_subscribe_ee_t* ee, - char** channels, - char** channel_groups) -{ - if (NULL != channels && 0 == strlen(*channels)) { - free(*channels); - *channels = NULL; - } - if (NULL != channel_groups && 0 == strlen(*channel_groups)) { - free(*channel_groups); - *channel_groups = NULL; - } - - const pbcc_subscribe_ee_event type = - SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED; - return _pbcc_subscribe_ee_event_alloc(ee, - type, - channels, - channel_groups, - NULL, - NULL); -} - -pbcc_ee_event_t* _pbcc_subscription_restored_event_alloc( - const pbcc_subscribe_ee_t* ee, - char** channels, - char** channel_groups, - const pubnub_subscribe_cursor_t cursor) -{ - if (NULL != channels && 0 == strlen(*channels)) { - free(*channels); - *channels = NULL; - } - if (NULL != channel_groups && 0 == strlen(*channel_groups)) { - free(*channel_groups); - *channel_groups = NULL; - } - - const pbcc_subscribe_ee_event type = - SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED; - return _pbcc_subscribe_ee_event_alloc(ee, - type, - channels, - channel_groups, - &cursor, - NULL); -} - -pbcc_ee_event_t* _pbcc_handshake_success_event_alloc( - const pbcc_subscribe_ee_t* ee, - const pubnub_subscribe_cursor_t cursor) -{ - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS; - return _pbcc_subscribe_ee_event_alloc(ee, type, NULL, NULL, &cursor, NULL); -} - -pbcc_ee_event_t* _pbcc_handshake_failure_event_alloc( - const pbcc_subscribe_ee_t* ee, - const enum pubnub_res reason) -{ - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE; - return _pbcc_subscribe_ee_event_alloc(ee, type, NULL, NULL, NULL, &reason); -} - -pbcc_ee_event_t* _pbcc_receive_success_event_alloc( - const pbcc_subscribe_ee_t* ee, - const pubnub_subscribe_cursor_t cursor) -{ - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS; - return _pbcc_subscribe_ee_event_alloc(ee, type, NULL, NULL, &cursor, NULL); -} - -pbcc_ee_event_t* _pbcc_receive_failure_event_alloc( - const pbcc_subscribe_ee_t* ee, - const enum pubnub_res reason) -{ - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE; - return _pbcc_subscribe_ee_event_alloc(ee, type, NULL, NULL, NULL, &reason); -} - -pbcc_ee_event_t* _pbcc_disconnect_event_alloc( - const pbcc_subscribe_ee_t* ee) -{ - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_DISCONNECT; - return _pbcc_subscribe_ee_event_alloc(ee, type, NULL, NULL, NULL, NULL); -} - -pbcc_ee_event_t* _pbcc_reconnect_event_alloc( - const pbcc_subscribe_ee_t* ee, - const pubnub_subscribe_cursor_t cursor) -{ - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECONNECT; - return _pbcc_subscribe_ee_event_alloc(ee, type, NULL, NULL, &cursor, NULL); -} - -pbcc_ee_event_t* _pbcc_unsubscribe_all_event_alloc( - const pbcc_subscribe_ee_t* ee, - char** channels, - char** channel_groups) -{ - if (NULL != channels && 0 == strlen(*channels)) { - free(*channels); - *channels = NULL; - } - if (NULL != channel_groups && 0 == strlen(*channel_groups)) { - free(*channel_groups); - *channel_groups = NULL; - } - - const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL; - return _pbcc_subscribe_ee_event_alloc(ee, - type, - channels, - channel_groups, - NULL, - NULL); -} - -pbcc_ee_event_t* _pbcc_subscribe_ee_event_alloc( - const pbcc_subscribe_ee_t* ee, - const pbcc_subscribe_ee_event event, - char** channels, - char** channel_groups, - const pubnub_subscribe_cursor_t* cursor, - const enum pubnub_res* reason) -{ - pubnub_mutex_lock(ee->mutw); - const pbcc_subscribe_ee_context_t* current_context = - _pbcc_subscribe_ee_current_state_context(ee); - pbcc_subscribe_ee_context_t* ctx = _pbcc_subscribe_ee_context_copy( - ee->pb, - current_context, - channels, - channel_groups); - - if (NULL == ctx) { - pubnub_mutex_unlock(ee->mutw); - return NULL; - } - if (NULL != cursor) { ctx->cursor = *cursor; } - if (NULL != reason) { ctx->reason = *reason; } - - pbcc_ee_data_t* data = - pbcc_ee_data_alloc(ctx, _pbcc_subscribe_ee_context_free); - if (NULL == data) { - _pbcc_subscribe_ee_context_free(ctx); - pubnub_mutex_unlock(ee->mutw); - return NULL; - } - pubnub_mutex_unlock(ee->mutw); - - return pbcc_ee_event_alloc(event, data); -} - - -// ---------------------------------------------- -// Functions: Transition -// ---------------------------------------------- - -pbcc_ee_transition_t* _pbcc_unsubscribed_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT(current_state->type == SUBSCRIBE_EE_STATE_UNSUBSCRIBED); - - if (event->type != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED && - event->type != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED) { - PUBNUB_LOG_INFO("pbcc_unsubscribed_state_transition_alloc: can't " - "handle transition for %d event type\n", - event->type); - return _pbcc_transition_alloc(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); - } - - return _pbcc_transition_alloc(ee, - SUBSCRIBE_EE_STATE_HANDSHAKING, - event->data, - NULL); -} - -pbcc_ee_transition_t* _pbcc_handshaking_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT(current_state->type == SUBSCRIBE_EE_STATE_HANDSHAKING); - - pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; - pbcc_ee_data_t* data = event->data; - pbarray_t* invocations = NULL; - - switch (event->type) { - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: { - PUBNUB_ASSERT_OPT(NULL != data); - - const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); - const char* channel_groups = - pbcc_ee_data_value(context->channel_groups); - const char* channels = pbcc_ee_data_value(context->channels); - - if (NULL != context && 0 == strlen(channels) && - 0 == strlen(channel_groups)) { - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - data = NULL; - } - else { target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; } - } - break; - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; - break; - case SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE: - case SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS: { - const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); - pbcc_subscribe_ee_t* subscribe_ee = context->pb->core.subscribe_ee; - target_state_type = event->type == SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS - ? SUBSCRIBE_EE_STATE_RECEIVING - : SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED; - invocations = pbarray_alloc(1, - PBARRAY_RESIZE_NONE, - PBARRAY_GENERIC_CONTENT_TYPE, - pbcc_ee_invocation_free); - pbcc_ee_invocation_t* invocation = - pbcc_ee_invocation_alloc(_emit_status_effect, data, false); - - if (NULL == invocations || NULL == invocation) { - PUBNUB_LOG_ERROR("pbcc_handshaking_state_transition_alloc: failed " - "to allocate memory for invocations\n"); - if (NULL != invocations) { pbarray_free(invocations); } - if (NULL != invocation) { pbcc_ee_invocation_free(invocation); } - return NULL; - } - - if (PBAR_OK != pbarray_add(invocations, invocation)) { - PUBNUB_LOG_ERROR("pbcc_handshaking_state_transition_alloc: failed " - "to allocate memory for invocation entry\n"); - pbcc_ee_invocation_free(invocation); - pbarray_free(invocations); - return NULL; - } - - // Update latest Subscribe Event Engine subscription status. - pubnub_mutex_lock(subscribe_ee->mutw); - subscribe_ee->status = SUBSCRIBE_EE_STATE_RECEIVING - ? SUBSCRIPTION_STATUS_CONNECTED - : SUBSCRIPTION_STATUS_CONNECTION_ERROR; - pubnub_mutex_unlock(subscribe_ee->mutw); - } - break; - case SUBSCRIBE_EE_EVENT_DISCONNECT: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED; - break; - case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - break; - default: - invocations = NULL; - data = NULL; - break; - } - - if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { - PUBNUB_LOG_INFO("pbcc_handshaking_state_transition_alloc: can't handle " - "transition for %d event type\n", - event->type); - } - - return _pbcc_transition_alloc(ee, target_state_type, data, invocations); -} - -pbcc_ee_transition_t* _pbcc_handshake_failed_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT( - current_state->type == SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED); - - pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; - pbcc_ee_data_t* data = event->data; - - switch (event->type) { - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: - PUBNUB_ASSERT_OPT(NULL != data); - - const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); - const char* channel_groups = - pbcc_ee_data_value(context->channel_groups); - const char* channels = pbcc_ee_data_value(context->channels); - - if (NULL != context && 0 == strlen(channels) && - 0 == strlen(channel_groups)) { - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - data = NULL; - } - else { target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; } - break; - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: - case SUBSCRIBE_EE_EVENT_RECONNECT: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; - break; - case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - break; - default: - data = NULL; - break; - } - - if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { - PUBNUB_LOG_INFO("pbcc_handshake_failed_state_transition_alloc: can't " - "handle transition for %d event type\n", - event->type); - } - - return _pbcc_transition_alloc(ee, target_state_type, data, NULL); -} - -pbcc_ee_transition_t* _pbcc_handshake_stopped_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT( - current_state->type == SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED); - - pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; - - switch (event->type) { - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED; - break; - case SUBSCRIBE_EE_EVENT_RECONNECT: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; - break; - case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - break; - default: - break; - } - - if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { - PUBNUB_LOG_INFO("pbcc_handshake_stopped_state_transition_alloc: can't " - "handle transition for %d event type\n", - event->type); - } - - return _pbcc_transition_alloc(ee, target_state_type, event->data, NULL); -} - -pbcc_ee_transition_t* _pbcc_receiving_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT(current_state->type == SUBSCRIBE_EE_STATE_RECEIVING); - - pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; - pubnub_subscription_status status = -1; - pbarray_t* invocations = NULL; - pbcc_ee_invocation_t* invocation = NULL; - pbcc_ee_data_t* data = event->data; - - switch (event->type) { - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: - target_state_type = SUBSCRIBE_EE_STATE_RECEIVING; - status = SUBSCRIPTION_STATUS_SUBSCRIPTION_CHANGED; - break; - case SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS: - target_state_type = SUBSCRIBE_EE_STATE_RECEIVING; - break; - case SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE: - target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_FAILED; - status = SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY; - break; - case SUBSCRIBE_EE_EVENT_DISCONNECT: - target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_STOPPED; - status = SUBSCRIPTION_STATUS_DISCONNECTED; - break; - case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - status = SUBSCRIPTION_STATUS_DISCONNECTED; - break; - default: - data = NULL; - break; - } - - if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == target_state_type || - -1 != status) { - invocations = pbarray_alloc(1, - PBARRAY_RESIZE_NONE, - PBARRAY_GENERIC_CONTENT_TYPE, - pbcc_ee_invocation_free); - - if (NULL == invocations) { - PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " - "to allocate memory for invocations\n"); - return NULL; - } - } - - if (-1 != status) { - const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); - pbcc_subscribe_ee_t* subscribe_ee = context->pb->core.subscribe_ee; - invocation = - pbcc_ee_invocation_alloc(_emit_status_effect, data, false); - - // Update latest Subscribe Event Engine subscription status. - pubnub_mutex_lock(subscribe_ee->mutw); - subscribe_ee->status = status; - pubnub_mutex_unlock(subscribe_ee->mutw); - } - - if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == target_state_type) { - invocation = pbcc_ee_invocation_alloc(_emit_messages_effect, - data, - false); - } - - if (NULL != invocation) { - if (PBAR_OK != pbarray_add(invocations, invocation)) { - PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " - "to allocate memory for invocation entry\n"); - pbcc_ee_invocation_free(invocation); - pbarray_free(invocations); - return NULL; - } - } - else if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == target_state_type || - -1 != status) { - PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " - "to allocate memory for invocation\n"); - if (NULL != invocations) { pbarray_free(invocations); } - return NULL; - } - - if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { - PUBNUB_LOG_INFO("pbcc_receiving_state_transition_alloc: can't handle " - "transition for %d event type\n", - event->type); - } - - return _pbcc_transition_alloc(ee, - target_state_type, - event->data, - invocations); -} - -pbcc_ee_transition_t* _pbcc_receiving_failed_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT(current_state->type == SUBSCRIBE_EE_STATE_RECEIVE_FAILED); - - pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; - - switch (event->type) { - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: - case SUBSCRIBE_EE_EVENT_RECONNECT: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; - break; - case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - break; - default: - break; - } - - if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { - PUBNUB_LOG_INFO("pbcc_receiving_failed_state_transition_alloc: can't " - "handle transition for %d event type\n", - event->type); - } - - return _pbcc_transition_alloc(ee, target_state_type, event->data, NULL); -} - -pbcc_ee_transition_t* _pbcc_receiving_stopped_state_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_ee_state_t* current_state, - const pbcc_ee_event_t* event) -{ - PUBNUB_ASSERT_OPT(current_state->type == - SUBSCRIBE_EE_STATE_RECEIVE_STOPPED); - - pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; - - switch (event->type) { - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: - case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: - target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_STOPPED; - break; - case SUBSCRIBE_EE_EVENT_RECONNECT: - target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; - break; - case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: - target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; - break; - default: - break; - } - - if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { - PUBNUB_LOG_INFO("pbcc_receiving_stopped_state_transition_alloc: can't " - "handle transition for %d event type\n", - event->type); - } - - return _pbcc_transition_alloc(ee, target_state_type, event->data, NULL); -} - -pbcc_ee_transition_t* _pbcc_transition_alloc( - const pbcc_event_engine_t* ee, - const pbcc_subscribe_ee_state target_state_type, - pbcc_ee_data_t* context, - const pbarray_t* invocations) -{ - const pbcc_ee_state_t* target_state = NULL; - pbcc_ee_data_t* state_context = NULL; - - if (NULL != context && SUBSCRIBE_EE_STATE_NONE != target_state_type && - SUBSCRIBE_EE_STATE_UNSUBSCRIBED != target_state_type) { - state_context = context; - } - - switch (target_state_type) { - case SUBSCRIBE_EE_STATE_UNSUBSCRIBED: - target_state = _pbcc_unsubscribed_state_alloc(); - break; - case SUBSCRIBE_EE_STATE_HANDSHAKING: - target_state = _pbcc_handshaking_state_alloc(state_context); - break; - case SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED: - target_state = _pbcc_handshake_failed_state_alloc(state_context); - break; - case SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED: - target_state = _pbcc_handshake_stopped_state_alloc(state_context); - break; - case SUBSCRIBE_EE_STATE_RECEIVING: - target_state = _pbcc_receiving_state_alloc(state_context); - break; - case SUBSCRIBE_EE_STATE_RECEIVE_FAILED: - target_state = _pbcc_receive_failed_state_alloc(state_context); - break; - case SUBSCRIBE_EE_STATE_RECEIVE_STOPPED: - target_state = _pbcc_receive_stopped_state_alloc(state_context); - break; - default: - PUBNUB_LOG_ERROR("pbcc_transition_alloc: unknown target state type\n"); - return NULL; - } - - if (NULL == target_state && SUBSCRIBE_EE_STATE_NONE != target_state_type) { - PUBNUB_LOG_ERROR("pbcc_transition_alloc: failed to allocate memory for " - "state object\n"); - return NULL; - } - - return pbcc_ee_transition_alloc(ee, target_state, invocations); -} - - -// ---------------------------------------------- -// Functions: Effect -// ---------------------------------------------- - -enum pubnub_res _handshake_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - const pbcc_ee_effect_completion_function_t cb) -{ - return _make_subscribe_request(invocation, pbcc_ee_data_value(context), cb); -} - -enum pubnub_res _receive_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - const pbcc_ee_effect_completion_function_t cb) -{ - return _make_subscribe_request(invocation, pbcc_ee_data_value(context), cb); -} - -enum pubnub_res _emit_status_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - const pbcc_ee_effect_completion_function_t cb) -{ - const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context); - pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; - const enum pubnub_res reason = ctx->reason; - - // Update latest Subscribe Event Engine subscription status. - pubnub_mutex_lock(subscribe_ee->mutw); - const pubnub_subscription_status status = subscribe_ee->status; - pubnub_mutex_unlock(subscribe_ee->mutw); - - pbcc_event_listener_emit_status(subscribe_ee->event_listener, - status, - reason); - cb(subscribe_ee->ee, invocation); - - return PNR_OK; -} - -enum pubnub_res _emit_messages_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - const pbcc_ee_effect_completion_function_t cb) -{ - const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context); - const pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; - pubnub_t* pb = ctx->pb; - - for (struct pubnub_v2_message msg = pubnub_get_v2(pb); msg.payload.size > 0; - msg = pubnub_get_v2(pb)) { - pbcc_event_listener_emit_message(subscribe_ee->event_listener, msg); - } - - cb(subscribe_ee->ee, invocation); - - return PNR_OK; -} - -void _cancel_effect( - pbcc_ee_invocation_t* invocation, - const pbcc_ee_data_t* context, - const pbcc_ee_effect_completion_function_t cb) -{ - PUBNUB_ASSERT_OPT(NULL != context); - - const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context); - const pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; - PUBNUB_ASSERT(pb_valid_ctx_ptr(ctx->pb)); - - if (PN_CANCEL_FINISHED == pubnub_cancel(ctx->pb)) - cb(subscribe_ee->ee, invocation); -} - -enum pubnub_res _make_subscribe_request( - pbcc_ee_invocation_t* invocation, - const pbcc_subscribe_ee_context_t* context, - const pbcc_ee_effect_completion_function_t cb) -{ - PUBNUB_ASSERT_OPT(NULL != context); - - pubnub_t* pb = context->pb; - // Override timetoken stored for next subscription loop. - pubnub_mutex_lock(pb->monitor); - size_t token_len = strlen(pb->core.timetoken); - memcpy(pb->core.timetoken, context->cursor.timetoken, token_len); - pb->core.timetoken[token_len] = '\0'; - if (context->cursor.region > 0) { - pb->core.region = context->cursor.region; - } - pbpal_mutex_unlock(pb->monitor); - - struct pubnub_subscribe_v2_options opts = pubnub_subscribe_v2_defopts(); - opts.filter_expr = pb->core.subscribe_ee->filter_expr; - opts.channel_group = pbcc_ee_data_value(context->channel_groups); - opts.heartbeat = pb->core.subscribe_ee->heartbeat; - - const enum pubnub_res rslt = pubnub_subscribe_v2( - pb, - pbcc_ee_data_value(context->channels), - opts); - if (PNR_STARTED == rslt) { cb(pb->core.subscribe_ee->ee, invocation); } - - return rslt; -} - - -// ---------------------------------------------- -// Functions: Event Engine -// ---------------------------------------------- - -enum pubnub_res _pbcc_subscribe_ee_subscribe( - const pbcc_subscribe_ee_t* ee, +enum pubnub_res pbcc_subscribe_ee_subscribe_( + pbcc_subscribe_ee_t* ee, const pubnub_subscribe_cursor_t* cursor, const bool update) { @@ -2167,14 +614,17 @@ enum pubnub_res _pbcc_subscribe_ee_subscribe( char* ch = NULL,* cg = NULL; enum pubnub_res rslt = PNR_OK; - if (update) { rslt = _pbcc_subscribe_ee_update_subscribables(ee); } + if (update) { rslt = pbcc_subscribe_ee_update_subscribables_(ee, NULL); } if (PNR_OK == rslt) { - rslt = _pbcc_subscribe_ee_subscribables(ee->subscribables, + rslt = pbcc_subscribe_ee_subscribables_(ee->subscribables, &ch, &cg, true); - // Empty list allowed and will mean that event engine should transit to - // the `Unsubscribed` state. + + /** + * Empty list allowed and will mean that event engine should transit to + * the `Unsubscribed` state. + */ if (PNR_INVALID_PARAMETERS == rslt) { if (NULL != ch) { free(ch); } if (NULL != cg) { free(cg); } @@ -2188,9 +638,9 @@ enum pubnub_res _pbcc_subscribe_ee_subscribe( const bool restore = NULL != cursor && '0' != cursor->timetoken[0]; if (NULL == cursor || !restore) - event = _pbcc_subscription_changed_event_alloc(ee, &ch, &cg); + event = pbcc_subscription_changed_event_alloc(ee, &ch, &cg); else - event = _pbcc_subscription_restored_event_alloc( + event = pbcc_subscription_restored_event_alloc( ee, &ch, &cg, @@ -2199,36 +649,47 @@ enum pubnub_res _pbcc_subscribe_ee_subscribe( PUBNUB_LOG_ERROR( "pbcc_subscribe_ee_subscribe: failed to allocate memory for " "event\n"); + if (NULL != ch) { free(ch); } + if (NULL != cg) { free(cg); } rslt = PNR_OUT_OF_MEMORY; } } - if (PNR_OK == rslt) { - rslt = pbcc_ee_handle_event(ee->ee, event); - // `pubnub_heartbeat` not used separately because `pubnub_subscribe_v2` - // internally use automated heartbeat feature. - } - - if (PNR_OK != rslt) { - if (NULL != event) { pbcc_ee_event_free(event); } - if (NULL != ch) { free(ch); } - if (NULL != cg) { free(cg); } - } + // TODO: We probably will need presence event engine implementation. + /** + * `pubnub_heartbeat` not used separately because `pubnub_subscribe_v2` + * internally use automated heartbeat feature. + */ - return rslt; + return PNR_OK == rslt ? pbcc_ee_handle_event(ee->ee, event) : rslt; } -enum pubnub_res _pbcc_subscribe_ee_unsubscribe( - const pbcc_subscribe_ee_t* ee, - pbhash_set_t* subscribables) +enum pubnub_res pbcc_subscribe_ee_unsubscribe_( + pbcc_subscribe_ee_t* ee, + pbhash_set_t* subscribables) { - char* ch,* cg; + char* ch = NULL,* cg = NULL; bool send_leave = false; enum pubnub_res rslt; - if (PNR_OK == (rslt = _pbcc_subscribe_ee_update_subscribables(ee))) { - pbhash_set_subtract(subscribables, ee->subscribables); - _pbcc_subscribe_ee_subscribables( + size_t count = 0; + pubnub_subscribable_t** subs = (pubnub_subscribable_t**) + pbhash_set_elements(subscribables, &count); + if (NULL == subs) { return PNR_OUT_OF_MEMORY; } + + /** Removing subscribable which is not part of subscription loop. */ + for (size_t i = 0; i < count; ++i) { + pubnub_subscribable_t* sub = subs[i]; + if (pbhash_set_contains(ee->subscribables, sub)) { continue; } + pbhash_set_remove(subscribables, (void**)&sub->id->ptr, (void**)&sub); + pubnub_subscribable_free_(sub); + } + free(subs); + + if (PNR_OK == (rslt = pbcc_subscribe_ee_update_subscribables_( + ee, + subscribables))) { + pbcc_subscribe_ee_subscribables_( subscribables, &ch, &cg, @@ -2239,14 +700,18 @@ enum pubnub_res _pbcc_subscribe_ee_unsubscribe( } } - // Update user presence information for channels which user actually left. + /** + * Update user presence information for channels which user actually + * left. + */ if (PNR_OK == rslt && send_leave) { + // TODO: CLIENT MAY NOT BE READY TO PROCESS LEAVE. pubnub_leave(ee->pb, ch, cg); } - // Update subscription loop. + /** Update subscription loop. */ if (PNR_OK == rslt) - rslt = _pbcc_subscribe_ee_subscribe(ee, NULL, false); + rslt = pbcc_subscribe_ee_subscribe_(ee, NULL, false); if (NULL != ch) { free(ch); } if (NULL != cg) { free(cg); } @@ -2254,48 +719,50 @@ enum pubnub_res _pbcc_subscribe_ee_unsubscribe( return rslt; } -enum pubnub_res _pbcc_subscribe_ee_update_subscribables( - const pbcc_subscribe_ee_t* ee) - +enum pubnub_res pbcc_subscribe_ee_update_subscribables_( + const pbcc_subscribe_ee_t* ee, + pbhash_set_t* excluded_subscribables) { PUBNUB_ASSERT_OPT(NULL != ee); - const pbarray_t* sets = ee->subscription_sets; - const pbarray_t* subs = ee->subscriptions; - enum pubnub_res rslt = PNR_OK; + pbarray_t* sets = ee->subscription_sets; + pbarray_t* subs = ee->subscriptions; + enum pubnub_res rslt = PNR_OK; - // Clean up previous set to start with fresh list. + /** Clean up previous set to start with fresh list. */ pbhash_set_remove_all(ee->subscribables); for (int i = 0; i < pbarray_count(subs); ++i) { const pubnub_subscription_t* sub = pbarray_element_at(subs, i); - pbhash_set_t* subc = _pubnub_subscription_subscribables(sub, NULL); - rslt = _pbcc_subscribe_ee_add_subscribables(ee, subc); - pbhash_set_free(subc); + pbhash_set_t* subc = pubnub_subscription_subscribables_(sub, NULL); + rslt = pbcc_subscribe_ee_add_subscribables_(ee, subc); + pbhash_set_free(&subc); if (PNR_OK != rslt) { return rslt; } } for (int i = 0; i < pbarray_count(sets); ++i) { const pubnub_subscription_set_t* sub = pbarray_element_at(sets, i); - pbhash_set_t* subc = _pubnub_subscription_set_subscribables(sub); - rslt = _pbcc_subscribe_ee_add_subscribables(ee, subc); - pbhash_set_free(subc); + pbhash_set_t* subc = pubnub_subscription_set_subscribables_(sub); + rslt = pbcc_subscribe_ee_add_subscribables_(ee, subc); + pbhash_set_free(&subc); if (PNR_OK != rslt) { return rslt; } } + if (NULL != excluded_subscribables) + pbhash_set_subtract(ee->subscribables, excluded_subscribables); + return rslt; } - -enum pubnub_res _pbcc_subscribe_ee_add_subscribables( +enum pubnub_res pbcc_subscribe_ee_add_subscribables_( const pbcc_subscribe_ee_t* ee, - const pbhash_set_t* subscribables) + pbhash_set_t* subscribables) { PUBNUB_ASSERT_OPT(NULL != ee); PUBNUB_ASSERT_OPT(NULL != subscribables); enum pubnub_res rslt = PNR_OK; - pbhash_set_t* dups; + pbhash_set_t* dups = NULL; if (NULL == subscribables || PBHSR_OUT_OF_MEMORY == pbhash_set_union( @@ -2318,7 +785,7 @@ enum pubnub_res _pbcc_subscribe_ee_add_subscribables( if (NULL == dups) { return rslt; } - // Make sure that duplicates will be freed. + /** Make sure that duplicates will be freed. */ size_t dups_count = 0; pubnub_subscribable_t** elms = (pubnub_subscribable_t**) pbhash_set_elements(dups, &dups_count); @@ -2328,16 +795,16 @@ enum pubnub_res _pbcc_subscribe_ee_add_subscribables( if (PBHSR_VALUE_EXISTS == pbhash_set_match_element( ee->subscribables, elm)) { - _pubnub_subscribable_free(elm); + pubnub_subscribable_free_(elm); } } if (NULL != elms) { free(elms); } - pbhash_set_free(dups); + pbhash_set_free(&dups); return rslt; } -enum pubnub_res _pbcc_subscribe_ee_subscribables( +enum pubnub_res pbcc_subscribe_ee_subscribables_( pbhash_set_t* subscribables, char** channels, char** channel_groups, @@ -2345,9 +812,9 @@ enum pubnub_res _pbcc_subscribe_ee_subscribables( { PUBNUB_ASSERT_OPT(NULL != subscribables); - // Expected length of comma-separated channel names. + /** Expected length of comma-separated channel names. */ size_t ch_list_length = 0, ch_count = 0, ch_offset = 0; - // Expected length of comma-separated channel group names. + /** Expected length of comma-separated channel group names. */ size_t cg_list_length = 0, cg_count = 0, cg_offset = 0; size_t count; @@ -2356,22 +823,22 @@ enum pubnub_res _pbcc_subscribe_ee_subscribables( for (int i = 0; i < count; ++i) { const pubnub_subscribable_t* sub = subs[i]; - // Ignoring presence subscribables if they shouldn't be included. - if (!include_presence && _pubnub_subscribable_is_presence(sub)) + /** Ignoring presence subscribables if they shouldn't be included. */ + if (!include_presence && pubnub_subscribable_is_presence_(sub)) continue; - if (!_pubnub_subscribable_is_cg(sub)) { - ch_list_length += _pubnub_subscribable_length(sub); + if (!pubnub_subscribable_is_cg_(sub)) { + ch_list_length += pubnub_subscribable_length_(sub); ch_count++; } else { - cg_list_length += _pubnub_subscribable_length(sub); + cg_list_length += pubnub_subscribable_length_(sub); cg_count++; } } - const size_t ch_len = ch_count ? 1 : ch_list_length + ch_count; - const size_t cg_len = cg_count ? 1 : cg_list_length + cg_count; + const size_t ch_len = ch_list_length + ch_count + 1; + const size_t cg_len = cg_list_length + cg_count + 1; *channels = calloc(ch_len, sizeof(char)); *channel_groups = calloc(cg_len, sizeof(char)); @@ -2379,17 +846,28 @@ enum pubnub_res _pbcc_subscribe_ee_subscribables( PUBNUB_LOG_ERROR( "pbcc_subscribe_ee_subscribables: failed to allocate memory to " "store subscription channels / channel groups\n"); + if (NULL != *channel_groups) { + free(*channel_groups); + *channel_groups = NULL; + } + if (NULL != *channels) { + free(*channels); + *channels = NULL; + } if (NULL != subs) { free(subs); } return PNR_OUT_OF_MEMORY; } + if (0 == cg_count) { *channel_groups[0] = '\0'; } + if (0 == ch_count) { *channels[0] = '\0'; } + for (int i = 0; i < count; ++i) { const pubnub_subscribable_t* sub = subs[i]; - // Ignoring presence subscribables if they shouldn't be included. - if (!include_presence && _pubnub_subscribable_is_presence(sub)) + /** Ignoring presence subscribables if they shouldn't be included. */ + if (!include_presence && pubnub_subscribable_is_presence_(sub)) continue; - if (!_pubnub_subscribable_is_cg(sub)) { + if (!pubnub_subscribable_is_cg_(sub)) { ch_offset += snprintf(*channels + ch_offset, ch_len - ch_offset, "%s%s", @@ -2409,77 +887,4 @@ enum pubnub_res _pbcc_subscribe_ee_subscribables( return ch_list_length == 0 && cg_list_length == 0 ? PNR_INVALID_PARAMETERS : PNR_OK; -} - -pbcc_subscribe_ee_context_t* _pbcc_subscribe_ee_context_alloc( - pubnub_t* pb, - char** channels, - char** channel_groups) -{ - pbcc_subscribe_ee_context_t* context = - malloc(sizeof(pbcc_subscribe_ee_context_t)); - if (NULL == context) { - PUBNUB_LOG_ERROR( - "pbcc_subscribe_ee_context_alloc: failed to allocate memory\n"); - if (NULL != channels && NULL != *channels) { - free(*channels); - *channels = NULL; - } - if (NULL != channel_groups && NULL != *channel_groups) { - free(*channel_groups); - *channel_groups = NULL; - } - return NULL; - } - - context->pb = pb; - context->reason = PNR_OK; - if (NULL != channels && NULL != *channels) - context->channels = pbcc_ee_data_alloc(*channels, free); - else - context->channels = NULL; - if (NULL != channel_groups && NULL != *channel_groups) - context->channel_groups = pbcc_ee_data_alloc(*channel_groups, free); - else - context->channel_groups = NULL; - - return context; -} - -pbcc_subscribe_ee_context_t* _pbcc_subscribe_ee_context_copy( - pubnub_t* pb, - const pbcc_subscribe_ee_context_t* ctx, - char** channels, - char** channel_groups) -{ - pbcc_subscribe_ee_context_t* context = _pbcc_subscribe_ee_context_alloc( - pb, - channels, - channel_groups); - if (NULL == context) { return NULL; } - if (NULL == ctx) { return context; } - - if (NULL == context->channels && NULL != ctx->channels) - context->channels = pbcc_ee_data_copy(ctx->channels); - if (NULL == context->channel_groups && NULL != ctx->channel_groups) - context->channel_groups = pbcc_ee_data_copy(ctx->channel_groups); - context->cursor = ctx->cursor; - - return context; -} - -pbcc_subscribe_ee_context_t* _pbcc_subscribe_ee_current_state_context( - const pbcc_subscribe_ee_t* ee) -{ - const pbcc_ee_state_t* current_state = pbcc_ee_current_state(ee->ee); - return NULL != current_state ? current_state->data : NULL; -} - -void _pbcc_subscribe_ee_context_free(pbcc_subscribe_ee_context_t* ctx) -{ - if (NULL == ctx) { return; } - - if (NULL != ctx->channels) { pbcc_ee_data_free(ctx->channels); } - if (NULL != ctx->channel_groups) { pbcc_ee_data_free(ctx->channel_groups); } - free(ctx); } \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine.h b/core/pbcc_subscribe_event_engine.h index ce2e4771..e580d097 100644 --- a/core/pbcc_subscribe_event_engine.h +++ b/core/pbcc_subscribe_event_engine.h @@ -1,25 +1,36 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_H #define PBCC_SUBSCRIBE_EVENT_ENGINE_H #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #if !PUBNUB_USE_SUBSCRIBE_V2 #error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 -#endif +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API + /** * @file pbcc_subscribe_event_engine.h * @brief Event engine implementation for subscription loop. */ +#include "core/pbcc_subscribe_event_engine_types.h" #include "core/pbcc_subscribe_event_listener.h" #include "core/pubnub_subscribe_event_engine.h" -// Subscribe event engine structure. + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** Subscribe event engine structure. */ typedef struct pbcc_subscribe_ee pbcc_subscribe_ee_t; -// ---------------------------------- -// Functions -// ---------------------------------- +// ---------------------------------------------- +// Functions +// ---------------------------------------------- /** * @brief Create Subscribe Event Engine. @@ -32,6 +43,15 @@ typedef struct pbcc_subscribe_ee pbcc_subscribe_ee_t; */ pbcc_subscribe_ee_t* pbcc_subscribe_ee_alloc(pubnub_t* pb); +/** + * @brief Dispose and free up resources used by Subscribe Event Engine. + * + * @note Function will NULLify provided subscribe event engine pointer. + * + * @param ee Pointer to the Subscribe Event Engine which should be disposed. + */ +void pbcc_subscribe_ee_free(pbcc_subscribe_ee_t** ee); + /** * @brief Get Subscribe Event Listener. * @@ -42,6 +62,16 @@ pbcc_subscribe_ee_t* pbcc_subscribe_ee_alloc(pubnub_t* pb); pbcc_event_listener_t* pbcc_subscribe_ee_event_listener( const pbcc_subscribe_ee_t* ee); +/** + * @brief Get current state context. + * + * @param ee Pointer to the Subscribe Event Engine, which should provide current + * state object. + * @return Current Subscribe Event Engine context data. + */ +pbcc_ee_data_t* pbcc_subscribe_ee_current_state_context( + const pbcc_subscribe_ee_t* ee); + /** * @brief Update real-time updates filter expression. * @@ -87,7 +117,7 @@ void pbcc_subscribe_ee_set_heartbeat( */ enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription( pbcc_subscribe_ee_t* ee, - const pubnub_subscription_t* sub, + pubnub_subscription_t* sub, const pubnub_subscribe_cursor_t* cursor); /** @@ -128,7 +158,7 @@ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription( */ enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription_set( pbcc_subscribe_ee_t* ee, - const pubnub_subscription_set_t* set, + pubnub_subscription_set_t* set, const pubnub_subscribe_cursor_t* cursor); /** @@ -145,7 +175,7 @@ enum pubnub_res pbcc_subscribe_ee_subscribe_with_subscription_set( */ enum pubnub_res pbcc_subscribe_ee_unsubscribe_with_subscription_set( pbcc_subscribe_ee_t* ee, - const pubnub_subscription_set_t* set); + pubnub_subscription_set_t* set); /** * @brief Subscription set modification handler. @@ -242,14 +272,7 @@ pubnub_subscription_t** pbcc_subscribe_ee_subscriptions( pubnub_subscription_set_t** pbcc_subscribe_ee_subscription_sets( pbcc_subscribe_ee_t* ee, size_t* count); - -/** - * @brief Dispose and free up resources used by Subscribe Event Engine. - * - * @param ee Pointer to the Subscribe Event Engine which should be disposed. - */ -void pbcc_subscribe_ee_free(pbcc_subscribe_ee_t* ee); -#else +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_H +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_H diff --git a/core/pbcc_subscribe_event_engine_effects.c b/core/pbcc_subscribe_event_engine_effects.c new file mode 100644 index 00000000..0be70c26 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_effects.c @@ -0,0 +1,145 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_effects.h" + +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pubnub_subscribe_v2.h" +#include "core/pubnub_pubsubapi.h" +#include "core/pubnub_assert.h" +#include "pubnub_internal.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Make actual call to the Subscribe v2. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with all required + * information to perform Subscribe v2 call. + * @param cb Pointer to the effect execution completion callback + * function. + * @return Subscribe v2 call operation result. + */ +static enum pubnub_res make_subscribe_request_( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +enum pubnub_res pbcc_subscribe_ee_handshake_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + return make_subscribe_request_(invocation, context, cb); +} + +enum pubnub_res pbcc_subscribe_ee_receive_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + return make_subscribe_request_(invocation, context, cb); +} + +enum pubnub_res pbcc_subscribe_ee_emit_status_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + + pubnub_mutex_lock(subscribe_ee->mutw); + const pubnub_subscription_status status = subscribe_ee->status; + pubnub_mutex_unlock(subscribe_ee->mutw); + + pbcc_event_listener_emit_status(subscribe_ee->event_listener, + status, + ctx->reason, + pbcc_ee_data_value(ctx->channels), + pbcc_ee_data_value(ctx->channel_groups)); + cb(subscribe_ee->ee, invocation); + pbcc_ee_data_free(context_copy); + + return PNR_OK; +} + +enum pubnub_res pbcc_subscribe_ee_emit_messages_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + const pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + pubnub_t* pb = ctx->pb; + + for (struct pubnub_v2_message msg = pubnub_get_v2(pb); msg.payload.size > 0; + msg = pubnub_get_v2(pb)) { + pbcc_event_listener_emit_message(subscribe_ee->event_listener, msg); + } + + cb(subscribe_ee->ee, invocation); + pbcc_ee_data_free(context_copy); + + return PNR_OK; +} + +void pbcc_subscribe_ee_cancel_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + PUBNUB_ASSERT_OPT(NULL != context); + + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + const pbcc_subscribe_ee_t* subscribe_ee = ctx->pb->core.subscribe_ee; + PUBNUB_ASSERT(pb_valid_ctx_ptr(ctx->pb)); + + if (PN_CANCEL_FINISHED == pubnub_cancel(ctx->pb)) + cb(subscribe_ee->ee, invocation); + pbcc_ee_data_free(context_copy); +} + +enum pubnub_res make_subscribe_request_( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + const pbcc_ee_effect_completion_function_t cb) +{ + PUBNUB_ASSERT_OPT(NULL != context); + + pbcc_ee_data_t* context_copy = pbcc_ee_data_copy(context); + const pbcc_subscribe_ee_context_t* ctx = pbcc_ee_data_value(context_copy); + pubnub_t* pb = ctx->pb; + /** Override timetoken stored for next subscription loop. */ + pubnub_mutex_lock(pb->monitor); + size_t token_len = strlen(pb->core.timetoken); + memcpy(pb->core.timetoken, ctx->cursor.timetoken, token_len); + pb->core.timetoken[token_len] = '\0'; + if (ctx->cursor.region > 0) { pb->core.region = ctx->cursor.region; } + pbpal_mutex_unlock(pb->monitor); + + struct pubnub_subscribe_v2_options opts = pubnub_subscribe_v2_defopts(); + opts.filter_expr = pb->core.subscribe_ee->filter_expr; + opts.channel_group = pbcc_ee_data_value(ctx->channel_groups); + opts.heartbeat = pb->core.subscribe_ee->heartbeat; + + const enum pubnub_res rslt = pubnub_subscribe_v2( + pb, + pbcc_ee_data_value(ctx->channels), + opts); + if (PNR_STARTED == rslt) { cb(pb->core.subscribe_ee->ee, invocation); } + pbcc_ee_data_free(context_copy); + + return rslt; +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_effects.h b/core/pbcc_subscribe_event_engine_effects.h new file mode 100644 index 00000000..bda9f064 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_effects.h @@ -0,0 +1,111 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_EFFECTS_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_EFFECTS_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_events.h + * @brief Subscribe Event Engine states transition events. +*/ + +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Execute (schedule) initial subscribe to the channels / groups. + * + * Effect scheduled because PubNub context uses a callback interface and status + * will be known only with callback call. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context object with information + * for initial subscribe (tt=0). + * @param cb Pointer to the effect execution completion callback + * function. + * @return Handshake effect execution (scheduling) operation result. + */ +enum pubnub_res pbcc_subscribe_ee_handshake_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Execute (schedule) next subscription loop. + * + * Effect scheduled because PubNub context uses a callback interface and status + * will be known only with callback call. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context object with information + * for next subscription loop (tt!=0). + * @param cb Pointer to the effect execution completion callback + * function. + * @return Receive effect execution (scheduling) operation result. + */ +enum pubnub_res pbcc_subscribe_ee_receive_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Notify status change listeners. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with information which + * is required by the Event Listener to notify all status + * change listeners. + * @param cb Pointer to the effect execution completion callback + * function. + * @return Status change emit operation result. + */ +enum pubnub_res pbcc_subscribe_ee_emit_status_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Notify real-time updates listeners. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with information which + * is required by the Event Listener to notify all real-time + * update listeners. + * @param cb Pointer to the effect execution completion callback + * function. + * @return Real-time updates emit operation result. + */ +enum pubnub_res pbcc_subscribe_ee_emit_messages_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); + +/** + * @brief Cancel previously started HTTP operation. + * + * @param invocation Pointer to the invocation which has been used to execute + * effect. + * @param context Pointer to the Subscription context with information which + * is required to cancel a previously started HTTP operation. + * @param cb Pointer to the effect execution completion callback + * function. + */ +void pbcc_subscribe_ee_cancel_effect( + pbcc_ee_invocation_t* invocation, + pbcc_ee_data_t* context, + pbcc_ee_effect_completion_function_t cb); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_EFFECTS_H diff --git a/core/pbcc_subscribe_event_engine_events.c b/core/pbcc_subscribe_event_engine_events.c new file mode 100644 index 00000000..07ab08d2 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_events.c @@ -0,0 +1,333 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_events.h" + +#include + +#include "core/pubnub_log.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create event for underlying Event Engine. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param event Type of Subscribe Event Engine event. + * @param [in,out] channels Pointer to the byte sting of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte string of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @param cursor Time token, which should be used with next + * subscription REST API loop call. + * @param reason Subscription processing result code which + * explain failure reason. + * @return Pointer to requested `event` type, which will be processed by the + * Subscribe Event Engine to get transition instructions from the + * current state. + */ +static pbcc_ee_event_t* pbcc_subscribe_ee_event_alloc_( + pbcc_subscribe_ee_t* ee, + pbcc_subscribe_ee_event event, + char** channels, + char** channel_groups, + const pubnub_subscribe_cursor_t* cursor, + const enum pubnub_res* reason); + +/** + * @brief Create Subscribe Event Engine context object. + * + * @param pb PubNub context, which should be used for + * effects execution by Subscribe Event Engine + * effects dispatcher. + * @param [in,out] channels Pointer to the byte string with + * comma-separated of channels which should be + * passed between states in context object. + * @param [in,out] channel_groups Pointer to the byte string with + * comma-separated of channel groups which should + * be passed between states in context object. + * @return Pointer to the ready to use Subscribe Event Engine context object, or + * `NULL` in case of insufficient memory error. + * The returned pointer must be passed to the + * `pbcc_subscribe_ee_context_free_` to avoid a memory leak. + */ +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_alloc_( + pubnub_t* pb, + char** channels, + char** channel_groups); + +/** + * @brief Copy source context with `channels` and `channel_groups` override. +* + * @param pb PubNub context, which should be used for + * effects execution by Subscribe Event Engine + * effects dispatcher. + * @param ctx Pointer to the context, from which data will + * be copied to the next Subscribe Event Engine + * context. + * @param [in,out] channels Pointer to the byte string with + * comma-separated channels which should be used + * instead of `channels` from provided context. + * @param [in,out] channel_groups Pointer to the byte string with + * comma-separated channel groups which should be + * used instead of `channel_groups` from provided + * context. + * @return Pointer to the ready to use Subscribe Event Engine context created + * from the data of source context, or `NULL` in case of insufficient + * memory error. + * The returned pointer must be passed to the + * `pbcc_subscribe_ee_context_free_` to avoid a memory leak. + */ +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_copy_( + pubnub_t* pb, + const pbcc_subscribe_ee_context_t* ctx, + char** channels, + char** channel_groups); + +/** + * @brief Clean up resources used by subscription context. + * + * @param ctx Pointer to the context, which should free up resources. + */ +void pbcc_subscribe_ee_context_free_(pbcc_subscribe_ee_context_t** ctx); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_ee_event_t* pbcc_subscription_changed_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups) +{ + if (NULL != channels && 0 == strlen(*channels)) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && 0 == strlen(*channel_groups)) { + free(*channel_groups); + *channel_groups = NULL; + } + + const pbcc_subscribe_ee_event type = + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + channels, + channel_groups, + NULL, + NULL); +} + +pbcc_ee_event_t* pbcc_subscription_restored_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups, + const pubnub_subscribe_cursor_t cursor) +{ + if (NULL != channels && 0 == strlen(*channels)) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && 0 == strlen(*channel_groups)) { + free(*channel_groups); + *channel_groups = NULL; + } + + const pbcc_subscribe_ee_event type = + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + channels, + channel_groups, + &cursor, + NULL); +} + +pbcc_ee_event_t* pbcc_handshake_success_event_alloc( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS; + return pbcc_subscribe_ee_event_alloc_(ee, type, NULL, NULL, &cursor, NULL); +} + +pbcc_ee_event_t* pbcc_handshake_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + const enum pubnub_res reason) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE; + return pbcc_subscribe_ee_event_alloc_(ee, type, NULL, NULL, NULL, &reason); +} + +pbcc_ee_event_t* pbcc_receive_success_event_alloc( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS; + return pbcc_subscribe_ee_event_alloc_(ee, type, NULL, NULL, &cursor, NULL); +} + +pbcc_ee_event_t* pbcc_receive_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + const enum pubnub_res reason) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE; + return pbcc_subscribe_ee_event_alloc_(ee, type, NULL, NULL, NULL, &reason); +} + +pbcc_ee_event_t* pbcc_disconnect_event_alloc(pbcc_subscribe_ee_t* ee) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_DISCONNECT; + return pbcc_subscribe_ee_event_alloc_(ee, type, NULL, NULL, NULL, NULL); +} + +pbcc_ee_event_t* pbcc_reconnect_event_alloc( + pbcc_subscribe_ee_t* ee, + const pubnub_subscribe_cursor_t cursor) +{ + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_RECONNECT; + return pbcc_subscribe_ee_event_alloc_(ee, type, NULL, NULL, &cursor, NULL); +} + +pbcc_ee_event_t* pbcc_unsubscribe_all_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups) +{ + if (NULL != channels && 0 == strlen(*channels)) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && 0 == strlen(*channel_groups)) { + free(*channel_groups); + *channel_groups = NULL; + } + + const pbcc_subscribe_ee_event type = SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL; + return pbcc_subscribe_ee_event_alloc_(ee, + type, + channels, + channel_groups, + NULL, + NULL); +} + +pbcc_ee_event_t* pbcc_subscribe_ee_event_alloc_( + pbcc_subscribe_ee_t* ee, + const pbcc_subscribe_ee_event event, + char** channels, + char** channel_groups, + const pubnub_subscribe_cursor_t* cursor, + const enum pubnub_res* reason) +{ + pubnub_mutex_lock(ee->mutw); + pbcc_ee_data_t* current_state_data = + pbcc_subscribe_ee_current_state_context(ee); + const pbcc_subscribe_ee_context_t* current_context = + pbcc_ee_data_value(current_state_data); + pbcc_subscribe_ee_context_t* ctx = pbcc_subscribe_ee_context_copy_( + ee->pb, + current_context, + channels, + channel_groups); + pbcc_ee_data_free(current_state_data); + + if (NULL == ctx) { + pubnub_mutex_unlock(ee->mutw); + return NULL; + } + if (NULL != cursor) { ctx->cursor = *cursor; } + if (NULL != reason) { ctx->reason = *reason; } + + pbcc_ee_data_t* data = pbcc_ee_data_alloc( + ctx, + (pbcc_ee_data_free_function_t)pbcc_subscribe_ee_context_free_); + if (NULL == data) { + pubnub_mutex_unlock(ee->mutw); + pbcc_subscribe_ee_context_free_(&ctx); + return NULL; + } + pubnub_mutex_unlock(ee->mutw); + + pbcc_ee_event_t* event_object = pbcc_ee_event_alloc(event, data); + if (NULL == event_object) { + pbcc_ee_data_free(data); + return NULL; + } + + return event_object; +} + +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_alloc_( + pubnub_t* pb, + char** channels, + char** channel_groups) +{ + pbcc_subscribe_ee_context_t* context = + malloc(sizeof(pbcc_subscribe_ee_context_t)); + if (NULL == context) { + PUBNUB_LOG_ERROR( + "pbcc_subscribe_ee_context_alloc_: failed to allocate memory\n"); + if (NULL != channels && NULL != *channels) { + free(*channels); + *channels = NULL; + } + if (NULL != channel_groups && NULL != *channel_groups) { + free(*channel_groups); + *channel_groups = NULL; + } + return NULL; + } + + context->pb = pb; + context->reason = PNR_OK; + context->channels = NULL; + context->channel_groups = NULL; + if (NULL != channels && NULL != *channels) + context->channels = pbcc_ee_data_alloc(*channels, free); + if (NULL != channel_groups && NULL != *channel_groups) { + context->channel_groups = + pbcc_ee_data_alloc(*channel_groups, free); + } + + return context; +} + +pbcc_subscribe_ee_context_t* pbcc_subscribe_ee_context_copy_( + pubnub_t* pb, + const pbcc_subscribe_ee_context_t* ctx, + char** channels, + char** channel_groups) +{ + pbcc_subscribe_ee_context_t* context = pbcc_subscribe_ee_context_alloc_( + pb, + channels, + channel_groups); + if (NULL == context) { return NULL; } + if (NULL == ctx) { return context; } + + if (NULL == context->channels && NULL != ctx->channels) + context->channels = pbcc_ee_data_copy(ctx->channels); + if (NULL == context->channel_groups && NULL != ctx->channel_groups) + context->channel_groups = pbcc_ee_data_copy(ctx->channel_groups); + context->cursor = ctx->cursor; + + return context; +} + +void pbcc_subscribe_ee_context_free_(pbcc_subscribe_ee_context_t** ctx) +{ + if (NULL == ctx || NULL == *ctx) { return; } + + if (NULL != (*ctx)->channels) { pbcc_ee_data_free((*ctx)->channels); } + if (NULL != (*ctx)->channel_groups) + pbcc_ee_data_free((*ctx)->channel_groups); + free(*ctx); + *ctx = NULL; +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_events.h b/core/pbcc_subscribe_event_engine_events.h new file mode 100644 index 00000000..65e20d25 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_events.h @@ -0,0 +1,216 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_EVENTS_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_EVENTS_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_events.h + * @brief Subscribe Event Engine states transition events. + */ + +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pbcc_subscribe_event_engine.h" +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Subscription list of channels / groups change event. + * + * This event will be emitted each time when user would like to add new or + * remove already active channels / channel groups. + * + * @note State and subscription context objects will handle further memory + * management of the provided channels and channel groups byte string + * pointers. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param [in,out] channels Pointer to the byte sting of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte sting of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @return Pointer to the `Subscription change event`, which will be processed + * by the Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_subscription_changed_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups); + +/** + * @brief Subscription restore / catchup event. + * + * This event will be emitted each time when user would like to add new or + * remove already active channels / channel groups with specific time token + * (cursor) to catch up messages from. + * + * @note State and subscription context objects will handle further memory + * management of the provided channels and channel groups byte string + * pointers. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param [in,out] channels Pointer to the byte string of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte string of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @param cursor Time token to which PubNub client should try + * to restore subscription (catch up on missing + * messages) loop. + * @return Pointer to the `Subscription restore event`, which will be processed + * by the Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_subscription_restored_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups, + pubnub_subscribe_cursor_t cursor); + +/** + * @brief Initial subscription handshake success event. + * + * This event will be emitted each time when initial subscription to the set of + * channels / groups will complete with a service response which contains the + * time token (cursor) for the next subscription loop. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param cursor Time token, which should be used with next subscription loop. + * @return Pointer to the `Subscription handshake success event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_handshake_success_event_alloc( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** + * @brief Initial subscription handshake failure event. + * + * This event will be emitted each time when initial subscription to the set of + * channels / groups fails. Failure in most of the cases caused by permissions + * and network issues. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param reason Subscription processing result code which explains failure + * reason. + * @return Pointer to the `Subscription handshake failure event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_handshake_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + enum pubnub_res reason); + +/** + * @default Real-time updates receive success event. + * + * This event will be emitted each time when subscription receives a response + * from PubNub service. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param cursor Time token, which should be used with next subscription loop. + * @return Pointer to the `Subscription receive success event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_receive_success_event_alloc( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** +* @brief Real-time updates receive failure event. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param reason Subscription processing result code which explain failure + * reason. + * @return Pointer to the `Subscription receive failure event`, which will be + * processed by the Subscribe Event Engine to get transition + * instructions from the current state. + */ +pbcc_ee_event_t* pbcc_receive_failure_event_alloc( + pbcc_subscribe_ee_t* ee, + enum pubnub_res reason); + +/** + * @brief Disconnect from real-time updates event. + * + * @note List of channels / groups and active time token (cursor) will be + * saved for subscription restore. + * + * @param ee Pointer to the Subscribe Event Engine for which will process the + * event. + * @return Pointer to the `Disconnect event`, which will be processed by the + * Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_disconnect_event_alloc(pbcc_subscribe_ee_t* ee); + +/** + * @brief Reconnect for real-time updates event. + * + * @note Subscription loop will use time token (cursor) which has been in + * use before disconnection / failure. + * + * @param ee Pointer to the Subscribe Event Engine for which will process + * the event. + * @param cursor Time token, which should be used with next subscription + * REST API loop call. + * @return Pointer to the `Reconnect event`, which will be processed by the + * Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_reconnect_event_alloc( + pbcc_subscribe_ee_t* ee, + pubnub_subscribe_cursor_t cursor); + +/** + * @brief Unsubscribe from all channel / groups event. + * + * Unsubscribe from all real-time updates and notify other channel / groups + * subscribers about client presence change (`leave`). + * + * @note The list of channels / groups and used time token (cursor) will be + * reset, and further real-time updates can't be received without a + * `subscribe` call. + * @note State and subscription context objects will handle further memory + * management of the provided channels and channel groups byte string + * pointers. + * + * @param ee Pointer to the Subscribe Event Engine for + * which will process the event. + * @param [in,out] channels Pointer to the byte sting of comma-separated + * channels from which PubNub client should + * receive real-time updates. + * @param [in,out] channel_groups Pointer to the byte string of comma-separated + * channel groups from which PubNub client should + * receive real-time updates. + * @return Pointer to the `Unsubscribe all event`, which will be processed by + * the Subscribe Event Engine to get transition instructions from the + * current state. + */ +pbcc_ee_event_t* pbcc_unsubscribe_all_event_alloc( + pbcc_subscribe_ee_t* ee, + char** channels, + char** channel_groups); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_EVENTS_H diff --git a/core/pbcc_subscribe_event_engine_states.c b/core/pbcc_subscribe_event_engine_states.c new file mode 100644 index 00000000..c961e1ee --- /dev/null +++ b/core/pbcc_subscribe_event_engine_states.c @@ -0,0 +1,118 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_states.h" +#include "core/pbcc_subscribe_event_engine_transitions.h" +#include "core/pbcc_subscribe_event_engine_effects.h" +#include "core/pbcc_subscribe_event_engine_types.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_ee_state_t* pbcc_unsubscribed_state_alloc() +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_UNSUBSCRIBED, + NULL, + pbcc_unsubscribed_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_handshaking_state_alloc(pbcc_ee_data_t* context) +{ + pbcc_ee_state_t* state = pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_HANDSHAKING, + context, + pbcc_handshaking_state_transition_alloc); + if (NULL == state) { return NULL; } + + pbcc_ee_invocation_t* on_enter = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_handshake_effect, + context, + false); + + if (NULL == on_enter || + PNR_OK != pbcc_ee_state_add_on_enter_invocation(state, on_enter)) { + if (NULL != on_enter) { pbcc_ee_invocation_free(on_enter); } + pbcc_ee_state_free(&state); + return NULL; + } + + pbcc_ee_invocation_t* on_exit = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_cancel_effect, + context, + true); + if (NULL == on_exit || + PNR_OK != pbcc_ee_state_add_on_exit_invocation(state, on_exit)) { + if (NULL != on_exit) { pbcc_ee_invocation_free(on_exit); } + pbcc_ee_state_free(&state); + return NULL; + } + + return state; +} + +pbcc_ee_state_t* pbcc_handshake_failed_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED, + context, + pbcc_handshake_failed_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_handshake_stopped_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED, + context, + pbcc_handshake_stopped_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_receiving_state_alloc(pbcc_ee_data_t* context) +{ + pbcc_ee_state_t* state = pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_RECEIVING, + context, + pbcc_receiving_state_transition_alloc); + if (NULL == state) { return NULL; } + + pbcc_ee_invocation_t* on_enter = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_receive_effect, + context, + false); + + if (NULL == on_enter || + PNR_OK != pbcc_ee_state_add_on_enter_invocation(state, on_enter)) { + if (NULL != on_enter) { pbcc_ee_invocation_free(on_enter); } + pbcc_ee_state_free(&state); + return NULL; + } + + pbcc_ee_invocation_t* on_exit = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_cancel_effect, + context, + true); + if (NULL == on_exit || + PNR_OK != pbcc_ee_state_add_on_exit_invocation(state, on_exit)) { + if (NULL != on_exit) { pbcc_ee_invocation_free(on_exit); } + pbcc_ee_state_free(&state); + return NULL; + } + + return state; +} + +pbcc_ee_state_t* pbcc_receive_failed_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_RECEIVE_FAILED, + context, + pbcc_receiving_failed_state_transition_alloc); +} + +pbcc_ee_state_t* pbcc_receive_stopped_state_alloc(pbcc_ee_data_t* context) +{ + return pbcc_ee_state_alloc( + SUBSCRIBE_EE_STATE_RECEIVE_STOPPED, + context, + pbcc_receiving_stopped_state_transition_alloc); +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_states.h b/core/pbcc_subscribe_event_engine_states.h new file mode 100644 index 00000000..df821070 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_states.h @@ -0,0 +1,122 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_STATES_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_STATES_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_states.h + * @brief Subscribe Event Engine states. + */ + +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Inactive Subscribe Event Engine state. + * + * State with which Subscribe Event Engine will be initialized and end up + * when there will be no channels or groups to subscribe to. + * + * @return Pointer to the `Unsubscribed` state. + */ +pbcc_ee_state_t* pbcc_unsubscribed_state_alloc(void); + +/** + * @brief Initial subscription state. + * + * State, which is used to perform initial subscription to receive the next + * subscription loop time token (cursor) and notify channels / groups subscribes + * about change in subscriber's presence. + * + * @param context Pointer to the context with updated Subscribe Event Engine + * information which should be used for the next subscription + * loop. + * @return Pointer to the `Handshaking state`, which should be used as + * Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_handshaking_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Initial subscription failed state. + * + * State, which is used by the Subscribe Event Engine when the last + * subscription loop can't be retried anymore. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) which has been used during last initial + * subscription before receiving operation failure result. + * @return Pointer to the `Handshake failed state`, which should be used as + * Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_handshake_failed_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Initial subscription stopped state. + * + * State, which is used by the Subscribe Event Engine to preserve its last + * context while disconnected, and awaiting its reconnection. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) which has been used during last initial + * subscription. + * @return Pointer to the `Handshake stopped state`, which should be used as + * Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_handshake_stopped_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Real-time updates receiving state. + * + * State, which is used by the Subscribe Event Engine to perform a long-poll + * subscription loop to receive real-time updates. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) received from previous subscription loop to + * receive next real-time updates. + * @return Pointer to the `Receiving real-time updates state`, which should be + * used as Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_receiving_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Real-time updates receive failed state. + * + * State, which is used by the Subscribe Event Engine when the last long-poll + * subscription loop can't be retried anymore. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) received from previous subscription loop to + * receive next real-time updates before receiving operation + * failure result. + * @return Pointer to the `Receive real-time updates failed state`, which should + * be used as Subscribe Event Engine state transition target state. + */ +pbcc_ee_state_t* pbcc_receive_failed_state_alloc(pbcc_ee_data_t* context); + +/** + * @brief Real-time updates receive stopped state. + * + * State, which is used by the Subscribe Event Engine to preserve its last + * context used for long-poll subscription loop while disconnected, and awaiting + * its reconnection. + * + * @param context Pointer to the context with list of channels / groups and time + * token (cursor) received from previous subscription loop to + * receive next real-time updates. + * @return Pointer to the `Receive real-time updates stopped state`, which + * should be used as Subscribe Event Engine state transition target + * state. + */ +pbcc_ee_state_t* pbcc_receive_stopped_state_alloc(pbcc_ee_data_t* context); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_STATES_H diff --git a/core/pbcc_subscribe_event_engine_transitions.c b/core/pbcc_subscribe_event_engine_transitions.c new file mode 100644 index 00000000..1c07a0fc --- /dev/null +++ b/core/pbcc_subscribe_event_engine_transitions.c @@ -0,0 +1,474 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pbcc_subscribe_event_engine_transitions.h" + +#include "core/pbcc_subscribe_event_engine_effects.h" +#include "core/pbcc_subscribe_event_engine_states.h" +#include "core/pbcc_subscribe_event_engine_types.h" +#include "core/pubnub_assert.h" +#include "pubnub_internal.h" +#include "core/pubnub_log.h" + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create transition details for Event Engine. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param target_state_type Type of target Subscribe Event Engine state. + * @param context Subscribe Event Engine data which should be passed + * to the next state and transition invocations. + * @param invocations Pointer to the list of invocations which should be + * called before Event Engine will enter new state. + * @return Pointer to the ready to use transition details or `NULL` in case of + * insufficient memory error. The returned pointer must be passed to the + * `pbcc_ee_transition_free` to avoid a memory leak. + */ +static pbcc_ee_transition_t* pbcc_transition_alloc_( + pbcc_event_engine_t* ee, + pbcc_subscribe_ee_state target_state_type, + pbcc_ee_data_t* context, + pbarray_t* invocations); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +pbcc_ee_transition_t* pbcc_unsubscribed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != SUBSCRIBE_EE_STATE_UNSUBSCRIBED || + (event->type != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED && + event->type != SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED)) { + PUBNUB_LOG_INFO("pbcc_unsubscribed_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + return pbcc_transition_alloc_(ee, + SUBSCRIBE_EE_STATE_HANDSHAKING, + event->data, + NULL); +} + +pbcc_ee_transition_t* pbcc_handshaking_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != SUBSCRIBE_EE_STATE_HANDSHAKING) + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + pbcc_ee_data_t* data = event->data; + pbarray_t* invocations = NULL; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: { + PUBNUB_ASSERT_OPT(NULL != data); + + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + const char* channel_groups = + pbcc_ee_data_value(context->channel_groups); + const char* channels = pbcc_ee_data_value(context->channels); + + if (NULL != context && 0 == strlen(channels) && + 0 == strlen(channel_groups)) { + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + data = NULL; + } + else { target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; } + } + break; + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE: + case SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS: { + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + pbcc_subscribe_ee_t* subscribe_ee = context->pb->core.subscribe_ee; + target_state_type = event->type == + SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS + ? SUBSCRIBE_EE_STATE_RECEIVING + : SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED; + invocations = pbarray_alloc( + 1, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free)pbcc_ee_invocation_free); + pbcc_ee_invocation_t* invocation = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_emit_status_effect, + data, + false); + + if (NULL == invocations || NULL == invocation) { + PUBNUB_LOG_ERROR("pbcc_handshaking_state_transition_alloc: failed " + "to allocate memory for invocations\n"); + if (NULL != invocations) { pbarray_free(&invocations); } + if (NULL != invocation) { pbcc_ee_invocation_free(invocation); } + return NULL; + } + + if (PBAR_OK != pbarray_add(invocations, invocation)) { + PUBNUB_LOG_ERROR("pbcc_handshaking_state_transition_alloc: failed " + "to allocate memory for invocation entry\n"); + pbcc_ee_invocation_free(invocation); + pbarray_free(&invocations); + return NULL; + } + + /** Update latest Subscribe Event Engine subscription status. */ + pubnub_mutex_lock(subscribe_ee->mutw); + subscribe_ee->status = SUBSCRIBE_EE_STATE_RECEIVING == target_state_type + ? SUBSCRIPTION_STATUS_CONNECTED + : SUBSCRIPTION_STATUS_CONNECTION_ERROR; + pubnub_mutex_unlock(subscribe_ee->mutw); + } + break; + case SUBSCRIBE_EE_EVENT_DISCONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + invocations = NULL; + data = NULL; + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_handshaking_state_transition_alloc: can't handle " + "transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, data, invocations); +} + +pbcc_ee_transition_t* pbcc_handshake_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + pbcc_ee_data_t* data = event->data; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + PUBNUB_ASSERT_OPT(NULL != data); + + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + const char* channel_groups = + pbcc_ee_data_value(context->channel_groups); + const char* channels = pbcc_ee_data_value(context->channels); + + if (NULL != context && 0 == strlen(channels) && + 0 == strlen(channel_groups)) { + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + data = NULL; + } + else { target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; } + break; + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + data = NULL; + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_handshake_failed_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, data, NULL); +} + +pbcc_ee_transition_t* pbcc_handshake_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED; + break; + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_handshake_stopped_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, event->data, NULL); +} + +pbcc_ee_transition_t* pbcc_receiving_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != SUBSCRIBE_EE_STATE_RECEIVING) + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + pubnub_subscription_status status = -1; + pbarray_t* invocations = NULL; + pbcc_ee_invocation_t* invocation = NULL; + pbcc_ee_data_t* data = event->data; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVING; + status = SUBSCRIPTION_STATUS_SUBSCRIPTION_CHANGED; + break; + case SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVING; + break; + case SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_FAILED; + status = SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY; + break; + case SUBSCRIBE_EE_EVENT_DISCONNECT: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_STOPPED; + status = SUBSCRIPTION_STATUS_DISCONNECTED; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + status = SUBSCRIPTION_STATUS_DISCONNECTED; + break; + default: + data = NULL; + break; + } + + if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == event->type || -1 != status) { + invocations = pbarray_alloc( + 1, + PBARRAY_RESIZE_NONE, + PBARRAY_GENERIC_CONTENT_TYPE, + (pbarray_element_free)pbcc_ee_invocation_free); + + if (NULL == invocations) { + PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " + "to allocate memory for invocations\n"); + return NULL; + } + } + + if (-1 != status) { + const pbcc_subscribe_ee_context_t* context = pbcc_ee_data_value(data); + pbcc_subscribe_ee_t* subscribe_ee = context->pb->core.subscribe_ee; + invocation = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_emit_status_effect, + data, + false); + + /** Update latest Subscribe Event Engine subscription status. */ + pubnub_mutex_lock(subscribe_ee->mutw); + subscribe_ee->status = status; + pubnub_mutex_unlock(subscribe_ee->mutw); + } + + if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == event->type) { + invocation = pbcc_ee_invocation_alloc( + (pbcc_ee_effect_function_t)pbcc_subscribe_ee_emit_messages_effect, + data, + false); + } + + if (NULL != invocation) { + if (PBAR_OK != pbarray_add(invocations, invocation)) { + PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " + "to allocate memory for invocation entry\n"); + pbcc_ee_invocation_free(invocation); + pbarray_free(&invocations); + return NULL; + } + } + else if (SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS == event->type || + -1 != status) { + PUBNUB_LOG_ERROR("pbcc_receiving_state_transition_alloc: failed " + "to allocate memory for invocation\n"); + if (NULL != invocations) { pbarray_free(&invocations); } + return NULL; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_receiving_state_transition_alloc: can't handle " + "transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, + target_state_type, + event->data, + invocations); +} + +pbcc_ee_transition_t* pbcc_receiving_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_RECEIVE_FAILED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_receiving_failed_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, event->data, NULL); +} + +pbcc_ee_transition_t* pbcc_receiving_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event) +{ + if (pbcc_ee_state_type(current_state) != + SUBSCRIBE_EE_STATE_RECEIVE_STOPPED) { + return pbcc_transition_alloc_(ee, SUBSCRIBE_EE_STATE_NONE, NULL, NULL); + } + + pbcc_subscribe_ee_state target_state_type = SUBSCRIBE_EE_STATE_NONE; + + switch (event->type) { + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED: + case SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED: + target_state_type = SUBSCRIBE_EE_STATE_RECEIVE_STOPPED; + break; + case SUBSCRIBE_EE_EVENT_RECONNECT: + target_state_type = SUBSCRIBE_EE_STATE_HANDSHAKING; + break; + case SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL: + target_state_type = SUBSCRIBE_EE_STATE_UNSUBSCRIBED; + break; + default: + break; + } + + if (SUBSCRIBE_EE_STATE_NONE == target_state_type) { + PUBNUB_LOG_INFO("pbcc_receiving_stopped_state_transition_alloc: can't " + "handle transition for %d event type\n", + event->type); + } + + return pbcc_transition_alloc_(ee, target_state_type, event->data, NULL); +} + +pbcc_ee_transition_t* pbcc_transition_alloc_( + pbcc_event_engine_t* ee, + const pbcc_subscribe_ee_state target_state_type, + pbcc_ee_data_t* context, + pbarray_t* invocations) +{ + pbcc_ee_state_t* target_state = NULL; + pbcc_ee_data_t* state_context = NULL; + + if (NULL != context && SUBSCRIBE_EE_STATE_NONE != target_state_type && + SUBSCRIBE_EE_STATE_UNSUBSCRIBED != target_state_type) { + state_context = context; + } + + switch (target_state_type) { + case SUBSCRIBE_EE_STATE_UNSUBSCRIBED: + target_state = pbcc_unsubscribed_state_alloc(); + break; + case SUBSCRIBE_EE_STATE_HANDSHAKING: + target_state = pbcc_handshaking_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED: + target_state = pbcc_handshake_failed_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED: + target_state = pbcc_handshake_stopped_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_RECEIVING: + target_state = pbcc_receiving_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_RECEIVE_FAILED: + target_state = pbcc_receive_failed_state_alloc(state_context); + break; + case SUBSCRIBE_EE_STATE_RECEIVE_STOPPED: + target_state = pbcc_receive_stopped_state_alloc(state_context); + break; + default: + PUBNUB_LOG_ERROR("pbcc_transition_alloc: unknown target state type\n"); + return NULL; + } + + if (NULL == target_state && SUBSCRIBE_EE_STATE_NONE != target_state_type) { + PUBNUB_LOG_ERROR("pbcc_transition_alloc: failed to allocate memory for " + "state object\n"); + return NULL; + } + + pbcc_ee_transition_t* transition = pbcc_ee_transition_alloc( + ee, + target_state, + invocations); + if (NULL == transition) { + PUBNUB_LOG_ERROR("pbcc_transition_alloc: failed to allocate memory for " + "transition object\n"); + if (NULL != target_state) { pbcc_ee_state_free(&target_state); } + } + + return transition; +} \ No newline at end of file diff --git a/core/pbcc_subscribe_event_engine_transitions.h b/core/pbcc_subscribe_event_engine_transitions.h new file mode 100644 index 00000000..c11ef207 --- /dev/null +++ b/core/pbcc_subscribe_event_engine_transitions.h @@ -0,0 +1,148 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_TRANSITIONS_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_TRANSITIONS_H +#if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 + + +/** + * @file pbcc_subscribe_event_engine_transitions.h + * @brief Subscribe Event Engine state transitions. + */ + +#include "core/pbcc_event_engine.h" + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Create transition from `Unsubscribed` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_unsubscribed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Handshaking` state basing on received `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_handshaking_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Handshake Failed` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_handshake_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Handshake Stopped` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_handshake_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Receiving` state basing on received `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_receiving_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Receiving Failed` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_receiving_failed_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); + +/** + * @brief Create transition from `Receiving Stopped` state basing on received + * `event`. + * + * @param ee Pointer to the Event Engine which requested for + * transition details. + * @param current_state Pointer to the current Event Engine state from which + * transition should be decided. + * @param event Pointer to the Event Engine state changing event with + * information required for transition. + * @return Pointer to the state transition object or `NULL` in case of + * insufficient memory error. + */ +pbcc_ee_transition_t* pbcc_receiving_stopped_state_transition_alloc( + pbcc_event_engine_t* ee, + const pbcc_ee_state_t* current_state, + const pbcc_ee_event_t* event); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_TRANSITIONS_H diff --git a/core/pbcc_subscribe_event_engine_types.h b/core/pbcc_subscribe_event_engine_types.h new file mode 100644 index 00000000..c3c4298a --- /dev/null +++ b/core/pbcc_subscribe_event_engine_types.h @@ -0,0 +1,151 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PBCC_SUBSCRIBE_EVENT_ENGINE_TYPES_H +#define PBCC_SUBSCRIBE_EVENT_ENGINE_TYPES_H + + +/** + * @file pbcc_subscribe_event_engine_types.h + * @brief Subscribe Event Engine shared types. + */ + +#include "core/pbcc_subscribe_event_listener.h" +#include "core/pbcc_event_engine.h" +#include "core/pubnub_mutex.h" +#include "lib/pbhash_set.h" + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Subscribe Event Engine events. */ +typedef enum { + /** Subscription list of channels / groups change event. */ + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_CHANGED, + /** Subscription restore / catchup event. */ + SUBSCRIBE_EE_EVENT_SUBSCRIPTION_RESTORED, + /** Initial subscription handshake success event. */ + SUBSCRIBE_EE_EVENT_HANDSHAKE_SUCCESS, + /** Initial subscription handshake failure event. */ + SUBSCRIBE_EE_EVENT_HANDSHAKE_FAILURE, + /** Real-time updates receive success event. */ + SUBSCRIBE_EE_EVENT_RECEIVE_SUCCESS, + /** Real-time updates receive failure event. */ + SUBSCRIBE_EE_EVENT_RECEIVE_FAILURE, + /** Disconnect from real-time updates event. */ + SUBSCRIBE_EE_EVENT_DISCONNECT, + /** Reconnect for real-time updates event. */ + SUBSCRIBE_EE_EVENT_RECONNECT, + /** Unsubscribe from all channel / groups. */ + SUBSCRIBE_EE_EVENT_UNSUBSCRIBE_ALL, +} pbcc_subscribe_ee_event; + +/** Subscribe Event Engine states. */ +typedef enum { + /** + * @brief Unknown state. + * + * Placeholder state type without actual implementation which won't be + * treated as insufficient memory error. + */ + SUBSCRIBE_EE_STATE_NONE, + /** Inactive Subscribe Event Engine state. */ + SUBSCRIBE_EE_STATE_UNSUBSCRIBED, + /** Initial subscription state. */ + SUBSCRIBE_EE_STATE_HANDSHAKING, + /** Initial subscription failed state. */ + SUBSCRIBE_EE_STATE_HANDSHAKE_FAILED, + /** Initial subscription stopped state. */ + SUBSCRIBE_EE_STATE_HANDSHAKE_STOPPED, + /** Real-time updates receiving state. */ + SUBSCRIBE_EE_STATE_RECEIVING, + /** Real-time updates receive failed state. */ + SUBSCRIBE_EE_STATE_RECEIVE_FAILED, + /** Real-time updates receive stopped state. */ + SUBSCRIBE_EE_STATE_RECEIVE_STOPPED, +} pbcc_subscribe_ee_state; + +/** Subscribe event engine structure. */ +struct pbcc_subscribe_ee { + /** + * @brief Pointer to the set of subscribable objects which should be used in + * subscription loop. + * + * \b Important: Array allocated with `pubnub_subscribable_free_` elements + * destructor. + */ + pbhash_set_t* subscribables; + /** + * @brief Pointer to the list of active subscription sets. + * + * List of subscription sets which should be used in subscription loop and + * listen for real-time updates. + * + * \b Important: Array allocated with `pubnub_subscription_set_free_` + * elements destructor. + */ + pbarray_t* subscription_sets; + /** + * @brief List of active subscriptions. + * + * List of subscriptions which should be used in subscription loop and + * listen for real-time updates. + * + * \b Important: Array allocated with `pubnub_subscription_free_` + * elements destructor. + */ + pbarray_t* subscriptions; + /** + * @brief How long (in seconds) the server will consider the client alive + * for presence. + */ + unsigned heartbeat; + /** Pointer to the real-time updates filtering expression. */ + char* filter_expr; + /** Recent subscription status. */ + pubnub_subscription_status status; + /** Subscribe Event Listener. */ + pbcc_event_listener_t* event_listener; + /** + * @brief Pointer to the Event Engine which handles all states and + * transitions on events. + */ + pbcc_event_engine_t* ee; + /** + * @brief Pointer to the PubNub context, which should be used for effects + * execution by Subscribe Event Engine effects dispatcher. + */ + pubnub_t* pb; + /** Shared resources access lock. */ + pubnub_mutex_t mutw; +}; + +/** + * @brief Events and state context object. + * + * Context object contains information related required for proper + * Subscribe Event Engine operation. States and events contain event engine + * context 'snapshot' at the moment when they have been created. + */ +typedef struct { + /** + * @brief Pointer to the comma-separated channels which are or will be used + * in subscription loop. + */ + pbcc_ee_data_t* channels; + /** + * @brief Pointer to the comma-separated channel groups which are or will be + * used in subscription loop. + */ + pbcc_ee_data_t* channel_groups; + /** Subscription cursor which is or will be used in subscription loop. */ + pubnub_subscribe_cursor_t cursor; + /** Previous request failure reason. */ + enum pubnub_res reason; + /** + * @brief Pointer to the PubNub context, which should be used for effects + * execution by Subscribe Event Engine effects dispatcher. + */ + pubnub_t* pb; +} pbcc_subscribe_ee_context_t; +#endif //PBCC_SUBSCRIBE_EVENT_ENGINE_TYPES_H diff --git a/core/pbcc_subscribe_event_listener.c b/core/pbcc_subscribe_event_listener.c index 32151dc4..dc81e7c7 100644 --- a/core/pbcc_subscribe_event_listener.c +++ b/core/pbcc_subscribe_event_listener.c @@ -1,11 +1,13 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pbcc_subscribe_event_listener.h" #include -#include + #include "core/pbcc_memory_utils.h" #include "core/pubnub_mutex.h" #include "lib/pbref_counter.h" #include "core/pubnub_log.h" +#include "pubnub_internal.h" #include "lib/pbhash_set.h" #include "lib/pbarray.h" @@ -14,7 +16,7 @@ // Constants // ---------------------------------------------- -// How many listener objects `pbcc_object_listener_t` can hold by default. +/** How many listener objects `pbcc_object_listener_t` can hold by default. */ #define LISTENERS_LENGTH 10 @@ -61,19 +63,19 @@ typedef struct { * @see pubnub_subscription_set_t */ const void* subscription_object; - // Type of real-time update for which listener will be called. + /** Type of real-time update for which listener will be called. */ pubnub_subscribe_listener_type type; - // Real-time update handling listener function. + /** Real-time update handling listener function. */ pubnub_subscribe_message_callback_t callback; - // Object references counter. + /** Object references counter. */ pbref_counter_t* counter; } pbcc_listener_t; -// Event Listener definition. +/** Event Listener definition. */ struct pbcc_event_listener { - // Pointer to the array with subscription change listeners. + /** Pointer to the array with subscription change listeners. */ pbarray_t* global_status; - // Pointer to the array with PubNub context listeners (global). + /** Pointer to the array with PubNub context listeners (global). */ pbarray_t* global_events; /** * @brief Pointer to the hash set with listeners mapped to the actual @@ -87,7 +89,7 @@ struct pbcc_event_listener { * registered event listener functions. */ const pubnub_t* pb; - // Shared resources access lock. + /** Shared resources access lock. */ pubnub_mutex_t mutw; }; @@ -105,7 +107,7 @@ struct pbcc_event_listener { * insufficient memory error. The returned pointer must be passed to the * `_pbcc_object_listener_free` to avoid a memory leak. */ -static pbcc_object_listener_t* _pbcc_object_listener_alloc(char* name); +static pbcc_object_listener_t* pbcc_object_listener_alloc_(char* name); /** * @brief Add / register object's listener. @@ -118,10 +120,10 @@ static pbcc_object_listener_t* _pbcc_object_listener_alloc(char* name); * be used for new updates. * @return Result of the listener addition. */ -static enum pubnub_res _pbcc_add_object_listener( +static enum pubnub_res pbcc_add_object_listener_( const pbcc_event_listener_t* event_listener, char* name, - const pbcc_listener_t* listener); + pbcc_listener_t* listener); /** * @brief Remove / unregister real-time update listener. @@ -134,9 +136,9 @@ static enum pubnub_res _pbcc_add_object_listener( * shouldn't be used for updates anymore. * @return Result of the listener removal. */ -static enum pubnub_res _pbcc_remove_object_listener( +static enum pubnub_res pbcc_remove_object_listener_( const pbcc_event_listener_t* event_listener, - const char* name, + char* name, const pbcc_listener_t* listener); /** @@ -145,7 +147,7 @@ static enum pubnub_res _pbcc_remove_object_listener( * @param listener Pointer to the updates listener, which should free up * resources. */ -static void _pbcc_object_listener_free(pbcc_object_listener_t* listener); +static void pbcc_object_listener_free_(pbcc_object_listener_t* listener); /** * @brief Create real-time events listener. @@ -159,7 +161,7 @@ static void _pbcc_object_listener_free(pbcc_object_listener_t* listener); * case of insufficient memory error. The returned pointer must be * passed to the `_pbcc_listener_free` to avoid a memory leak. */ -static pbcc_listener_t* _pbcc_listener_alloc( +static pbcc_listener_t* pbcc_listener_alloc_( const void* subscription, pubnub_subscribe_listener_type type, pubnub_subscribe_message_callback_t callback); @@ -172,9 +174,9 @@ static pbcc_listener_t* _pbcc_listener_alloc( * used for new updates. * @return Result of the listener addition. */ -static enum pubnub_res _pbcc_add_listener( - pbarray_t* listeners, - const pbcc_listener_t* listener); +static enum pubnub_res pbcc_add_listener_( + pbarray_t* listeners, + pbcc_listener_t* listener); /** * @brief Remove / unregister real-time update listener. @@ -184,7 +186,7 @@ static enum pubnub_res _pbcc_add_listener( * used for updates anymore. * @return Result of the listener removal. */ -static enum pubnub_res _pbcc_remove_listener( +static enum pubnub_res pbcc_remove_listener_( pbarray_t* listeners, const pbcc_listener_t* listener); @@ -194,7 +196,7 @@ static enum pubnub_res _pbcc_remove_listener( * @param listener Pointer to the object real-time events listeners, which * should free up resources. */ -static void _pbcc_listener_free(pbcc_listener_t* listener); +static void pbcc_listener_free_(pbcc_listener_t* listener); /** * @brief Helper function to notify listeners from the list. @@ -208,9 +210,9 @@ static void _pbcc_listener_free(pbcc_listener_t* listener); * be notified about new `message`. * @param message Received message which should be delivered to the listeners. */ -static void _pbcc_event_listener_emit_message( +static void pbcc_event_listener_emit_message_( const pbcc_event_listener_t* listener, - const pbarray_t* listeners, + pbarray_t* listeners, struct pubnub_v2_message message); /** @@ -222,7 +224,7 @@ static void _pbcc_event_listener_emit_message( * @param type Received message type specified by PubNub service. * @return Subscription event source type from message type. */ -static pubnub_subscribe_listener_type _pbcc_message_type_to_listener_type( +static pubnub_subscribe_listener_type pbcc_message_type_to_listener_type_( enum pubnub_message_type type); /** @@ -237,7 +239,7 @@ static pubnub_subscribe_listener_type _pbcc_message_type_to_listener_type( * memory error. The returned pointer must be passed to the * `pbarray_free` to avoid a memory leak. */ -static pbarray_t* _pbcc_initialize_array( +static pbarray_t* pbcc_initialize_array_( pbarray_t** array, pbarray_element_free free_fn); @@ -262,8 +264,8 @@ enum pubnub_res pbcc_event_listener_add_status_listener( if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } pubnub_mutex_lock(listener->mutw); - // Check whether listeners array should be created on demand or not. - if (NULL == _pbcc_initialize_array(&listener->global_status, NULL)) { + /** Check whether listeners array should be created on demand or not. */ + if (NULL == pbcc_initialize_array_(&listener->global_status, NULL)) { pubnub_mutex_unlock(listener->mutw); return PNR_OUT_OF_MEMORY; } @@ -275,8 +277,8 @@ enum pubnub_res pbcc_event_listener_add_status_listener( } enum pubnub_res pbcc_event_listener_remove_status_listener( - pbcc_event_listener_t* listener, - const pubnub_subscribe_status_callback_t cb) + pbcc_event_listener_t* listener, + pubnub_subscribe_status_callback_t cb) { if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } @@ -286,7 +288,7 @@ enum pubnub_res pbcc_event_listener_remove_status_listener( return PNR_OK; } - pbarray_remove(listener->global_status, cb, true); + pbarray_remove(listener->global_status, (void**)&cb, true); pubnub_mutex_unlock(listener->mutw); return PNR_OK; @@ -300,26 +302,27 @@ enum pubnub_res pbcc_event_listener_add_message_listener( if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } pubnub_mutex_lock(listener->mutw); - // Check whether listeners array should be created on demand or not. - if (NULL == _pbcc_initialize_array(&listener->global_events, - _pbcc_listener_free)) { + /** Check whether listeners array should be created on demand or not. */ + if (NULL == pbcc_initialize_array_(&listener->global_events, + (pbarray_element_free) + pbcc_listener_free_)) { pubnub_mutex_unlock(listener->mutw); return PNR_OUT_OF_MEMORY; } - pbcc_listener_t* _listener = _pbcc_listener_alloc(NULL, type, cb); + pbcc_listener_t* _listener = pbcc_listener_alloc_(NULL, type, cb); if (NULL == _listener) { pubnub_mutex_unlock(listener->mutw); return PNR_OUT_OF_MEMORY; } - const pbarray_res result = _pbcc_add_listener( + const enum pubnub_res result = pbcc_add_listener_( listener->global_events, _listener); - if (PNR_OK != result) { _pbcc_listener_free(_listener); } + if (PNR_OK != result) { pbcc_listener_free_(_listener); } pubnub_mutex_unlock(listener->mutw); - return PBAR_OUT_OF_MEMORY != result ? PNR_OK : PNR_OUT_OF_MEMORY; + return result; } enum pubnub_res pbcc_event_listener_remove_message_listener( @@ -336,18 +339,20 @@ enum pubnub_res pbcc_event_listener_remove_message_listener( return PNR_OK; } - pbcc_listener_t* _listener = _pbcc_listener_alloc(NULL, type, cb); + pbcc_listener_t* _listener = pbcc_listener_alloc_(NULL, type, cb); if (NULL == _listener) { pubnub_mutex_unlock(listener->mutw); return PNR_OUT_OF_MEMORY; } - const enum pubnub_res rslt = _pbcc_remove_listener( + const enum pubnub_res rslt = pbcc_remove_listener_( listener->global_events, _listener); - // It is safe to release temporarily object which used only to match object. - _pbcc_listener_free(_listener); + /** + * It is safe to release temporarily object which used only to match object. + */ + pbcc_listener_free_(_listener); pubnub_mutex_unlock(listener->mutw); return rslt; @@ -356,19 +361,19 @@ enum pubnub_res pbcc_event_listener_remove_message_listener( enum pubnub_res pbcc_event_listener_add_subscription_object_listener( pbcc_event_listener_t* listener, const pubnub_subscribe_listener_type type, - const pbarray_t* names, + pbarray_t* names, const void* subscription, const pubnub_subscribe_message_callback_t cb) { if (NULL == listener || NULL == cb) { return PNR_INVALID_PARAMETERS; } pubnub_mutex_lock(listener->mutw); - // Check whether object listeners hash set is set or not. + /** Check whether object listeners hash set is set or not. */ if (NULL == listener->listeners) { listener->listeners = pbhash_set_alloc( LISTENERS_LENGTH, PBHASH_SET_CHAR_CONTENT_TYPE, - _pbcc_object_listener_free); + (pbhash_set_element_free)pbcc_object_listener_free_); if (NULL == listener->listeners) { pubnub_mutex_unlock(listener->mutw); @@ -376,7 +381,7 @@ enum pubnub_res pbcc_event_listener_add_subscription_object_listener( } } - pbcc_listener_t* _listener = _pbcc_listener_alloc(subscription, type, cb); + pbcc_listener_t* _listener = pbcc_listener_alloc_(subscription, type, cb); bool added = false; if (NULL == _listener) { pubnub_mutex_unlock(listener->mutw); @@ -387,20 +392,20 @@ enum pubnub_res pbcc_event_listener_add_subscription_object_listener( const size_t names_count = pbarray_count(names); for (size_t i = 0; i < names_count; ++i) { char* name = (char*)pbarray_element_at(names, i); - rslt = _pbcc_add_object_listener(listener, name, _listener); + rslt = pbcc_add_object_listener_(listener, name, _listener); if (PNR_OK == rslt) { added = true; } else { break; } } if (added && PNR_OK != rslt) { - // Remove any added entries of the `listener` in case of failure. + /** Remove any added entries of the `listener` in case of failure. */ for (size_t i = 0; i < names_count; ++i) { - const char* name = pbarray_element_at(names, i); - _pbcc_remove_object_listener(listener, name, _listener); + char* name = (char*)pbarray_element_at(names, i); + pbcc_remove_object_listener_(listener, name, _listener); } } - // Manual `free` required if arrays doesn't manage listener lifetime. - if (PNR_OK != rslt && !added) { _pbcc_listener_free(_listener); } + /** Manual `free` required if arrays doesn't manage listener lifetime. */ + if (PNR_OK != rslt && !added) { pbcc_listener_free_(_listener); } pubnub_mutex_unlock(listener->mutw); return rslt; @@ -409,7 +414,7 @@ enum pubnub_res pbcc_event_listener_add_subscription_object_listener( enum pubnub_res pbcc_event_listener_remove_subscription_object_listener( pbcc_event_listener_t* listener, const pubnub_subscribe_listener_type type, - const pbarray_t* names, + pbarray_t* names, const void* subscription, const pubnub_subscribe_message_callback_t cb) { @@ -421,7 +426,7 @@ enum pubnub_res pbcc_event_listener_remove_subscription_object_listener( return PNR_OK; } - pbcc_listener_t* _listener = _pbcc_listener_alloc(subscription, type, cb); + pbcc_listener_t* _listener = pbcc_listener_alloc_(subscription, type, cb); if (NULL == _listener) { pubnub_mutex_unlock(listener->mutw); return PNR_OUT_OF_MEMORY; @@ -429,37 +434,43 @@ enum pubnub_res pbcc_event_listener_remove_subscription_object_listener( const size_t names_count = pbarray_count(names); for (size_t i = 0; i < names_count; ++i) { - const char* name = pbarray_element_at(names, i); - _pbcc_remove_object_listener(listener, name, _listener); + char* name = (char*)pbarray_element_at(names, i); + pbcc_remove_object_listener_(listener, name, _listener); } - // It is safe to release temporarily object which used only to match object. - _pbcc_listener_free(_listener); + /** + * It is safe to release temporarily object which used only to match object. + */ + pbcc_listener_free_(_listener); pubnub_mutex_unlock(listener->mutw); return PNR_OK; } void pbcc_event_listener_emit_status( - const pbcc_event_listener_t* listener, + pbcc_event_listener_t* listener, const pubnub_subscription_status status, - const enum pubnub_res reason) + const enum pubnub_res reason, + const char* channels, + const char* channel_groups) { if (NULL == listener) { return; } pubnub_mutex_lock(listener->mutw); + const pubnub_subscription_status_data_t data = + { reason, channels, channel_groups }; const size_t status_count = pbarray_count(listener->global_status); for (size_t i = 0; i < status_count; ++i) { const pubnub_subscribe_status_callback_t cb = ( pubnub_subscribe_status_callback_t) pbarray_element_at(listener->global_status, i); - cb(listener->pb, status, reason); + cb(listener->pb, status, data); } pubnub_mutex_unlock(listener->mutw); } void pbcc_event_listener_emit_message( - const pbcc_event_listener_t* listener, + pbcc_event_listener_t* listener, const struct pubnub_v2_message message) { if (NULL == listener) { return; } @@ -467,7 +478,7 @@ void pbcc_event_listener_emit_message( pubnub_mutex_lock(listener->mutw); // Notify global message listeners (if any has been registered). if (NULL != listener->global_events) { - _pbcc_event_listener_emit_message(listener, + pbcc_event_listener_emit_message_(listener, listener->global_events, message); } @@ -476,45 +487,48 @@ void pbcc_event_listener_emit_message( const pbcc_object_listener_t* object_listener = (pbcc_object_listener_t*) pbhash_set_element(listener->listeners, message.channel.ptr); if (NULL != object_listener) { - _pbcc_event_listener_emit_message(listener, + pbcc_event_listener_emit_message_(listener, object_listener->listeners, message); } pubnub_mutex_unlock(listener->mutw); } -void pbcc_event_listener_free(pbcc_event_listener_t* event_listener) +void pbcc_event_listener_free(pbcc_event_listener_t** event_listener) { - if (NULL == event_listener) { return; } - - pubnub_mutex_lock(event_listener->mutw); - pbarray_free(event_listener->global_status); - pbarray_free(event_listener->global_events); - pbhash_set_free(event_listener->listeners); - pubnub_mutex_unlock(event_listener->mutw); - pubnub_mutex_destroy(event_listener->mutw); - free(event_listener); + if (NULL == event_listener || NULL == *event_listener) { return; } + + pubnub_mutex_lock((*event_listener)->mutw); + pbarray_free(&(*event_listener)->global_status); + pbarray_free(&(*event_listener)->global_events); + pbhash_set_free(&(*event_listener)->listeners); + pubnub_mutex_unlock((*event_listener)->mutw); + pubnub_mutex_destroy((*event_listener)->mutw); + free(*event_listener); + *event_listener = NULL; } -pbcc_object_listener_t* _pbcc_object_listener_alloc(char* name) +pbcc_object_listener_t* pbcc_object_listener_alloc_(char* name) { PBCC_ALLOCATE_TYPE(updates, pbcc_object_listener_t, true, NULL); updates->name = name; - _pbcc_initialize_array(&updates->listeners, _pbcc_listener_free); + pbcc_initialize_array_( + &updates->listeners, + (pbarray_element_free)pbcc_listener_free_); return updates; } -enum pubnub_res _pbcc_add_object_listener( +enum pubnub_res pbcc_add_object_listener_( const pbcc_event_listener_t* event_listener, char* name, - const pbcc_listener_t* listener) + pbcc_listener_t* listener) { // Try to retrieve previously created object-scoped listeners. pbcc_object_listener_t* object_listener = (pbcc_object_listener_t*) pbhash_set_element(event_listener->listeners, name); if (NULL == object_listener) { - object_listener = _pbcc_object_listener_alloc(name); + object_listener = pbcc_object_listener_alloc_(name); if (NULL == object_listener) { return PNR_OUT_OF_MEMORY; } const pbhash_set_res rslt = pbhash_set_add( @@ -522,17 +536,17 @@ enum pubnub_res _pbcc_add_object_listener( name, object_listener); if (PBHSR_OUT_OF_MEMORY == rslt) { - _pbcc_object_listener_free(object_listener); + pbcc_object_listener_free_(object_listener); return PNR_OUT_OF_MEMORY; } } - return _pbcc_add_listener(object_listener->listeners, listener); + return pbcc_add_listener_(object_listener->listeners, listener); } -enum pubnub_res _pbcc_remove_object_listener( +enum pubnub_res pbcc_remove_object_listener_( const pbcc_event_listener_t* event_listener, - const char* name, + char* name, const pbcc_listener_t* listener) { // Try to retrieve previously created object-scoped listeners. @@ -541,26 +555,26 @@ enum pubnub_res _pbcc_remove_object_listener( name); if (NULL == object_listener) { return PNR_OK; } - const enum pubnub_res rslt = _pbcc_remove_listener( + const enum pubnub_res rslt = pbcc_remove_listener_( object_listener->listeners, listener); if (PNR_OK == rslt && 0 == pbarray_count(object_listener->listeners)) - pbhash_set_remove(event_listener->listeners, name); + pbhash_set_remove(event_listener->listeners, (void**)&name, NULL); return rslt; } -void _pbcc_object_listener_free(pbcc_object_listener_t* listener) +void pbcc_object_listener_free_(pbcc_object_listener_t* listener) { if (NULL == listener) { return; } - if (NULL != listener->listeners) { pbarray_free(listener->listeners); } + if (NULL != listener->listeners) { pbarray_free(&listener->listeners); } if (NULL != listener->name) { free(listener->name); } free(listener); } -pbcc_listener_t* _pbcc_listener_alloc( +pbcc_listener_t* pbcc_listener_alloc_( const void* subscription, const pubnub_subscribe_listener_type type, const pubnub_subscribe_message_callback_t callback) @@ -574,9 +588,9 @@ pbcc_listener_t* _pbcc_listener_alloc( return listener; } -enum pubnub_res _pbcc_add_listener( - pbarray_t* listeners, - const pbcc_listener_t* listener) +enum pubnub_res pbcc_add_listener_( + pbarray_t* listeners, + pbcc_listener_t* listener) { const pbarray_res rslt = pbarray_add(listeners, listener); if (PBAR_OK == rslt) { pbref_counter_increment(listener->counter); } @@ -584,7 +598,7 @@ enum pubnub_res _pbcc_add_listener( return PBAR_OK == rslt ? PNR_OK : PNR_OUT_OF_MEMORY; } -enum pubnub_res _pbcc_remove_listener( +enum pubnub_res pbcc_remove_listener_( pbarray_t* listeners, const pbcc_listener_t* listener) { @@ -592,12 +606,14 @@ enum pubnub_res _pbcc_remove_listener( if (NULL == listener) { return PNR_INVALID_PARAMETERS; } for (size_t i = 0; i < pbarray_count(listeners);) { - const pbcc_listener_t* _listener = pbarray_element_at(listeners, i); + pbcc_listener_t* _listener = (pbcc_listener_t*) + pbarray_element_at(listeners, i); if (_listener->type == listener->type && _listener->subscription_object == listener->subscription_object && _listener->callback == listener->callback) { - pbarray_remove(listeners, _listener, true); + pbref_counter_decrement(_listener->counter); + pbarray_remove(listeners, (void**)&_listener, true); } else { i++; } } @@ -605,19 +621,19 @@ enum pubnub_res _pbcc_remove_listener( return PNR_OK; } -void _pbcc_listener_free(pbcc_listener_t* listener) +void pbcc_listener_free_(pbcc_listener_t* listener) { if (NULL == listener) { return; } if (0 == pbref_counter_free(listener->counter)) { free(listener); } } -void _pbcc_event_listener_emit_message( +void pbcc_event_listener_emit_message_( const pbcc_event_listener_t* listener, - const pbarray_t* listeners, + pbarray_t* listeners, const struct pubnub_v2_message message) { const pubnub_subscribe_listener_type type = - _pbcc_message_type_to_listener_type(message.message_type); + pbcc_message_type_to_listener_type_(message.message_type); const size_t count = pbarray_count(listeners); for (size_t i = 0; i < count; ++i) { @@ -628,7 +644,7 @@ void _pbcc_event_listener_emit_message( } } -pubnub_subscribe_listener_type _pbcc_message_type_to_listener_type( +pubnub_subscribe_listener_type pbcc_message_type_to_listener_type_( enum pubnub_message_type type) { switch (type) { @@ -647,7 +663,7 @@ pubnub_subscribe_listener_type _pbcc_message_type_to_listener_type( return LISTENER_ON_MESSAGE; } -pbarray_t* _pbcc_initialize_array( +pbarray_t* pbcc_initialize_array_( pbarray_t** array, const pbarray_element_free free_fn) { diff --git a/core/pbcc_subscribe_event_listener.h b/core/pbcc_subscribe_event_listener.h index 42554599..4c914aee 100644 --- a/core/pbcc_subscribe_event_listener.h +++ b/core/pbcc_subscribe_event_listener.h @@ -1,5 +1,12 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PBCC_SUBSCRIBE_EVENT_LISTENER_H #define PBCC_SUBSCRIBE_EVENT_LISTENER_H +#if !PUBNUB_USE_SUBSCRIBE_V2 +#error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API /** @@ -8,16 +15,16 @@ * definitions and functions. */ -#include "pbarray.h" #include "core/pubnub_subscribe_event_listener_types.h" #include "core/pubnub_api_types.h" +#include "lib/pbarray.h" // ---------------------------------------------- // Types forwarding // ---------------------------------------------- -// Event Listener definition. +/** Event Listener definition. */ typedef struct pbcc_event_listener pbcc_event_listener_t; @@ -44,7 +51,7 @@ pbcc_event_listener_t* pbcc_event_listener_alloc(const pubnub_t* pb); * @return Results of listener addition. */ enum pubnub_res pbcc_event_listener_add_status_listener( - pbcc_event_listener_t* listener, + pbcc_event_listener_t* listener, pubnub_subscribe_status_callback_t cb); /** @@ -60,7 +67,7 @@ enum pubnub_res pbcc_event_listener_add_status_listener( * @return Results of listener removal. */ enum pubnub_res pbcc_event_listener_remove_status_listener( - pbcc_event_listener_t* listener, + pbcc_event_listener_t* listener, pubnub_subscribe_status_callback_t cb); /** @@ -76,8 +83,8 @@ enum pubnub_res pbcc_event_listener_remove_status_listener( * @return Results of listener addition. */ enum pubnub_res pbcc_event_listener_add_message_listener( - pbcc_event_listener_t* listener, - pubnub_subscribe_listener_type type, + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, pubnub_subscribe_message_callback_t cb); /** @@ -95,8 +102,8 @@ enum pubnub_res pbcc_event_listener_add_message_listener( * @return Results of listener removal. */ enum pubnub_res pbcc_event_listener_remove_message_listener( - const pbcc_event_listener_t* listener, - pubnub_subscribe_listener_type type, + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, pubnub_subscribe_message_callback_t cb); /** @@ -114,10 +121,10 @@ enum pubnub_res pbcc_event_listener_remove_message_listener( * @return Result of listener addition. */ enum pubnub_res pbcc_event_listener_add_subscription_object_listener( - const pbcc_event_listener_t* listener, - pubnub_subscribe_listener_type type, - pbarray_t* names, - const void* subscription, + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, + pbarray_t* names, + const void* subscription, pubnub_subscribe_message_callback_t cb); /** @@ -140,27 +147,34 @@ enum pubnub_res pbcc_event_listener_add_subscription_object_listener( * @return Results of listener removal. */ enum pubnub_res pbcc_event_listener_remove_subscription_object_listener( - const pbcc_event_listener_t* listener, - pubnub_subscribe_listener_type type, - pbarray_t* names, - const void* subscription, + pbcc_event_listener_t* listener, + pubnub_subscribe_listener_type type, + pbarray_t* names, + const void* subscription, pubnub_subscribe_message_callback_t cb); /** * @brief Notify subscription status listeners about status change. * - * @param listener Pointer to the Event Listener which contains list of - * listeners for subscription status change event. - * @param status New subscription status which should be sent the the - * listeners. - * @param reason In case of `SUBSCRIPTION_STATUS_CONNECTION_ERROR` and - * `SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY` may contain - * additional information about reasons of failure. + * @param listener Pointer to the Event Listener which contains list of + * listeners for subscription status change event. + * @param status New subscription status which should be sent the the + * listeners. + * @param reason In case of `SUBSCRIPTION_STATUS_CONNECTION_ERROR` and + * `SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY` may + * contain additional information about reasons of + * failure. + * @param channels Byte string with comma-separated / `NULL` channel + * identifiers which have been used with recent operation. + * @param channel_groups Byte string with comma-separated / `NULL` channel group + * identifiers which have been used with recent operation. */ void pbcc_event_listener_emit_status( - const pbcc_event_listener_t* listener, - pubnub_subscription_status status, - enum pubnub_res reason); + pbcc_event_listener_t* listener, + pubnub_subscription_status status, + enum pubnub_res reason, + const char* channels, + const char* channel_groups); /** * @brief Notify listeners about new real-time update / message. @@ -170,8 +184,8 @@ void pbcc_event_listener_emit_status( * @param message Received message which should be delivered to the listeners. */ void pbcc_event_listener_emit_message( - const pbcc_event_listener_t* listener, - struct pubnub_v2_message message); + pbcc_event_listener_t* listener, + struct pubnub_v2_message message); /** * @brief Clean up resources used by the Event Listener object. @@ -179,5 +193,5 @@ void pbcc_event_listener_emit_message( * @param event_listener Pointer to the Event Listener, which should free up * used resources. */ -void pbcc_event_listener_free(pbcc_event_listener_t* event_listener); -#endif //PBCC_SUBSCRIBE_EVENT_LISTENER_H \ No newline at end of file +void pbcc_event_listener_free(pbcc_event_listener_t** event_listener); +#endif // #ifndef PBCC_SUBSCRIBE_EVENT_LISTENER_H diff --git a/core/pbpal_ntf_callback_admin.c b/core/pbpal_ntf_callback_admin.c index cf1c75b4..9aa3791f 100644 --- a/core/pbpal_ntf_callback_admin.c +++ b/core/pbpal_ntf_callback_admin.c @@ -1,23 +1,59 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_internal.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pbntf_trans_outcome_common.h" #include "pubnub_log.h" #include "pubnub_assert.h" + void pbntf_trans_outcome(pubnub_t* pb, enum pubnub_state state) { PBNTF_TRANS_OUTCOME_COMMON(pb, state); PUBNUB_ASSERT(pbnc_can_start_transaction(pb)); - pb->flags.sent_queries = 0; - if (pb->cb != NULL) { - PUBNUB_LOG_TRACE("pbntf_trans_outcome(pb=%p) calling callback:\n" - "pb->trans = %d, pb->core.last_result=%d, pb->user_data=%p\n", - pb, pb->trans, pb->core.last_result, pb->user_data); - pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); +#if PUBNUB_USE_RETRY_CONFIGURATION + pubnub_mutex_lock(pb->monitor); + uint16_t delay = 0; + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + delay = pubnub_retry_configuration_delay_(pb); + } + pubnub_mutex_unlock(pb->monitor); + + if (delay > 0) { + pubnub_mutex_lock(pb->monitor); + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + + if (NULL != pb->core.retry_timer) { + pubnub_mutex_unlock(pb->monitor); + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + } else { pubnub_mutex_unlock(pb->monitor); } + } else { + pubnub_mutex_lock(pb->monitor); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_stop(pb->core.retry_timer); + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } + pubnub_mutex_unlock(pb->monitor); + +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + pb->flags.sent_queries = 0; + if (pb->cb != NULL) { + PUBNUB_LOG_TRACE("pbntf_trans_outcome(pb=%p) calling callback:\n" + "pb->trans = %d, pb->core.last_result=%d, pb->user_data=%p\n", + pb, pb->trans, pb->core.last_result, pb->user_data); + pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); + } +#if PUBNUB_USE_RETRY_CONFIGURATION } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } void pbnc_tr_cxt_state_reset(pubnub_t* pb) diff --git a/core/pubnub_ccore_pubsub.c b/core/pubnub_ccore_pubsub.c index 2c0af713..adc00c43 100644 --- a/core/pubnub_ccore_pubsub.c +++ b/core/pubnub_ccore_pubsub.c @@ -46,7 +46,11 @@ void pbcc_init(struct pbcc_context* p, const char* publish_key, const char* subs #endif /* PUBNUB_DYNAMIC_REPLY_BUFFER */ p->message_to_send = NULL; p->state = NULL; - +#if PUBNUB_USE_RETRY_CONFIGURATION + p->retry_configuration = NULL; + p->http_retry_count = 0; + p->retry_timer = NULL; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION #if PUBNUB_USE_GZIP_COMPRESSION p->gzip_msg_len = 0; #endif @@ -73,6 +77,15 @@ void pbcc_deinit(struct pbcc_context* p) } #endif /* PUBNUB_RECEIVE_GZIP_RESPONSE */ #endif /* PUBNUB_DYNAMIC_REPLY_BUFFER */ +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != p->retry_configuration) + pubnub_retry_configuration_free(&p->retry_configuration); + p->http_retry_count = 0; + if (NULL != p->retry_timer) { + pbcc_request_retry_timer_stop(p->retry_timer); + pbcc_request_retry_timer_free(&p->retry_timer); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION #if PUBNUB_CRYPTO_API if (NULL != p->crypto_module) { free(p->crypto_module); @@ -80,8 +93,12 @@ void pbcc_deinit(struct pbcc_context* p) } #endif /* PUBNUB_CRYPTO_API */ #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE - pbcc_subscribe_ee_free(p->subscribe_ee); + pbcc_subscribe_ee_free(&p->subscribe_ee); #endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#if PUBNUB_USE_RETRY_CONFIGURATION + if (NULL != p->retry_configuration) + pubnub_retry_configuration_free(&p->retry_configuration); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } diff --git a/core/pubnub_ccore_pubsub.h b/core/pubnub_ccore_pubsub.h index 02fec690..7accec9b 100644 --- a/core/pubnub_ccore_pubsub.h +++ b/core/pubnub_ccore_pubsub.h @@ -6,6 +6,11 @@ #include "pbcc_subscribe_event_engine.h" #endif +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration.h" +#include "core/pbcc_request_retry_timer.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_config.h" #include "pubnub_api_types.h" #include "pubnub_generate_uuid.h" @@ -128,6 +133,15 @@ struct pbcc_context { */ unsigned chan_ofs, chan_end; +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Pointer to the configuration with failed request handling details. */ + pubnub_retry_configuration_t* retry_configuration; + /** Failed request retry timer. */ + pbcc_request_retry_timer_t* retry_timer; + /** Current number of retry attempts. */ + int http_retry_count; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #if PUBNUB_CRYPTO_API /** Secret key to use for encryption/decryption */ char const* secret_key; diff --git a/core/pubnub_entities.c b/core/pubnub_entities.c index d4acfcef..dd55bae2 100644 --- a/core/pubnub_entities.c +++ b/core/pubnub_entities.c @@ -1,12 +1,13 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_entities.h" -#include "pubnub_entities_internal.h" + #include -#include -#include "pbcc_memory_utils.h" -#include "core/pubnub_internal_common.h" +#include "core/pubnub_entities_internal.h" +#include "core/pbcc_memory_utils.h" #include "core/pubnub_assert.h" #include "core/pubnub_log.h" +#include "pubnub_internal.h" #include "lib/pbstrdup.h" @@ -25,7 +26,7 @@ * @return Ready to use PubNub entity. The returned pointer must be passed to * the `pubnub_entity_free` to avoid a memory leak. */ -static pubnub_entity_t* _create_entity( +static pubnub_entity_t* create_entity_( pubnub_t* pb, const char* id, pubnub_entity_type type); @@ -37,7 +38,8 @@ static pubnub_entity_t* _create_entity( * entity. * @return `true` if passed `entity` is one of PubNub entities. */ -static bool _is_pubnub_entity(void* entity); +static bool is_pubnub_entity_(void* entity); + // ---------------------------------- // Functions @@ -47,14 +49,14 @@ pubnub_channel_t* pubnub_channel_alloc( pubnub_t* pb, const char* name) { - return (pubnub_channel_t*)_create_entity(pb, name, PUBNUB_ENTITY_CHANNEL); + return (pubnub_channel_t*)create_entity_(pb, name, PUBNUB_ENTITY_CHANNEL); } pubnub_channel_group_t* pubnub_channel_group_alloc( pubnub_t* pb, const char* name) { - return (pubnub_channel_group_t*)_create_entity( + return (pubnub_channel_group_t*)create_entity_( pb, name, PUBNUB_ENTITY_CHANNEL_GROUP); @@ -64,7 +66,7 @@ pubnub_channel_metadata_t* pubnub_channel_metadata_alloc( pubnub_t* pb, const char* id) { - return (pubnub_channel_metadata_t*)_create_entity( + return (pubnub_channel_metadata_t*)create_entity_( pb, id, PUBNUB_ENTITY_CHANNEL_METADATA); @@ -74,29 +76,35 @@ pubnub_user_metadata_t* pubnub_user_metadata_alloc( pubnub_t* pb, const char* id) { - return (pubnub_user_metadata_t*)_create_entity( + return (pubnub_user_metadata_t*)create_entity_( pb, id, PUBNUB_ENTITY_USER_METADATA); } -void pubnub_entity_free(pubnub_entity_t* entity) +bool pubnub_entity_free(void** entity) { - if (NULL == entity) { return; } - PUBNUB_ASSERT(_is_pubnub_entity(entity)); - - _entity_reference_count_update(entity, false); - pubnub_mutex_lock(entity->mutw); - if (entity->reference_count == 0) { - free(entity->id.ptr); - pubnub_mutex_unlock(entity->mutw); - pubnub_mutex_destroy(entity->mutw); - free(entity); + if (NULL == entity || NULL == *entity) { return false; } + + printf("~~~~~~> RESULT: %d\n", is_pubnub_entity_(*entity)); + PUBNUB_ASSERT_OPT(true == is_pubnub_entity_(*entity)); + + pubnub_entity_t* _entity = *entity; + pubnub_mutex_lock(_entity->mutw); + if (0 == pbref_counter_free(_entity->counter)) { + free(_entity->id.ptr); + pubnub_mutex_unlock(_entity->mutw); + pubnub_mutex_destroy(_entity->mutw); + free(*entity); + *entity = NULL; + return true; } - else { pubnub_mutex_unlock(entity->mutw); } + pubnub_mutex_unlock(_entity->mutw); + + return false; } -pubnub_entity_t* _create_entity( +pubnub_entity_t* create_entity_( pubnub_t* pb, const char* id, const pubnub_entity_type type) @@ -115,14 +123,14 @@ pubnub_entity_t* _create_entity( } pubnub_mutex_init(entity->mutw); - entity->reference_count = 1; - entity->type = type; - entity->pb = pb; + entity->counter = pbref_counter_alloc(); + entity->type = type; + entity->pb = pb; return entity; } -bool _is_pubnub_entity(void* entity) +bool is_pubnub_entity_(void* entity) { if (NULL == entity) { return false; } @@ -133,15 +141,14 @@ bool _is_pubnub_entity(void* entity) || type == PUBNUB_ENTITY_USER_METADATA; } -void _entity_reference_count_update( +void entity_reference_count_update_( pubnub_entity_t* entity, const bool increase) { PUBNUB_ASSERT_OPT(NULL != entity); pubnub_mutex_lock(entity->mutw); - if (increase) { entity->reference_count++; } - else - if (entity->reference_count > 0) { entity->reference_count--; } + if (increase) { pbref_counter_increment(entity->counter); } + else { pbref_counter_decrement(entity->counter); } pubnub_mutex_unlock(entity->mutw); } \ No newline at end of file diff --git a/core/pubnub_entities.h b/core/pubnub_entities.h index ac6ae45f..98e5c792 100644 --- a/core/pubnub_entities.h +++ b/core/pubnub_entities.h @@ -1,8 +1,10 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_ENTITIES_H #define PUBNUB_ENTITIES_H // Currently, entities is part of the subscribe event engine feature. #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + /** * @file pubnub_entities.h * @brief Module to manage first-class types. @@ -10,6 +12,8 @@ * Entities used to access contextual endpoints related to a specific entity. */ +#include + #include "core/pubnub_api_types.h" #include "lib/pb_extern.h" @@ -18,19 +22,19 @@ // Types // ---------------------------------- -// PubNub Channel entity definition. +/** PubNub Channel entity definition. */ typedef struct pubnub_channel pubnub_channel_t; -// PubNub Channel Group entity definition. +/** PubNub Channel Group entity definition. */ typedef struct pubnub_channel_group pubnub_channel_group_t; -// PubNub Channel Metadata entity (App Context) definition. +/** PubNub Channel Metadata entity (App Context) definition. */ typedef struct pubnub_channel_metadata pubnub_channel_metadata_t; -// PubNub User Metadata entity (App Context) definition. +/** PubNub User Metadata entity (App Context) definition. */ typedef struct pubnub_user_metadata pubnub_user_metadata_t; -// PubNub Entity definition. +/** PubNub Entity definition. */ typedef struct pubnub_entity pubnub_entity_t; @@ -125,16 +129,20 @@ PUBNUB_EXTERN pubnub_user_metadata_t* pubnub_user_metadata_alloc( * @code * pubnub_channel_group_t* group = pubnub_channel_group_alloc(pb, "my_group"); * // ... - * pubnub_entity_free(group); + * pubnub_entity_free(&group); * @endcode * * \b Warning: Make sure that calls to the `pubnub_entity_free` is done only * once to not disrupt other types which still use this entity. * + * @note Function will `NULL`ify provided entity pointer (if there is no more + * references to the entity). + * * @param entity PubNub entity, which should free up resources. + * @return `false` if there are more references to the entity. */ -PUBNUB_EXTERN void pubnub_entity_free(pubnub_entity_t* entity); -#else +PUBNUB_EXTERN bool pubnub_entity_free(void** entity); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use entities API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PUBNUB_ENTITIES_H \ No newline at end of file +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_ENTITIES_H \ No newline at end of file diff --git a/core/pubnub_entities_internal.h b/core/pubnub_entities_internal.h index dd0ed9c2..36fa0ed5 100644 --- a/core/pubnub_entities_internal.h +++ b/core/pubnub_entities_internal.h @@ -1,24 +1,29 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_ENTITIES_INTERNAL_H #define PUBNUB_ENTITIES_INTERNAL_H // Currently, entities is part of the subscribe event engine feature. #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE + /** * @file pubnub_entities_internal.h * @brief Module to manage first-class types internal / lib interface. */ +#include + #include "core/pubnub_memory_block.h" #include "core/pubnub_api_types.h" -#if PUBNUB_THREADSAFE #include "core/pubnub_mutex.h" -#endif +#include "lib/pbref_counter.h" + + // ---------------------------------- // Types // ---------------------------------- -// Known PubNub entities. +/** Known PubNub entities. */ typedef enum { PUBNUB_ENTITY_CHANNEL, PUBNUB_ENTITY_CHANNEL_GROUP, @@ -26,60 +31,53 @@ typedef enum { PUBNUB_ENTITY_USER_METADATA, } pubnub_entity_type; -// PubNub Channel entity definition. +/** PubNub Channel entity definition. */ struct pubnub_channel { - // Channel entity type (`PUBNUB_ENTITY_CHANNEL`). + /** Channel entity type (`PUBNUB_ENTITY_CHANNEL`). */ pubnub_entity_type type; - // Identifier which will be used with channel-specific REST API. + /** Identifier which will be used with channel-specific REST API. */ struct pubnub_char_mem_block name; }; -// PubNub Channel Group entity definition. +/** PubNub Channel Group entity definition. */ struct pubnub_channel_group { - // Channel group entity type (`PUBNUB_ENTITY_CHANNEL_GROUP`). + /** Channel group entity type (`PUBNUB_ENTITY_CHANNEL_GROUP`). */ pubnub_entity_type type; - // Identifier which will be used with channel group-specific REST API. + /** Identifier which will be used with channel group-specific REST API. */ struct pubnub_char_mem_block name; }; -// PubNub Channel Metadata entity (App Context) definition. +/** PubNub Channel Metadata entity (App Context) definition. */ struct pubnub_channel_metadata { - // Channel metadata entity type (`PUBNUB_ENTITY_CHANNEL_METADATA`). + /** Channel metadata entity type (`PUBNUB_ENTITY_CHANNEL_METADATA`). */ pubnub_entity_type type; - // Identifier which will be used with channel metadata specific REST API. + /** Identifier which will be used with channel metadata specific REST API. */ struct pubnub_char_mem_block id; }; -// PubNub User Metadata entity (App Context) definition. +/** PubNub User Metadata entity (App Context) definition. */ struct pubnub_user_metadata { - // User metadata entity type (`PUBNUB_ENTITY_USER_METADATA`). + /** User metadata entity type (`PUBNUB_ENTITY_USER_METADATA`). */ pubnub_entity_type type; - // Identifier which will be used with user metadata specific REST API. + /** Identifier which will be used with user metadata specific REST API. */ struct pubnub_char_mem_block id; }; -// PubNub Entity definition. +/** PubNub Entity definition. */ struct pubnub_entity { - // PubNub entity type. + /** PubNub entity type. */ pubnub_entity_type type; - // Entity identifier. + /** Entity identifier. */ struct pubnub_char_mem_block id; /** * @brief PubNub context, which will be used by the created entity to * interact with PubNub REST API */ pubnub_t* pb; - /** - * @brief How many subscriptions have reference to this entity. - * - * \b Important: `pubnub_entity_free` won't do anything if value is - * larger than 0. - */ - int reference_count; -#if PUBNUB_THREADSAFE - // Shared resources access lock. + /** Object references counter. */ + pbref_counter_t* counter; + /** Shared resources access lock. */ pubnub_mutex_t mutw; -#endif }; @@ -94,9 +92,8 @@ struct pubnub_entity { * should be updated. * @param increase Whether reference count should be increased or decreased. */ -void _entity_reference_count_update(pubnub_entity_t* entity, bool increase); - -#else +void entity_reference_count_update_(pubnub_entity_t* entity, bool increase); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use entities API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif //PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PUBNUB_ENTITIES_INTERNAL_H \ No newline at end of file +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_ENTITIES_INTERNAL_H \ No newline at end of file diff --git a/core/pubnub_internal_common.h b/core/pubnub_internal_common.h index c90768ae..a8977c1c 100644 --- a/core/pubnub_internal_common.h +++ b/core/pubnub_internal_common.h @@ -1,4 +1,5 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ + #include "pbcc_crypto.h" #if !defined INC_PUBNUB_INTERNAL_COMMON #define INC_PUBNUB_INTERNAL_COMMON @@ -360,6 +361,11 @@ struct pubnub_ { /** Last received HTTP (result) code */ uint16_t http_code; +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Last received HTTP (result) `Retry-After` header value. */ + uint16_t http_header_retry_after; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #if defined PUBNUB_ORIGIN_SETTABLE char const* origin; diff --git a/core/pubnub_netcore.c b/core/pubnub_netcore.c index 697ed425..16bf3162 100644 --- a/core/pubnub_netcore.c +++ b/core/pubnub_netcore.c @@ -409,6 +409,10 @@ static char const* pbnc_state2str(enum pubnub_state e) case PBS_RETRY: return "PBS_RETRY"; #endif +#if PUBNUB_USE_RETRY_CONFIGURATION + case PBS_WAIT_RETRY: + return "PBS_WAIT_RETRY"; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION case PBS_IDLE: return "PBS_IDLE"; case PBS_READY: @@ -1059,6 +1063,10 @@ int pbnc_fsm(struct pubnub_* pb) pb->http_code = atoi(pb->core.http_buf + 9); WATCH_USHORT(pb->http_code); pb->core.http_content_len = 0; +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Reset failed request `Retry-After` HTTP header value. */ + pb->http_header_retry_after = 0; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION pb->http_chunked = false; pb->state = PBS_RX_HEADERS; goto next_state; @@ -1089,6 +1097,15 @@ int pbnc_fsm(struct pubnub_* pb) switch (pbrslt) { case PNR_IN_PROGRESS: break; + case PNR_TX_BUFF_TOO_SMALL: + /** We could copy the "line so far" to the reply buffer + and then process the line from the reply buffer when + it's finished. This would be much more complex, yet + most lines will not be that long and, if the reply + buffer is static, it, too, might not be large enough. + */ + pb->state = PBS_RX_HEADERS; + goto next_state; case PNR_OK: { /* We know that Pubnub will always use keep-alive unless we ask for `close`, so, we don't check for the @@ -1097,6 +1114,9 @@ int pbnc_fsm(struct pubnub_* pb) char h_chunked[] = "Transfer-Encoding: chunked"; char h_length[] = "Content-Length: "; char h_close[] = "Connection: close"; +#if PUBNUB_USE_RETRY_CONFIGURATION + char h_retry_after[] = "Retry-After: "; +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION #if PUBNUB_RECEIVE_GZIP_RESPONSE char h_encoding[] = "Content-Encoding: gzip"; #endif @@ -1142,6 +1162,11 @@ int pbnc_fsm(struct pubnub_* pb) } pb->core.http_content_len = len; } +#if PUBNUB_USE_RETRY_CONFIGURATION + else if (pb_strncasecmp(pb->core.http_buf, h_retry_after, sizeof h_retry_after - 1) == 0) { + pb->http_header_retry_after = atoi(pb->core.http_buf + sizeof h_retry_after - 1); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION else if (pb_strncasecmp(pb->core.http_buf, h_close, sizeof h_close - 1) == 0) { pb->flags.should_close = true; } @@ -1158,15 +1183,6 @@ int pbnc_fsm(struct pubnub_* pb) pb->state = PBS_RX_HEADERS; goto next_state; } - case PNR_TX_BUFF_TOO_SMALL: - /** We could copy the "line so far" to the reply buffer - and then process the line from the reply buffer when - it's finished. This would be much more complex, yet - most lines will not be that long and, if the reply - buffer is static, it, too, might not be large enough. - */ - pb->state = PBS_RX_HEADERS; - goto next_state; default: outcome_detected(pb, pbrslt); break; diff --git a/core/pubnub_netcore.h b/core/pubnub_netcore.h index 0014f8f6..4bb8cb62 100644 --- a/core/pubnub_netcore.h +++ b/core/pubnub_netcore.h @@ -21,6 +21,10 @@ enum pubnub_state { 407, sending (some more) authentication data. */ PBS_RETRY, +#if PUBNUB_USE_RETRY_CONFIGURATION + /** Request retry timer is active and FSM await for it to timeout. */ + PBS_WAIT_RETRY, +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION /** Ready to start a transaction */ PBS_READY, /** Waiting for sending a DNS request */ diff --git a/core/pubnub_ntf_sync.c b/core/pubnub_ntf_sync.c index cfee8dcd..71ffe15a 100644 --- a/core/pubnub_ntf_sync.c +++ b/core/pubnub_ntf_sync.c @@ -1,6 +1,11 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_ntf_sync.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_internal.h" #include "pbpal.h" #include "pubnub_assert.h" @@ -54,6 +59,33 @@ void pbntf_trans_outcome(pubnub_t* pb, enum pubnub_state state) { PBNTF_TRANS_OUTCOME_COMMON(pb, state); PUBNUB_ASSERT(pbnc_can_start_transaction(pb)); +#if PUBNUB_USE_RETRY_CONFIGURATION + pubnub_mutex_lock(pb->monitor); + uint16_t delay = 0; + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + delay = pubnub_retry_configuration_delay_(pb); + } + pubnub_mutex_unlock(pb->monitor); + + if (delay > 0) { + pubnub_mutex_lock(pb->monitor); + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + + if (NULL != pb->core.retry_timer) { + pubnub_mutex_unlock(pb->monitor); + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + } else { pubnub_mutex_unlock(pb->monitor); } + } else { + pubnub_mutex_lock(pb->monitor); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_stop(pb->core.retry_timer); + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } + pubnub_mutex_unlock(pb->monitor); + } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } diff --git a/core/pubnub_pubsubapi.c b/core/pubnub_pubsubapi.c index e5e26a25..56245b90 100644 --- a/core/pubnub_pubsubapi.c +++ b/core/pubnub_pubsubapi.c @@ -303,6 +303,15 @@ int pubnub_last_http_code(pubnub_t* pb) return result; } +uint16_t pubnub_last_http_retry_header(pubnub_t* pb) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + pubnub_mutex_lock(pb->monitor); + uint16_t retry_after = pb->http_header_retry_after; + pubnub_mutex_unlock(pb->monitor); + return retry_after; +} + char const* pubnub_last_time_token(pubnub_t* pb) { diff --git a/core/pubnub_pubsubapi.h b/core/pubnub_pubsubapi.h index c4bea77c..82c0d1b8 100644 --- a/core/pubnub_pubsubapi.h +++ b/core/pubnub_pubsubapi.h @@ -296,6 +296,10 @@ PUBNUB_EXTERN enum pubnub_res pubnub_last_result(pubnub_t* p); * context. */ PUBNUB_EXTERN int pubnub_last_http_code(pubnub_t* p); +/** Returns the HTTP reply `Retry-After` header value of the last transaction in + * the @p p context. */ +PUBNUB_EXTERN uint16_t pubnub_last_http_retry_header(pubnub_t* pb); + /** Returns the string of the result of the last `publish` transaction, as returned from Pubnub. If the last transaction is not a publish, or there is some other error, it returns NULL. If the Publish diff --git a/core/pubnub_retry_configuration.c b/core/pubnub_retry_configuration.c new file mode 100644 index 00000000..1b06fb9d --- /dev/null +++ b/core/pubnub_retry_configuration.c @@ -0,0 +1,470 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pubnub_retry_configuration_internal.h" + +#include +#include +#include +#include +#include + +#include "core/pubnub_api_types.h" +#include "core/pubnub_pubsubapi.h" +#include "core/pubnub_assert.h" +#include "core/pubnub_mutex.h" +#include "core/pubnub_log.h" +#include "pubnub_internal.h" + + +// ---------------------------------------------- +// Constants +// ---------------------------------------------- + +/** Minimum and default delay between retry attempt. */ +#define DEFAULT_MINIMUM_DELAY 2 +/** Maximum delay between retry attempt. */ +#define DEFAULT_MAXIMUM_DELAY 150 +/** Default failed request retry attempts with linear retry policy. */ +#define DEFAULT_LINEAR_RETRY_ATTEMPTS 10 +/** Default failed request retry attempts with exponential retry policy. */ +#define DEFAULT_EXPONENTIAL_RETRY_ATTEMPTS 6 + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Retry policy type. */ +typedef enum { + /** Use equal delays between retry attempts. */ + PUBNUB_LINEAR_RETRY_POLICY, + /** + * @brief Use a `minimum_delay` delay that will exponentially increase with + * each failed request retry attempt. + */ + PUBNUB_EXPONENTIAL_RETRY_POLICY, +} pubnub_retry_policy_t; + +/** Failed requests retry configuration definition. */ +struct pubnub_retry_configuration { + /** Delay computation retry policy. */ + pubnub_retry_policy_t policy; + /** + * @brief Maximum allowed number of failed requests that should be retried + * automatically before reporting an error. + * + * \b Important: The maximum allowed number of retries is \b 10. + */ + int max_retries; + /** + * @brief Minimum delay between retry attempts. + * + * The delay is used for the `PUBNUB_LINEAR_RETRY_POLICY` policy, which is + * used between retry attempts. For the `PUBNUB_EXPONENTIAL_RETRY_POLICY` + * policy, which is used as the `minimum_delay`, which will be used to + * calculate the next delay based on the number of retry attempts. + * + * \b Important: The minimum allowed delay is \b 2. + */ + int minimum_delay; + /** + * @brief Maximum allowed computed delay that should be used between retry + * attempts. + */ + int maximum_delay; + /** Excluded endpoints map. */ + bool excluded_endpoints[PNRC_ENDPOINTS_COUNT]; +}; + + +// ---------------------------------------------- +// Function prototypes +// ---------------------------------------------- + +/** + * @brief Create a request retry configuration with a linear retry policy and + * list of API groups which won't be retried. + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param policy Retry policy type. + * @param minimum_delay Delay is used for the 'PUBNUB_LINEAR_RETRY_POLICY' + * policy, which is used between retry attempts. For the + * 'PUBNUB_EXPONENTIAL_RETRY_POLICY' policy, which is used + * as the `minimum_delay`, which will be used to calculate + * the next delay based on the number of retry attempts. + * @param maximum_delay Maximum allowed computed delay that should be used + * between retry attempts. + * @param maximum_retry The number of failed requests that should be retried + * automatically before reporting an error. + * @param excluded Endpoint for which retry shouldn't be used. + * @param endpoints Variadic list of endpoints for which retry shouldn't be + * used. \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of error insufficient memory error. The returned + * pointer must be passed to the `pubnub_retry_configuration_free` to + * avoid a memory leak. + */ +static pubnub_retry_configuration_t* +pubnub_retry_configuration_alloc_with_options_( + pubnub_retry_policy_t policy, + int minimum_delay, + int maximum_delay, + int maximum_retry, + pubnub_endpoint_t excluded, + va_list endpoints); + +/** + * @brief Handle user-provided excluded endpoints for retry configuration. + * + * @param configuration Pointer to the configuration for which list of + * exclusions should be set. + * @param excluded_endpoint First entry in the list of excluded endpoints. + * @param endpoints Variadic list of other excluded endpoints. + */ +static void pubnub_retry_configuration_set_excluded_( + pubnub_retry_configuration_t* configuration, + pubnub_endpoint_t excluded_endpoint, + va_list endpoints); + +/** + * @brief Check whether `transaction` can be retried after failure. + * + * @param configuration Pointer to the configuration with failed request + * handling details. + * @param transaction Failed transaction type. + * @param current_retry How many consequential retry attempts has been done. + * @param http_status Failed transaction status code. + * @return `true` if transaction can be handled once more after retry. + */ +static bool pubnub_retry_configuration_retryable_( + const pubnub_retry_configuration_t* configuration, + enum pubnub_trans transaction, + int current_retry, + int http_status); + +/** + * @brief Identify API endpoint group from transaction type. + * + * @param trans Transaction which should be translated. + * @return One of known PubNub API endpoint group type. + */ +static pubnub_endpoint_t pubnub_endpoint_from_transaction_( + enum pubnub_trans trans); + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +void pubnub_set_retry_configuration( + pubnub_t* pb, + pubnub_retry_configuration_t* configuration) +{ + PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); + pubnub_mutex_lock(pb->monitor); + if (NULL != pb->core.retry_configuration) + pubnub_retry_configuration_free(&pb->core.retry_configuration); + pb->core.retry_configuration = configuration; + pubnub_mutex_unlock(pb->monitor); +} + +pubnub_retry_configuration_t* pubnub_retry_configuration_linear_alloc(void) +{ + return pubnub_retry_configuration_linear_alloc_with_excluded(-1); +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_excluded( + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_LINEAR_RETRY_POLICY, + DEFAULT_MINIMUM_DELAY, + 0, + DEFAULT_LINEAR_RETRY_ATTEMPTS, + excluded, + args); + va_end(args); + + return configuration; +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_options( + const int delay, + const int maximum_retry, + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_LINEAR_RETRY_POLICY, + delay, + 0, + maximum_retry, + excluded, + args); + va_end(args); + + return configuration; +} + +pubnub_retry_configuration_t* pubnub_retry_configuration_exponential_alloc(void) +{ + return pubnub_retry_configuration_exponential_alloc_with_excluded(-1); +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_excluded( + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_EXPONENTIAL_RETRY_POLICY, + DEFAULT_MINIMUM_DELAY, + DEFAULT_MAXIMUM_DELAY, + DEFAULT_EXPONENTIAL_RETRY_ATTEMPTS, + excluded, + args); + va_end(args); + + return configuration; +} + +pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_options( + const int minimum_delay, + const int maximum_delay, + const int maximum_retry, + const int excluded, + ...) +{ + va_list args; + va_start(args, excluded); + pubnub_retry_configuration_t* configuration = + pubnub_retry_configuration_alloc_with_options_( + PUBNUB_EXPONENTIAL_RETRY_POLICY, + minimum_delay, + maximum_delay, + maximum_retry, + excluded, + args); + va_end(args); + + return configuration; +} + +void pubnub_retry_configuration_free( + pubnub_retry_configuration_t** configuration) +{ + if (NULL == configuration || NULL == *configuration) { return; } + + free(*configuration); + *configuration = NULL; +} + +pubnub_retry_configuration_t* pubnub_retry_configuration_alloc_with_options_( + const pubnub_retry_policy_t policy, + const int minimum_delay, + const int maximum_delay, + const int maximum_retry, + const pubnub_endpoint_t excluded, + const va_list endpoints) +{ + pubnub_retry_configuration_t* configuration = malloc( + sizeof(pubnub_retry_configuration_t)); + if (NULL == configuration) { + PUBNUB_LOG_ERROR("pubnub_retry_configuration_linear_alloc: failed to " + "allocate memory\n"); + return configuration; + } + + memset(configuration->excluded_endpoints, + false, + sizeof(configuration->excluded_endpoints)); + configuration->policy = policy; + configuration->minimum_delay = minimum_delay; + configuration->maximum_delay = maximum_delay; + configuration->max_retries = maximum_retry; + + pubnub_retry_configuration_set_excluded_(configuration, + excluded, + endpoints); + + return configuration; +} + +bool pubnub_retry_configuration_retryable_result_(pubnub_t* pb) +{ + switch (pb->core.last_result) { + case PNR_ADDR_RESOLUTION_FAILED: + case PNR_WAIT_CONNECT_TIMEOUT: + case PNR_CONNECT_FAILED: + case PNR_CONNECTION_TIMEOUT: + case PNR_TIMEOUT: + case PNR_ABORTED: + case PNR_HTTP_ERROR: + return true; + default: + return false; + } +} + +bool pubnub_retry_configuration_retryable_( + const pubnub_retry_configuration_t* configuration, + const enum pubnub_trans transaction, + const int current_retry, + const int http_status) +{ + const pubnub_endpoint_t endpoint = + pubnub_endpoint_from_transaction_(transaction); + + if (configuration->excluded_endpoints[endpoint] || + current_retry >= configuration->max_retries) + return false; + + return http_status == 429 || http_status >= 500 || http_status == 0; +} + +size_t pubnub_retry_configuration_delay_(pubnub_t* pb) +{ + const pubnub_retry_configuration_t* configuration = + pb->core.retry_configuration; + const int current_retry = pb->core.http_retry_count; + const int http_status = pubnub_last_http_code(pb); + const int retry_after = pubnub_last_http_retry_header(pb); + + size_t delay = -1; + if (!pubnub_retry_configuration_retryable_(configuration, + pb->trans, + current_retry, + http_status)) + return delay; + + if (retry_after > 0 && 429 == http_status) { delay = retry_after; } + else if (PUBNUB_LINEAR_RETRY_POLICY == configuration->policy) + delay = configuration->minimum_delay; + else { + delay = (int)fmin( + (double)(configuration->minimum_delay * pow(2, current_retry - 1)), + (double)configuration->maximum_delay); + } + + return delay * 1000 + random() % 1000; +} + +void pubnub_retry_configuration_set_excluded_( + pubnub_retry_configuration_t* configuration, + const pubnub_endpoint_t excluded_endpoint, + va_list endpoints) +{ + int endpoint = excluded_endpoint; + + while (endpoint != -1) { + if (endpoint >= 0 && endpoint < PNRC_ENDPOINTS_COUNT) + configuration->excluded_endpoints[endpoint] = true; + endpoint = va_arg(endpoints, int); + } +} + +pubnub_endpoint_t pubnub_endpoint_from_transaction_( + const enum pubnub_trans trans) +{ + pubnub_endpoint_t endpoint; + + switch (trans) { + case PBTT_SUBSCRIBE: +#if PUBNUB_USE_SUBSCRIBE_V2 + case PBTT_SUBSCRIBE_V2: +#endif // #if PUBNUB_USE_SUBSCRIBE_V2 + endpoint = PNRC_SUBSCRIBE_ENDPOINT; + break; + case PBTT_PUBLISH: + case PBTT_SIGNAL: + endpoint = PNRC_MESSAGE_SEND_ENDPOINT; + break; + case PBTT_WHERENOW: + case PBTT_GLOBAL_HERENOW: + case PBTT_HERENOW: + case PBTT_LEAVE: + case PBTT_HEARTBEAT: + case PBTT_SET_STATE: + case PBTT_STATE_GET: + endpoint = PNRC_PRESENCE_ENDPOINT; + break; + case PBTT_HISTORY: +#if PUBNUB_USE_FETCH_HISTORY + case PBTT_FETCH_HISTORY: +#endif // #if PUBNUB_USE_FETCH_HISTORY +#if PUBNUB_USE_ADVANCED_HISTORY + case PBTT_MESSAGE_COUNTS: +#endif // #if PUBNUB_USE_ADVANCED_HISTORY +#if PUBNUB_USE_ACTIONS_API + case PBTT_HISTORY_WITH_ACTIONS: +#endif // #if PUBNUB_USE_ACTIONS_API + endpoint = PNRC_MESSAGE_STORAGE_ENDPOINT; + break; + case PBTT_REMOVE_CHANNEL_GROUP: + case PBTT_REMOVE_CHANNEL_FROM_GROUP: + case PBTT_ADD_CHANNEL_TO_GROUP: + case PBTT_LIST_CHANNEL_GROUP: + endpoint = PNRC_CHANNEL_GROUPS_ENDPOINT; + break; +#if PUBNUB_USE_OBJECTS_API + case PBTT_GETALL_UUIDMETADATA: + case PBTT_SET_UUIDMETADATA: + case PBTT_GET_UUIDMETADATA: + case PBTT_DELETE_UUIDMETADATA: + case PBTT_GETALL_CHANNELMETADATA: + case PBTT_SET_CHANNELMETADATA: + case PBTT_GET_CHANNELMETADATA: + case PBTT_REMOVE_CHANNELMETADATA: + case PBTT_GET_MEMBERSHIPS: + case PBTT_SET_MEMBERSHIPS: + case PBTT_REMOVE_MEMBERSHIPS: + case PBTT_GET_MEMBERS: + case PBTT_ADD_MEMBERS: + case PBTT_SET_MEMBERS: + case PBTT_REMOVE_MEMBERS: + endpoint = PNRC_APP_CONTEXT_ENDPOINT; + break; +#endif // #if PUBNUB_USE_OBJECTS_API +#if PUBNUB_USE_ACTIONS_API + case PBTT_ADD_ACTION: + case PBTT_REMOVE_ACTION: + case PBTT_GET_ACTIONS: + endpoint = PNRC_MESSAGE_REACTIONS_ENDPOINT; + break; +#endif // #if PUBNUB_USE_ACTIONS_API +#if PUBNUB_USE_GRANT_TOKEN_API || PUBNUB_USE_REVOKE_TOKEN_API +#if PUBNUB_USE_GRANT_TOKEN_API + case PBTT_GRANT_TOKEN: +#endif // #if PUBNUB_USE_GRANT_TOKEN_API +#if PUBNUB_USE_REVOKE_TOKEN_API + case PBTT_REVOKE_TOKEN: +#endif // #if PUBNUB_USE_REVOKE_TOKEN_API + endpoint = PNRC_PAM_ENDPOINT; + break; +#endif // #if PUBNUB_USE_GRANT_TOKEN_API || PUBNUB_USE_REVOKE_TOKEN_API + default: + endpoint = PNRC_UNKNOWN_ENDPOINT; + break; + } + + return endpoint; +} \ No newline at end of file diff --git a/core/pubnub_retry_configuration.h b/core/pubnub_retry_configuration.h new file mode 100644 index 00000000..2fd59a23 --- /dev/null +++ b/core/pubnub_retry_configuration.h @@ -0,0 +1,335 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_RETRY_CONFIGURATION_H +#define PUBNUB_RETRY_CONFIGURATION_H +#if PUBNUB_USE_RETRY_CONFIGURATION + + +/** + * @file pubnub_retry_configuration.h + * @brief PubNub request retry configuration. + */ + +#include "core/pubnub_api_types.h" +#include "lib/pb_extern.h" + + +// ---------------------------------------------- +// Types forwarding +// ---------------------------------------------- + +/** + * @brief Failed requests retry configuration definition. + * + * PubNub client can automatically retry failed requests basing on retry + * configuration criteria. + */ +typedef struct pubnub_retry_configuration pubnub_retry_configuration_t; + + +// ---------------------------------------------- +// Types +// ---------------------------------------------- + +/** Excluded endpoint groups definition. */ +typedef enum +{ + /** Unknown API endpoint group. */ + PNRC_UNKNOWN_ENDPOINT, + /** The endpoints to send messages. */ + PNRC_MESSAGE_SEND_ENDPOINT, + /** The endpoint for real-time update retrieval. */ + PNRC_SUBSCRIBE_ENDPOINT, + /** + * @brief The endpoint to access and manage `user_id` presence and fetch + * channel presence information. + */ + PNRC_PRESENCE_ENDPOINT, + /** + * @brief The endpoint to access and manage messages for a specific + * channel(s) in the persistent storage. + */ + PNRC_MESSAGE_STORAGE_ENDPOINT, + /** The endpoint to access and manage channel groups. */ + PNRC_CHANNEL_GROUPS_ENDPOINT, +#if PUBNUB_USE_OBJECTS_API + /** The endpoint to access and manage App Context objects. */ + PNRC_APP_CONTEXT_ENDPOINT, +#endif // #if PUBNUB_USE_OBJECTS_API +#if PUBNUB_USE_ACTIONS_API + /** The endpoint to access and manage reactions for a specific message. */ + PNRC_MESSAGE_REACTIONS_ENDPOINT, +#endif // #if PUBNUB_USE_ACTIONS_API +#if PUBNUB_USE_GRANT_TOKEN_API + /** The endpoint to access and manage permissions (auth keys / tokens). */ + PNRC_PAM_ENDPOINT, +#endif // #if PUBNUB_USE_GRANT_TOKEN_API + /** Number of known endpoint groups. */ + PNRC_ENDPOINTS_COUNT +} pubnub_endpoint_t; + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Set failed request retry configuration. + * @code + * // Create default configuration with linear retry policy. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc(void); + * pubnub_set_retry_configuration(pb, configuration); + * @endcode + * + * @note `PubNub` context will free configuration instance on `deinit`. + * + * @param pb Pointer to the PubNub context which failed to complete + * transaction and will retry them depending from + * configuration. + * @param configuration Pointer to the request retry configuration, which will + * be used by PubNub to compute delays between retry + * attempts. + * + * @see pubnub_retry_configuration_linear_alloc + */ +PUBNUB_EXTERN void pubnub_set_retry_configuration( + pubnub_t* pb, + pubnub_retry_configuration_t* configuration); + +/** + * @brief Create a request retry configuration with a linear retry policy. + * @code + * // Create default configuration with linear retry policy. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc(void); + * @endcode + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* + pubnub_retry_configuration_linear_alloc(void); + +/** + * @brief Create a request retry configuration with a linear retry policy and + * list of API groups which won't be retried. + * @code + * // Create default configuration with linear retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc_with_excluded( + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be used. + *
\b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_excluded(int excluded, ...); + +/** + * @brief Create a request retry configuration with a linear retry policy and + * list of API groups which won't be retried. + * @code + * // Create custom configuration with linear retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc_with_options( + * 5, + * 3, + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with a linear retry policy will use equal delays between retry + * attempts. The default implementation uses a \b 2 seconds delay and \b 10 + * maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param delay Delay between failed requests automatically retries + * attempts. \b Important: The minimum allowed delay + * is \b 2.0. + * @param maximum_retry The number of failed requests that should be retried + * automatically before reporting an error. + * \b Important: The maximum allowed number of retries + * is \b 10. + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be + * used. \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_linear_alloc_with_options( + int delay, + int maximum_retry, + int excluded, + ...); + +/** + * @brief Create a request retry configuration with a exponential retry policy. + * @code + * // Create default configuration with exponential retry policy. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_exponential_alloc(void); + * @endcode + * + * Configurations with an exponential retry policy will use `minimum_delay` that + * will exponentially increase with each failed request retry attempt.
+ * The default implementation uses a \b 2 seconds minimum, \b 150 maximum + * delays, and \b 6 maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* + pubnub_retry_configuration_exponential_alloc(void); + +/** + * @brief Create a request retry configuration with a exponential retry policy + * and list of API groups which won't be retried. + * @code + * // Create default configuration with exponential retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_exponential_alloc_with_excluded( + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with an exponential retry policy will use `minimum_delay` that + * will exponentially increase with each failed request retry attempt.
+ * The default implementation uses a \b 2 seconds minimum, \b 150 maximum + * delays, and \b 6 maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be used. + * \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_excluded(int excluded, ...); + +/** + * @brief Create a request retry configuration with a exponential retry policy + * and list of API groups which won't be retried. + * @code + * // Create custom configuration with linear retry policy and excluded + * // endpoints: publish and history. + * pubnub_retry_configuration_t* configuration = + * pubnub_retry_configuration_linear_alloc_with_options( + * 5, + * 20, + * 6, + * PNRC_MESSAGE_SEND_ENDPOINT, + * PNRC_MESSAGE_STORAGE_ENDPOINT, + * -1); + * @endcode + * + * Configurations with an exponential retry policy will use `minimum_delay` that + * will exponentially increase with each failed request retry attempt.
+ * The default implementation uses a \b 2 seconds minimum, \b 150 maximum + * delays, and \b 6 maximum retry attempts. + * + * @note The PubNub client will schedule request retries after a calculated + * delay, but the actual retry may not happen at the same moment (there is + * a small leeway). + * + * @param minimum_delay Base delay, which will be used to calculate the next + * delay depending on the number of retry attempts. + * \b Important: The minimum allowed delay is \b 2. + * @param maximum_delay Maximum allowed computed delay that should be used + * between retry attempts. + * @param maximum_retry The number of failed requests that should be retried + * automatically before reporting an error. + * \b Important: The maximum allowed number of retries + * is \b 10. + * @param excluded Endpoint for which retry shouldn't be used. + * @param ... Variadic list of endpoints for which retry shouldn't be + * used. \b Important: list should be terminated by \b -1. + * @return Pointer to the ready to use PubNub request retry configuration or + * `NULL` in case of insufficient memory error. The returned pointer + * must be passed to the `pubnub_retry_configuration_free` to avoid a + * memory leak. + * + * @see pubnub_set_retry_configuration + * @see pubnub_retry_configuration_free + */ +PUBNUB_EXTERN pubnub_retry_configuration_t* +pubnub_retry_configuration_exponential_alloc_with_options( + int minimum_delay, + int maximum_delay, + int maximum_retry, + int excluded, + ...); + +/** + * @brief Release resources used by request retry configuration object. + * + * @param configuration Pointer to the configuration object, which should free + * up resources. + */ +PUBNUB_EXTERN void pubnub_retry_configuration_free( + pubnub_retry_configuration_t** configuration); +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION +#endif // #ifndef PUBNUB_RETRY_CONFIGURATION_H diff --git a/core/pubnub_retry_configuration_internal.h b/core/pubnub_retry_configuration_internal.h new file mode 100644 index 00000000..23d904f6 --- /dev/null +++ b/core/pubnub_retry_configuration_internal.h @@ -0,0 +1,41 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#ifndef PUBNUB_RETRY_CONFIGURATION_INTERNAL_H +#define PUBNUB_RETRY_CONFIGURATION_INTERNAL_H + + +/** + * @file pubnub_retry_configuration_internal.h + * @brief PubNub request retry configuration. + */ + +#include "pubnub_retry_configuration.h" + +#include +#include + + + +// ---------------------------------------------- +// Functions +// ---------------------------------------------- + +/** + * @brief Check whether transaction and it outcome can be retried at all. + * + * Not all transactions can be handled and not all results makes sense to retry. + * + * @param pb Pointer to the PubNub context for which results of last transaction + * should be checked. + * @return `true` in case if it really failed and it wasn't because of user. + */ +bool pubnub_retry_configuration_retryable_result_(pubnub_t* pb); + +/** + * @brief Check whether `transaction` can be retried after failure. + * + * @param pb Pointer to the PubNub context for which delay should be computed. + * @return Next retry attempt delay value or \b -1 in case if retry is + * impossible. + */ +size_t pubnub_retry_configuration_delay_(pubnub_t* pb); +#endif //PUBNUB_RETRY_CONFIGURATION_INTERNAL_H diff --git a/core/pubnub_subscribe_event_engine.c b/core/pubnub_subscribe_event_engine.c index f228d2c8..5b79b46f 100644 --- a/core/pubnub_subscribe_event_engine.c +++ b/core/pubnub_subscribe_event_engine.c @@ -1,13 +1,14 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_subscribe_event_engine_internal.h" #include -#include "pbcc_memory_utils.h" #include "core/pubnub_entities_internal.h" -#include "core/pubnub_internal_common.h" +#include "core/pbcc_memory_utils.h" #include "core/pubnub_assert.h" #include "core/pubnub_mutex.h" #include "core/pubnub_log.h" +#include "pubnub_internal.h" #include "lib/pbhash_set.h" @@ -23,6 +24,37 @@ // Function prototypes // ---------------------------------------------- +/** + * @brief Add subscription to subscription set. + * + * @param set Pointer to the subscription set, which should be + * modified. + * @param sub Pointer to the subscription, which should be added to + * the set. + * @param notify_change Whether Subscribe Event Engine should be notified about + * change or not. + * + * @return Result of subscription add. + */ +static enum pubnub_res pubnub_subscription_set_add_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t* sub, + bool notify_change); + +/** + * @brief Remove subscription from subscription set. + * + * @param set Pointer to the subscription set, which should be modified. + * @param sub Pointer to the subscription, which should be removed from the set. + * @param notify_change Whether Subscribe Event Engine should be notified about + * change or not. + * @return Result of subscription removal from the subscription set operation. + */ +static enum pubnub_res pubnub_subscription_set_remove_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t** sub, + bool notify_change); + /** * @brief Create entity subscription. * @@ -38,7 +70,6 @@ * subscription. * @param options Pointer to the subscription configuration options. Set to * `NULL` if options not required. - * @param standalone Whether a subscription object is standalone or part of set. * @return Pointer to the ready to use PubNub subscription, or `NULL` will be * returned in case of error (in most of the cases caused by * insufficient memory for structure allocation). The returned pointer @@ -47,10 +78,9 @@ * * @see pubnub_subscriptions */ -static pubnub_subscription_t* _pubnub_subscription_alloc( +static pubnub_subscription_t* pubnub_subscription_alloc_( pubnub_entity_t* entity, - const pubnub_subscription_options_t* options, - bool standalone); + const pubnub_subscription_options_t* options); /** * @brief Update number references to the `subscription`. @@ -62,7 +92,7 @@ static pubnub_subscription_t* _pubnub_subscription_alloc( * updated. * @param increase Whether reference count should be increased or decreased. */ -static void _subscription_reference_count_update( +static void subscription_reference_count_update_( pubnub_subscription_t* subscription, bool increase); @@ -78,7 +108,7 @@ static void _subscription_reference_count_update( * structure allocation). The returned pointer must be passed to the * `pubnub_subscription_set_free` to avoid a memory leak. */ -static pubnub_subscription_set_t* _subscription_set_alloc( +static pubnub_subscription_set_t* subscription_set_alloc_( const pubnub_subscription_options_t* options, int length); @@ -91,10 +121,19 @@ static pubnub_subscription_set_t* _subscription_set_alloc( * updated. * @param increase Whether reference count should be increased or decreased. */ -static void _subscription_set_reference_count_update( +static void subscription_set_reference_count_update_( pubnub_subscription_set_t* set, bool increase); +/** + * @brief Release resources used by subscription from subscription set. + * + * @param sub Pointer to the subscription, which should free up resources. + * @return `false` if there are more references to the subscription. + */ +static bool pubnub_subscription_set_subscription_free_( + pubnub_subscription_t* sub); + /** * @brief Create subscribable object. * @@ -105,10 +144,10 @@ static void _subscription_set_reference_count_update( * must be passed to the `_pubnub_subscribable_free` to avoid a memory * leak. */ -static pubnub_subscribable_t* _pubnub_subscribable_alloc( - enum pubnub_subscribable_location location, - struct pubnub_char_mem_block* id, - bool presence); +static pubnub_subscribable_t* pubnub_subscribable_alloc_( + pubnub_subscribable_location location, + struct pubnub_char_mem_block* id, + bool presence); // ---------------------------------------------- @@ -124,23 +163,34 @@ pubnub_subscription_t* pubnub_subscription_alloc( pubnub_entity_t* entity, const pubnub_subscription_options_t* options) { - return _pubnub_subscription_alloc(entity, options, true); + return pubnub_subscription_alloc_(entity, options); +} + +bool pubnub_subscription_free(pubnub_subscription_t** sub) +{ + if (NULL == sub || NULL == *sub) { return false; } + + pubnub_mutex_lock((*sub)->mutw); + if (pubnub_subscription_free_(*sub)) { + pubnub_mutex_unlock((*sub)->mutw); + pubnub_mutex_destroy((*sub)->mutw); + *sub = NULL; + return true; + } + pubnub_mutex_unlock((*sub)->mutw); + + return false; } -bool pubnub_subscription_free(pubnub_subscription_t* sub) +bool pubnub_subscription_free_(pubnub_subscription_t* sub) { if (NULL == sub) { return false; } - _subscription_reference_count_update(sub, false); - pubnub_mutex_lock(sub->mutw); - if (sub->reference_count == 0) { - pubnub_entity_free(sub->entity); - pubnub_mutex_unlock(sub->mutw); - pubnub_mutex_destroy(sub->mutw); + if (0 == pbref_counter_free(sub->counter)) { + pubnub_entity_free((void**)&sub->entity); free(sub); return true; } - pubnub_mutex_unlock(sub->mutw); return false; } @@ -157,8 +207,9 @@ pubnub_subscription_t** pubnub_subscriptions(const pubnub_t* pb, size_t* count) if (NULL != count) { *count = cnt; } if (NULL == subscriptions) { PUBNUB_LOG_ERROR("pubnub_subscriptions: failed to allocate memory for " - "subscriptions list\n"); - } else if (0 == cnt) { + "subscriptions list\n"); + } + else if (0 == cnt) { PUBNUB_LOG_INFO("pubnub_subscriptions: subscriptions list is empty"); } @@ -176,21 +227,19 @@ pubnub_subscription_set_alloc_with_entities( const int length = entities_count - entities_count % SUBSCRIPTIONS_LENGTH + SUBSCRIPTIONS_LENGTH; const pubnub_t* pb = NULL; - pubnub_subscription_set_t* set = _subscription_set_alloc(options, length); + pubnub_subscription_set_t* set = subscription_set_alloc_(options, length); if (NULL == set) { return NULL; } for (int i = 0; i < entities_count; ++i) { pubnub_entity_t* entity = entities[i]; - pubnub_subscription_t* sub = _pubnub_subscription_alloc( - entity, - NULL, - false); + pubnub_subscription_t* sub = + pubnub_subscription_alloc_(entity, NULL); if (NULL == pb) { pb = entity->pb; } if (NULL == sub || PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, sub)) { - if (NULL != sub) { pubnub_subscription_free(sub); } - pubnub_subscription_set_free(set); + if (NULL != sub) { pubnub_subscription_free(&sub); } + pubnub_subscription_set_free(&set); return NULL; } } @@ -200,7 +249,7 @@ pubnub_subscription_set_alloc_with_entities( PUBNUB_LOG_ERROR( "pubnub_subscription_set_alloc_with_entities: invalid PubNub " "context\n"); - pubnub_subscription_set_free(set); + pubnub_subscription_set_free(&set); return NULL; } @@ -216,14 +265,14 @@ pubnub_subscription_set_alloc_with_subscriptions( PUBNUB_ASSERT_OPT(NULL != sub1); PUBNUB_ASSERT_OPT(NULL != sub2); - pubnub_subscription_set_t* set = _subscription_set_alloc( + pubnub_subscription_set_t* set = subscription_set_alloc_( options, SUBSCRIPTIONS_LENGTH); if (NULL == set) { return NULL; } if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, sub1) || PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, sub2)) { - pubnub_subscription_set_free(set); + pubnub_subscription_set_free(&set); return NULL; } @@ -240,87 +289,31 @@ enum pubnub_res pubnub_subscription_set_add( PUBNUB_ASSERT_OPT(NULL != sub); pubnub_mutex_lock(set->mutw); - const bool subscribed = set->subscribed; - - if (pbhash_set_contains(set->subscriptions, sub->entity->id.ptr)) { - PUBNUB_LOG_INFO( - "pubnub_subscription_set_add: subscription (sub=%p) for entity " - "('%s') already added into set", - sub, - sub->entity->id.ptr); - pubnub_mutex_unlock(set->mutw); - return PNR_SUB_ALREADY_ADDED; - - } - - if (PBHSR_OUT_OF_MEMORY == pbhash_set_add(set->subscriptions, - sub->entity->id.ptr, - sub)) { - pubnub_mutex_unlock(set->mutw); - PUBNUB_LOG_ERROR( - "pubnub_subscription_set_add: failed to allocate memory " - "for subscription\n"); - return PNR_OUT_OF_MEMORY; - } - - _subscription_reference_count_update(sub, true); + const enum pubnub_res rslt = pubnub_subscription_set_add_(set, sub, true); pubnub_mutex_unlock(set->mutw); - if (subscribed) { - // Notify changes in active subscription set. - pbcc_subscribe_ee_change_subscription_with_subscription_set( - set->ee, - set, - sub, - true); - } - - return PNR_OK; + return rslt; } enum pubnub_res pubnub_subscription_set_remove( pubnub_subscription_set_t* set, - pubnub_subscription_t* sub) + pubnub_subscription_t** sub) { PUBNUB_ASSERT_OPT(NULL != set); PUBNUB_ASSERT_OPT(NULL != sub); + PUBNUB_ASSERT_OPT(NULL != *sub); pubnub_mutex_lock(set->mutw); - const bool subscribed = set->subscribed; - - if (!pbhash_set_contains(set->subscriptions, sub->entity->id.ptr)) { - PUBNUB_LOG_INFO( - "pubnub_subscription_set_remove: subscription (sub=%p) for entity " - "('%s') not found", - sub, - sub->entity->id.ptr); - pubnub_mutex_unlock(set->mutw); - return PNR_SUB_NOT_FOUND; - } - - if (subscribed) { - // Temporarily release lock to avoid deadlocks from subscription ee core - // code access the list of subscribables. - pubnub_mutex_unlock(set->mutw); - - // Notify changes in active subscription set. - pbcc_subscribe_ee_change_subscription_with_subscription_set( - set->ee, - set, - sub, - false); - pubnub_mutex_lock(set->mutw); - pbhash_set_remove(set->subscriptions, sub->entity->id.ptr); - } - else { pbhash_set_remove(set->subscriptions, sub->entity->id.ptr); } + const enum pubnub_res rslt = + pubnub_subscription_set_remove_(set, sub, true); pubnub_mutex_unlock(set->mutw); - return PNR_OK; + return rslt; } enum pubnub_res pubnub_subscription_set_union( - pubnub_subscription_set_t* set, - const pubnub_subscription_set_t* other_set) + pubnub_subscription_set_t* set, + pubnub_subscription_set_t* other_set) { PUBNUB_ASSERT_OPT(NULL != set); PUBNUB_ASSERT_OPT(NULL != other_set); @@ -331,7 +324,10 @@ enum pubnub_res pubnub_subscription_set_union( pbhash_set_elements(set->subscriptions, &count); for (int i = 0; i < count; ++i) { - if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add(set, subs[i])) { + if (PNR_OUT_OF_MEMORY == pubnub_subscription_set_add_( + set, + subs[i], + i + 1 == count)) { pubnub_mutex_unlock(other_set->mutw); if (NULL != subs) { free(subs); } return PNR_OUT_OF_MEMORY; @@ -344,8 +340,8 @@ enum pubnub_res pubnub_subscription_set_union( } void pubnub_subscription_set_subtract( - pubnub_subscription_set_t* set, - const pubnub_subscription_set_t* other_set) + const pubnub_subscription_set_t* set, + pubnub_subscription_set_t* other_set) { PUBNUB_ASSERT_OPT(NULL != set); PUBNUB_ASSERT_OPT(NULL != other_set); @@ -356,7 +352,7 @@ void pubnub_subscription_set_subtract( pbhash_set_elements(set->subscriptions, &count); for (int i = 0; i < count; ++i) { - pubnub_subscription_set_remove(set, subs[i]); + pubnub_subscription_set_remove_(set, &subs[i], i + 1 == count); } pubnub_mutex_unlock(other_set->mutw); if (NULL != subs) { free(subs); } @@ -385,21 +381,22 @@ pubnub_subscription_set_t** pubnub_subscription_sets( } pubnub_subscription_t** pubnub_subscription_set_subscriptions( - const pubnub_subscription_set_t* set, - size_t* count) + pubnub_subscription_set_t* set, + size_t* count) { PUBNUB_ASSERT_OPT(NULL != set); pubnub_mutex_lock(set->mutw); - size_t cnt; - const pubnub_subscription_t** subs = (const pubnub_subscription_t**) + size_t cnt; + pubnub_subscription_t** subs = (pubnub_subscription_t**) pbhash_set_elements(set->subscriptions, &cnt); if (NULL != count) { *count = cnt; } if (NULL == subs) { PUBNUB_LOG_ERROR("pubnub_subscription_set_subscriptions: failed to " "allocate memory for subscriptions list\n"); - } else if (0 == cnt) { + } + else if (0 == cnt) { PUBNUB_LOG_INFO( "pubnub_subscription_set_subscriptions: subscriptions list is " "empty"); @@ -409,20 +406,33 @@ pubnub_subscription_t** pubnub_subscription_set_subscriptions( return subs; } -bool pubnub_subscription_set_free(pubnub_subscription_set_t* set) +bool pubnub_subscription_set_free(pubnub_subscription_set_t** set) +{ + if (NULL == set || NULL == *set) { return false; } + + pubnub_mutex_lock((*set)->mutw); + if (pubnub_subscription_set_free_(*set)) { + pubnub_mutex_unlock((*set)->mutw); + pubnub_mutex_destroy((*set)->mutw); + *set = NULL; + return true; + } + pubnub_mutex_unlock((*set)->mutw); + + return false; +} + +bool pubnub_subscription_set_free_(pubnub_subscription_set_t* set) { if (NULL == set) { return false; } - _subscription_set_reference_count_update(set, false); - pubnub_mutex_lock(set->mutw); - if (set->reference_count == 0) { - if (NULL != set->subscriptions) { pbhash_set_free(set->subscriptions); } - pubnub_mutex_unlock(set->mutw); - pubnub_mutex_destroy(set->mutw); + if (0 == pbref_counter_free(set->counter)) { + if (NULL != set->subscriptions) + pbhash_set_free(&set->subscriptions); free(set); return true; } - pubnub_mutex_unlock(set->mutw); + return false; } @@ -445,6 +455,7 @@ void pubnub_subscribe_set_heartbeat( pubnub_subscribe_cursor_t pubnub_subscribe_cursor(const char* timetoken) { pubnub_subscribe_cursor_t cursor; + if (NULL != timetoken) { memcpy(cursor.timetoken, timetoken, sizeof(cursor.timetoken) - 1); cursor.timetoken[sizeof(cursor.timetoken) - 1] = '\0'; @@ -469,20 +480,30 @@ enum pubnub_res pubnub_subscribe_with_subscription( sub, cursor); - if (PNR_OK == rslt) { _subscription_reference_count_update(sub, true); } + if (PNR_OUT_OF_MEMORY != rslt) + subscription_reference_count_update_(sub, true); return rslt; } enum pubnub_res pubnub_unsubscribe_with_subscription( - pubnub_subscription_t* sub) + pubnub_subscription_t** sub) { - if (NULL == sub) { return PNR_INVALID_PARAMETERS; } + if (NULL == sub || NULL == *sub) { return PNR_INVALID_PARAMETERS; } const enum pubnub_res rslt = - pbcc_subscribe_ee_unsubscribe_with_subscription(sub->ee, sub); + pbcc_subscribe_ee_unsubscribe_with_subscription((*sub)->ee, *sub); - if (PNR_OK == rslt) { pubnub_subscription_free(sub); } + if (PNR_OK == rslt || PNR_SUB_NOT_FOUND == rslt) { + /** + * Decrease subscription reference counter only if it has been used for + * previous subscriptions. + */ + if (PNR_OK == rslt) + subscription_reference_count_update_(*sub, false); + + pubnub_subscription_free(sub); + } return rslt; } @@ -499,8 +520,8 @@ enum pubnub_res pubnub_subscribe_with_subscription_set( set, cursor); - if (PNR_OK == rslt) { - _subscription_set_reference_count_update(set, true); + if (PNR_OUT_OF_MEMORY != rslt) { + subscription_set_reference_count_update_(set, true); pubnub_mutex_lock(set->mutw); set->subscribed = true; pubnub_mutex_unlock(set->mutw); @@ -510,17 +531,24 @@ enum pubnub_res pubnub_subscribe_with_subscription_set( } enum pubnub_res pubnub_unsubscribe_with_subscription_set( - pubnub_subscription_set_t* set) + pubnub_subscription_set_t** set) { - if (NULL == set) { return PNR_INVALID_PARAMETERS; } + if (NULL == set || NULL == *set) { return PNR_INVALID_PARAMETERS; } const enum pubnub_res rslt = - pbcc_subscribe_ee_unsubscribe_with_subscription_set(set->ee, set); - - if (PNR_OK == rslt) { - pubnub_mutex_lock(set->mutw); - set->subscribed = false; - pubnub_mutex_unlock(set->mutw); + pbcc_subscribe_ee_unsubscribe_with_subscription_set((*set)->ee, *set); + + if (PNR_OK == rslt || PNR_SUB_NOT_FOUND == rslt) { + /** + * Decrease subscription set reference counter only if it has been used + * for previous subscriptions. + */ + if (PNR_OK == rslt) + subscription_set_reference_count_update_(*set, false); + + pubnub_mutex_lock((*set)->mutw); + (*set)->subscribed = false; + pubnub_mutex_unlock((*set)->mutw); pubnub_subscription_set_free(set); } @@ -535,12 +563,14 @@ enum pubnub_res pubnub_disconnect(const pubnub_t* pb) } enum pubnub_res pubnub_reconnect( - const pubnub_t* pb, - const pubnub_subscribe_cursor_t cursor) + const pubnub_t* pb, + const pubnub_subscribe_cursor_t* cursor) { PUBNUB_ASSERT(pb_valid_ctx_ptr(pb)); - return pbcc_subscribe_ee_reconnect(pb->core.subscribe_ee, cursor); + const pubnub_subscribe_cursor_t subscribe_cursor = + NULL != cursor ? *cursor : pubnub_subscribe_cursor(NULL); + return pbcc_subscribe_ee_reconnect(pb->core.subscribe_ee, subscribe_cursor); } enum pubnub_res pubnub_unsubscribe_all(const pubnub_t* pb) @@ -550,10 +580,82 @@ enum pubnub_res pubnub_unsubscribe_all(const pubnub_t* pb) return pbcc_subscribe_ee_unsubscribe_all(pb->core.subscribe_ee); } -pubnub_subscription_t* _pubnub_subscription_alloc( +enum pubnub_res pubnub_subscription_set_add_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t* sub, + const bool notify_change) +{ + const bool subscribed = set->subscribed; + enum pubnub_res rslt = PNR_OK; + + if (pbhash_set_contains(set->subscriptions, sub->entity->id.ptr)) { + PUBNUB_LOG_INFO( + "pubnub_subscription_set_add: subscription (sub=%p) for entity " + "('%s') already added into set", + sub, + sub->entity->id.ptr); + return PNR_SUB_ALREADY_ADDED; + } + + if (PBHSR_OUT_OF_MEMORY == pbhash_set_add(set->subscriptions, + sub->entity->id.ptr, + sub)) { + PUBNUB_LOG_ERROR( + "pubnub_subscription_set_add: failed to allocate memory " + "for subscription\n"); + return PNR_OUT_OF_MEMORY; + } + + subscription_reference_count_update_(sub, true); + + if (subscribed && notify_change) { + /** Notify changes in active subscription set. */ + rslt = pbcc_subscribe_ee_change_subscription_with_subscription_set( + set->ee, + NULL, + NULL, + true); + } + + return rslt; +} + +enum pubnub_res pubnub_subscription_set_remove_( + const pubnub_subscription_set_t* set, + pubnub_subscription_t** sub, + bool notify_change) +{ + const bool subscribed = set->subscribed; + + if (!pbhash_set_contains(set->subscriptions, (*sub)->entity->id.ptr)) { + PUBNUB_LOG_INFO( + "pubnub_subscription_set_remove: subscription (sub=%p) for entity " + "('%s') not found", + *sub, + (*sub)->entity->id.ptr); + return PNR_SUB_NOT_FOUND; + } + + enum pubnub_res rslt = PNR_OK; + if (subscribed && notify_change) { + /** Notify changes in active subscription set. */ + rslt = pbcc_subscribe_ee_change_subscription_with_subscription_set( + set->ee, + set, + *sub, + false); + } + + pbhash_set_remove(set->subscriptions, + (void**)&(*sub)->entity->id.ptr, + (void**)sub); + + return rslt; +} + +pubnub_subscription_t* pubnub_subscription_alloc_( pubnub_entity_t* entity, - const pubnub_subscription_options_t* options, - const bool standalone) + const pubnub_subscription_options_t* options) { PUBNUB_ASSERT_OPT(NULL != entity); PUBNUB_ASSERT(pb_valid_ctx_ptr(entity->pb)); @@ -565,29 +667,28 @@ pubnub_subscription_t* _pubnub_subscription_alloc( } pubnub_mutex_init(subscription->mutw); - subscription->options = opts; - subscription->reference_count = standalone == true ? 1 : 0; - subscription->entity = entity; - subscription->ee = entity->pb->core.subscribe_ee; + subscription->options = opts; + subscription->counter = pbref_counter_alloc(); + subscription->entity = entity; + subscription->ee = entity->pb->core.subscribe_ee; + entity_reference_count_update_(entity, true); return subscription; } -void _subscription_reference_count_update( +void subscription_reference_count_update_( pubnub_subscription_t* subscription, const bool increase) { PUBNUB_ASSERT_OPT(NULL != subscription); pubnub_mutex_lock(subscription->mutw); - if (increase) { subscription->reference_count++; } - else if (subscription->reference_count > 0) { - subscription->reference_count--; - } + if (increase) { pbref_counter_increment(subscription->counter); } + else { pbref_counter_decrement(subscription->counter); } pubnub_mutex_unlock(subscription->mutw); } -pbhash_set_t* _pubnub_subscription_subscribables( +pbhash_set_t* pubnub_subscription_subscribables_( const pubnub_subscription_t* sub, const pubnub_subscription_options_t* options) { @@ -596,7 +697,7 @@ pbhash_set_t* _pubnub_subscription_subscribables( opts.receive_presence_events = sub->options.receive_presence_events; } - const enum pubnub_subscribable_location location = + const pubnub_subscribable_location location = sub->entity->type == PUBNUB_ENTITY_CHANNEL_GROUP ? SUBSCRIBABLE_LOCATION_QUERY : SUBSCRIBABLE_LOCATION_PATH; @@ -606,19 +707,19 @@ pbhash_set_t* _pubnub_subscription_subscribables( NULL); if (NULL == hash) { PUBNUB_LOG_ERROR("pubnub_subscription_subscribables: failed to allocate" - " memory for subscription's subscribable list\n"); - return NULL; + " memory for subscription's subscribable list\n"); + return hash; } - pubnub_subscribable_t* regular = _pubnub_subscribable_alloc( + pubnub_subscribable_t* regular = pubnub_subscribable_alloc_( location, &sub->entity->id, false); if (NULL == regular) { PUBNUB_LOG_ERROR("pubnub_subscription_subscribables: failed to allocate" - " memory for regular subscribable identifier\n"); - pbhash_set_free(hash); - return NULL; + " memory for regular subscribable identifier\n"); + pbhash_set_free(&hash); + return hash; } if (PBHSR_OUT_OF_MEMORY == @@ -626,22 +727,24 @@ pbhash_set_t* _pubnub_subscription_subscribables( PUBNUB_LOG_ERROR( "pubnub_subscription_subscribables: failed to allocate memory " "for node to store regular subscribable identifier in hash set\n"); - _pubnub_subscribable_free(regular); - pbhash_set_free(hash); - return NULL; + pubnub_subscribable_free_(regular); + pbhash_set_free(&hash); + return hash; } - if (!options->receive_presence_events) { return hash; } + if (!opts.receive_presence_events) { return hash; } - pubnub_subscribable_t* presence = _pubnub_subscribable_alloc( + pubnub_subscribable_t* presence = pubnub_subscribable_alloc_( location, &sub->entity->id, true); if (NULL == presence) { PUBNUB_LOG_ERROR("pubnub_subscription_subscribables: failed to allocate" - " memory for presence subscribable identifier\n"); - pbhash_set_free_with_destructor(hash, _pubnub_subscribable_free); - return NULL; + " memory for presence subscribable identifier\n"); + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + return hash; } if (PBHSR_OUT_OF_MEMORY == pbhash_set_add( @@ -651,15 +754,16 @@ pbhash_set_t* _pubnub_subscription_subscribables( PUBNUB_LOG_ERROR( "pubnub_subscription_subscribables: failed to allocate memory for " "node to store presence subscribable identifier in hash set\n"); - pbhash_set_free_with_destructor(hash, _pubnub_subscribable_free); - _pubnub_subscribable_free(presence); - return NULL; + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + pubnub_subscribable_free_(presence); } return hash; } -pubnub_subscription_set_t* _subscription_set_alloc( +pubnub_subscription_set_t* subscription_set_alloc_( const pubnub_subscription_options_t* options, const int length) { @@ -674,33 +778,38 @@ pubnub_subscription_set_t* _subscription_set_alloc( subscription_set->subscriptions = pbhash_set_alloc( length, PBHASH_SET_CHAR_CONTENT_TYPE, - pubnub_subscription_free); + (pbhash_set_element_free)pubnub_subscription_set_subscription_free_); if (NULL == subscription_set->subscriptions) { PUBNUB_LOG_ERROR("subscription_set_alloc: failed to allocate memory for" - " subscriptions\n"); - pubnub_subscription_set_free(subscription_set); + " subscriptions\n"); + pubnub_subscription_set_free(&subscription_set); return NULL; } - subscription_set->subscribed = false; - subscription_set->reference_count = 1; + subscription_set->subscribed = false; + subscription_set->counter = pbref_counter_alloc(); return subscription_set; } -void _subscription_set_reference_count_update( +void subscription_set_reference_count_update_( pubnub_subscription_set_t* set, const bool increase) { pubnub_mutex_lock(set->mutw); - if (increase) { set->reference_count++; } - else - if (set->reference_count > 0) { set->reference_count--; } + if (increase) { pbref_counter_increment(set->counter); } + else { pbref_counter_decrement(set->counter); } pubnub_mutex_unlock(set->mutw); } -pbhash_set_t* _pubnub_subscription_set_subscribables( +bool pubnub_subscription_set_subscription_free_(pubnub_subscription_t* sub) +{ + subscription_reference_count_update_(sub, false); + return pubnub_subscription_free(&sub); +} + +pbhash_set_t* pubnub_subscription_set_subscribables_( const pubnub_subscription_set_t* set) { size_t count; @@ -709,32 +818,38 @@ pbhash_set_t* _pubnub_subscription_set_subscribables( if (NULL == subs) { PUBNUB_LOG_ERROR("pubnub_subscription_set_subscribables: failed to " - "allocate memory for subscriptions list\n"); - } else if (0 == count) { + "allocate memory for subscriptions list\n"); + } + else if (0 == count) { PUBNUB_LOG_INFO("pubnub_subscription_set_subscribables: subscriptions " - "list is empty"); + "list is empty"); } pbhash_set_t* hash = pbhash_set_alloc( - count * (set->options.receive_presence_events ? 2 : 1), + (0 == count ? 1 : count) * ( + set->options.receive_presence_events ? 2 : 1), PBHASH_SET_CHAR_CONTENT_TYPE, NULL); if (NULL == hash) { PUBNUB_LOG_ERROR("pubnub_subscription_set_get_subscribable: failed to " - "allocate memory for subscribables list\n"); + "allocate memory for subscribables list\n"); if (NULL != subs) { free(subs); } - return NULL; + return hash; } + if (NULL == subs) { return hash; } + for (int i = 0; i < count; ++i) { - pbhash_set_t* subsc = _pubnub_subscription_subscribables( + pbhash_set_t* subsc = pubnub_subscription_subscribables_( subs[i], &set->options); if (NULL == subsc) { - pbhash_set_free_with_destructor(hash, _pubnub_subscribable_free); + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); free(subs); - return NULL; + return hash; } if (PBHSR_OUT_OF_MEMORY == pbhash_set_union(hash, subsc, NULL)) { @@ -742,25 +857,31 @@ pbhash_set_t* _pubnub_subscription_set_subscribables( "pubnub_subscription_set_get_subscribable: failed to allocate " "memory for nodes to store subscribable identifiers from other " "hash set\n"); - pbhash_set_free_with_destructor(hash, _pubnub_subscribable_free); - pbhash_set_free_with_destructor(subsc, _pubnub_subscribable_free); + pbhash_set_free_with_destructor( + &hash, + (pbhash_set_element_free)pubnub_subscribable_free_); + pbhash_set_free_with_destructor( + &subsc, + (pbhash_set_element_free)pubnub_subscribable_free_); free(subs); - return NULL; + return hash; } + + pbhash_set_free(&subsc); } if (NULL != subs) { free(subs); } return hash; } -pubnub_subscribable_t* _pubnub_subscribable_alloc( - const enum pubnub_subscribable_location location, - struct pubnub_char_mem_block* id, - const bool presence) +pubnub_subscribable_t* pubnub_subscribable_alloc_( + const pubnub_subscribable_location location, + struct pubnub_char_mem_block* id, + const bool presence) { PBCC_ALLOCATE_TYPE(sub, pubnub_subscribable_t, true, NULL); - sub->location = location; - sub->managed_memory_block = !presence; + sub->location = location; + sub->managed_memory_block = !presence; if (!presence) { sub->id = id; } else { const char* suffix = "-pnpres"; @@ -773,26 +894,29 @@ pubnub_subscribable_t* _pubnub_subscribable_alloc( return sub; } -size_t _pubnub_subscribable_length(const pubnub_subscribable_t* subscribable) +size_t pubnub_subscribable_length_(const pubnub_subscribable_t* subscribable) { return subscribable->id->size; } -bool _pubnub_subscribable_is_presence(const pubnub_subscribable_t* subscribable) +bool pubnub_subscribable_is_presence_(const pubnub_subscribable_t* subscribable) { - // Checking on variable which is set to `false` for presence because it is - // allocated by the client itself. + /** + * Checking on variable which is set to `false` for presence because it is + * allocated by the client itself. + */ return !subscribable->managed_memory_block; } -bool _pubnub_subscribable_is_cg(const pubnub_subscribable_t* subscribable) +bool pubnub_subscribable_is_cg_(const pubnub_subscribable_t* subscribable) { return subscribable->location == SUBSCRIBABLE_LOCATION_QUERY; } -void _pubnub_subscribable_free(pubnub_subscribable_t* subscribable) +void pubnub_subscribable_free_(pubnub_subscribable_t* subscribable) { if (NULL == subscribable) { return; } + if (!subscribable->managed_memory_block) { if (subscribable->id->ptr) { free(subscribable->id->ptr); } free(subscribable->id); diff --git a/core/pubnub_subscribe_event_engine.h b/core/pubnub_subscribe_event_engine.h index ae821edc..44e5f94a 100644 --- a/core/pubnub_subscribe_event_engine.h +++ b/core/pubnub_subscribe_event_engine.h @@ -1,9 +1,13 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_H #define PUBNUB_SUBSCRIBE_EVENT_ENGINE_H #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #if !PUBNUB_USE_SUBSCRIBE_V2 #error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 -#endif +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API /** @@ -13,6 +17,7 @@ #include #include + #include "core/pubnub_subscribe_event_engine_types.h" #include "core/pubnub_entities.h" #include "lib/pb_extern.h" @@ -84,7 +89,7 @@ pubnub_subscription_options_defopts(void); * @see pubnub_user_metadata_alloc */ PUBNUB_EXTERN pubnub_subscription_t* pubnub_subscription_alloc( - pubnub_entity_t* entity, + pubnub_entity_t* entity, const pubnub_subscription_options_t* options); /** @@ -97,7 +102,7 @@ PUBNUB_EXTERN pubnub_subscription_t* pubnub_subscription_alloc( * // * // ... later ... * // - * if(!pubnub_subscription_free(subscription)) { + * if(!pubnub_subscription_free(&subscription)) { * // This call will decrease references count, but won't actually release * // resources because the event engine holds the additional reference to * // the subscription object. @@ -114,13 +119,16 @@ PUBNUB_EXTERN pubnub_subscription_t* pubnub_subscription_alloc( * \b Warning: Make sure that calls to the `pubnub_subscription_free` are done * only once to not disrupt other types which still use this entity. * + * @note Function may NULLify provided subscription pointer if there is no other + * references to it. + * * @param sub Pointer to the subscription, which should free up resources. * @return `false` if there are more references to the subscription. * * @see pubnub_subscription_alloc * @see pubnub_subscribe_with_subscription */ -PUBNUB_EXTERN bool pubnub_subscription_free(pubnub_subscription_t* sub); +PUBNUB_EXTERN bool pubnub_subscription_free(pubnub_subscription_t** sub); /** * @brief Retrieve subscriptions currently used in subscribe. @@ -152,6 +160,7 @@ PUBNUB_EXTERN bool pubnub_subscription_free(pubnub_subscription_t* sub); * } * // subs: subscription1, subscription2, subscription3 * // subs_count: 3 + * free(subs); * @endcode * * \b Warning: Application execution will be terminated if PubNub pointer is an @@ -169,7 +178,7 @@ PUBNUB_EXTERN bool pubnub_subscription_free(pubnub_subscription_t* sub); */ PUBNUB_EXTERN pubnub_subscription_t** pubnub_subscriptions( const pubnub_t* pb, - size_t* count); + size_t* count); /** * @brief Create a subscription set from the list of entities. @@ -227,8 +236,8 @@ PUBNUB_EXTERN pubnub_subscription_t** pubnub_subscriptions( */ PUBNUB_EXTERN pubnub_subscription_set_t* pubnub_subscription_set_alloc_with_entities( - pubnub_entity_t** entities, - int entities_count, + pubnub_entity_t** entities, + int entities_count, const pubnub_subscription_options_t* options); /** @@ -266,8 +275,8 @@ pubnub_subscription_set_alloc_with_entities( */ PUBNUB_EXTERN pubnub_subscription_set_t* pubnub_subscription_set_alloc_with_subscriptions( - pubnub_subscription_t* sub1, - pubnub_subscription_t* sub2, + pubnub_subscription_t* sub1, + pubnub_subscription_t* sub2, const pubnub_subscription_options_t* options); /** @@ -282,7 +291,7 @@ pubnub_subscription_set_alloc_with_subscriptions( * // * // ... later ... * // - * if (!pubnub_subscription_set_free(subscription_set)) { + * if (!pubnub_subscription_set_free(&subscription_set)) { * // This call will decrease references count, but won't release resources * // because the event engine holds the additional reference to the * // subscription set object. @@ -297,6 +306,9 @@ pubnub_subscription_set_alloc_with_subscriptions( * \b Warning: Make sure that calls to the `pubnub_subscription_set_free` are * done only once to not disrupt other types which still use this entity. * + * @note Function will NULLify provided subscription set pointer if it is no + * other references to it. + * * @param set Pointer to the subscription set, which should free up resources. * @return `false` if there are more references to the subscription set. * @@ -305,7 +317,7 @@ pubnub_subscription_set_alloc_with_subscriptions( * @see pubnub_subscribe_with_subscription_set */ PUBNUB_EXTERN bool pubnub_subscription_set_free( - pubnub_subscription_set_t* set); + pubnub_subscription_set_t** set); /** * @brief Retrieve subscription sets currently used in subscribe. @@ -356,7 +368,7 @@ PUBNUB_EXTERN bool pubnub_subscription_set_free( */ PUBNUB_EXTERN pubnub_subscription_set_t** pubnub_subscription_sets( const pubnub_t* pb, - size_t* count); + size_t* count); /** * @brief Retrieve subscriptions from subscription set. @@ -400,8 +412,8 @@ PUBNUB_EXTERN pubnub_subscription_set_t** pubnub_subscription_sets( * @see pubnub_subscription_set_add */ PUBNUB_EXTERN pubnub_subscription_t** pubnub_subscription_set_subscriptions( - const pubnub_subscription_set_t* set, - size_t* count); + pubnub_subscription_set_t* set, + size_t* count); /** * @brief Add subscription to subscription set. @@ -438,7 +450,7 @@ PUBNUB_EXTERN pubnub_subscription_t** pubnub_subscription_set_subscriptions( */ PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_add( pubnub_subscription_set_t* set, - pubnub_subscription_t* sub); + pubnub_subscription_t* sub); /** * @brief Remove subscription from subscription set. @@ -446,7 +458,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_add( * Update list of set's subscription objects used in subscription loop. * @code * if (PNR_SUB_NOT_FOUND == pubnub_subscription_set_remove(subscription_set, - * subscription2)) { + * &subscription2)) { * // subscription is not part of set and can't be removed from it. * } * @endcode @@ -457,9 +469,12 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_add( * \b Warning: Application execution will be terminated if subscription `set` or * subscription is `NULL`. * + * @note Function may `NULL`ify provided subscription pointer if there is no + * other references to it. + * * @param set Pointer to the subscription set, which should be modified. * @param sub Pointer to the subscription, which should be removed from the set. - * @return `false` if `subscription` is not part of the `subscription_set`. + * @return Result of subscription removal from the subscription set operation. * * @see pubnub_subscription_set_alloc_with_entities * @see pubnub_subscription_set_alloc_with_subscriptions @@ -468,7 +483,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_add( */ PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_remove( pubnub_subscription_set_t* set, - pubnub_subscription_t* sub); + pubnub_subscription_t** sub); /** * @brief Merge a subscription set with other subscription set. @@ -491,15 +506,14 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_remove( * @param set Pointer to the subscription set, which should be modified. * @param other_set Pointer to the subscription set, which should be merged into * a set. - * @return `true` in case if another set's subscriptions have been added to the - * `set`. + * @return Result of subscription sets union operation. * * @see pubnub_subscription_set_alloc_with_entities * @see pubnub_subscription_set_alloc_with_subscriptions * @see pubnub_subscribe_with_subscription_set * @see pubnub_subscribe_with_subscription */ -PUBNUB_EXTERN bool pubnub_subscription_set_union( +PUBNUB_EXTERN enum pubnub_res pubnub_subscription_set_union( pubnub_subscription_set_t* set, pubnub_subscription_set_t* other_set); @@ -529,7 +543,7 @@ PUBNUB_EXTERN bool pubnub_subscription_set_union( * @see pubnub_unsubscribe_with_subscription */ PUBNUB_EXTERN void pubnub_subscription_set_subtract( - pubnub_subscription_set_t* set, + const pubnub_subscription_set_t* set, pubnub_subscription_set_t* other_set); /** @@ -556,7 +570,7 @@ PUBNUB_EXTERN void pubnub_subscription_set_subtract( */ PUBNUB_EXTERN void pubnub_subscribe_set_filter_expression( const pubnub_t* pb, - const char* filter_expr); + const char* filter_expr); /** * @brief Update client presence heartbeat / timeout. @@ -580,8 +594,8 @@ PUBNUB_EXTERN void pubnub_subscribe_set_filter_expression( * alive for presence. */ PUBNUB_EXTERN void pubnub_subscribe_set_heartbeat( - pubnub_t* pb, - unsigned heartbeat); + const pubnub_t* pb, + unsigned heartbeat); /** * @brief Subscription cursor. @@ -626,7 +640,7 @@ PUBNUB_EXTERN pubnub_subscribe_cursor_t pubnub_subscribe_cursor( * @see pubnub_subscription_alloc */ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription( - pubnub_subscription_t* sub, + pubnub_subscription_t* sub, const pubnub_subscribe_cursor_t* cursor); /** @@ -638,6 +652,9 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription( * } * @endcode * + * @note Function may NULLify provided subscription pointer if there is no other + * references to it. + * * @param sub Pointer to the subscription, which should be removed from the next * subscription loop. * @return Result of subscription unsubscribe transaction. @@ -645,7 +662,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription( * @see pubnub_subscription_alloc */ PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_with_subscription( - pubnub_subscription_t* sub); + pubnub_subscription_t** sub); /** * @brief Receive real-time updates for subscriptions' entities from the @@ -675,7 +692,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_with_subscription( * @see pubnub_subscription_set_alloc_with_subscriptions */ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription_set( - pubnub_subscription_set_t* set, + pubnub_subscription_set_t* set, const pubnub_subscribe_cursor_t* cursor); /** @@ -688,6 +705,9 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription_set( * } * @endcode * + * @note Function will NULLify provided subscription set pointer if it is no + * other references to it. + * * @param set Pointer to the set of subscription, which should be removed from * the next subscription loop. * @return Result of subscription set unsubscribe transaction. @@ -696,7 +716,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_with_subscription_set( * @see pubnub_subscription_set_alloc_with_subscriptions */ PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_with_subscription_set( - pubnub_subscription_set_t* set); + pubnub_subscription_set_t** set); /** * @brief Disconnect from real-time updates. @@ -755,7 +775,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_disconnect(const pubnub_t* pb); * use before disconnection / failure. */ PUBNUB_EXTERN enum pubnub_res pubnub_reconnect( - const pubnub_t* pb, + const pubnub_t* pb, const pubnub_subscribe_cursor_t* cursor); /** @@ -782,7 +802,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_reconnect( * @return Result of unsubscription transaction. */ PUBNUB_EXTERN enum pubnub_res pubnub_unsubscribe_all(const pubnub_t* pb); -#else +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PUBNUB_SUBSCRIBE_EVENT_ENGINE_H \ No newline at end of file +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_H diff --git a/core/pubnub_subscribe_event_engine_internal.h b/core/pubnub_subscribe_event_engine_internal.h index 52ea43ad..254bee8f 100644 --- a/core/pubnub_subscribe_event_engine_internal.h +++ b/core/pubnub_subscribe_event_engine_internal.h @@ -1,3 +1,4 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H #define PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE @@ -11,6 +12,7 @@ #include "core/pubnub_subscribe_event_engine.h" #include "core/pbcc_subscribe_event_engine.h" #include "core/pubnub_memory_block.h" +#include "lib/pbref_counter.h" #include "core/pubnub_mutex.h" #include "lib/pbhash_set.h" @@ -19,18 +21,18 @@ // Types // ---------------------------------------------- -// Subscribable object location in subscribe REST API URI. -enum { - // Object should be used as part of the path. +/** Subscribable object location in subscribe REST API URI. */ +typedef enum { + /** Object should be used as part of the path. */ SUBSCRIBABLE_LOCATION_PATH, - // Object should be used as a query parameter. + /** Object should be used as a query parameter. */ SUBSCRIBABLE_LOCATION_QUERY, } pubnub_subscribable_location; -// Subscribable object definition. +/** Subscribable object definition. */ typedef struct { - // Subscribable identifier location in subscribe REST API URI. - enum pubnub_subscribable_location location; + /** Subscribable identifier location in subscribe REST API URI. */ + pubnub_subscribable_location location; /** * @brief Whether lifecycle of the memory block for subscribable identifier * already managed or not. @@ -48,7 +50,7 @@ typedef struct { struct pubnub_char_mem_block* id; } pubnub_subscribable_t; -// Subscription definition. +/** Subscription definition. */ struct pubnub_subscription { /** * @brief Subscription configuration options. @@ -67,19 +69,13 @@ struct pubnub_subscription { * subscription object. */ pbcc_subscribe_ee_t* ee; - /** - * @brief A number of references have been created to this subscription - * object. - * - * \b Important: `pubnub_subscription_free` won't do anything if the value - * is larger than 0. - */ - int reference_count; - // Shared resources access lock. + /** Object references counter. */ + pbref_counter_t* counter; + /** Shared resources access lock. */ pubnub_mutex_t mutw; }; -// Subscription set definition. +/** Subscription set definition. */ struct pubnub_subscription_set { /** * @brief Subscription configuration options. @@ -88,24 +84,18 @@ struct pubnub_subscription_set { * get access to the subscription / set object options. */ pubnub_subscription_options_t options; - // Pointer to the hash set with pointers to subscription objects. + /** Pointer to the hash set with pointers to subscription objects. */ pbhash_set_t* subscriptions; - // Whether a subscription set is used in subscription loop or not. + /** Whether a subscription set is used in subscription loop or not. */ bool subscribed; /** * @brief Pointer to the Subscribe Event Engine, which will operate with a * subscription set object. */ pbcc_subscribe_ee_t* ee; - /** - * @brief A number of references have been created to this subscription set - * object. - * - * \b Important: `pubnub_subscription_set_free` won't do anything if the - * value is larger than 0. - */ - int reference_count; - // Shared resources access lock. + /** Object references counter. */ + pbref_counter_t* counter; + /** Shared resources access lock. */ pubnub_mutex_t mutw; }; @@ -114,6 +104,28 @@ struct pubnub_subscription_set { // Functions // ---------------------------------------------- +/** + * @brief Release resources used by subscription. + * + * @note Function may NULLify provided subscription pointer if there is no other + * references to it. + * + * @param sub Pointer to the subscription, which should free up resources. + * @return `false` if there are more references to the subscription. + */ +bool pubnub_subscription_free_(pubnub_subscription_t* sub); + +/** + * @brief Release resources used by subscription set. + * + * @note Function will NULLify provided subscription set pointer if it is no + * other references to it. + * + * @param set Pointer to the subscription set, which should free up resources. + * @return `false` if there are more references to the subscription set. + */ +bool pubnub_subscription_set_free_(pubnub_subscription_set_t* set); + /** * @brief Get a list of subscription's subscribable objects. * @@ -136,7 +148,7 @@ struct pubnub_subscription_set { * * @see pubnub_subscribable_t */ -pbhash_set_t* _pubnub_subscription_subscribables( +pbhash_set_t* pubnub_subscription_subscribables_( const pubnub_subscription_t* sub, const pubnub_subscription_options_t* options); @@ -160,7 +172,7 @@ pbhash_set_t* _pubnub_subscription_subscribables( * * @see pubnub_subscribable_t */ -pbhash_set_t* _pubnub_subscription_set_subscribables( +pbhash_set_t* pubnub_subscription_set_subscribables_( const pubnub_subscription_set_t* set); /** @@ -170,7 +182,7 @@ pbhash_set_t* _pubnub_subscription_set_subscribables( * retrieved. * @return Length of the subscribable string. */ -size_t _pubnub_subscribable_length(const pubnub_subscribable_t* subscribable); +size_t pubnub_subscribable_length_(const pubnub_subscribable_t* subscribable); /** * @brief Check whether `subscribable` represent name for presence. @@ -179,7 +191,7 @@ size_t _pubnub_subscribable_length(const pubnub_subscribable_t* subscribable); * be retrieved. * @return `false` if `subscribable` represents presence name. */ -bool _pubnub_subscribable_is_presence( +bool pubnub_subscribable_is_presence_( const pubnub_subscribable_t* subscribable); /** @@ -189,7 +201,7 @@ bool _pubnub_subscribable_is_presence( * be retrieved. * @return `false` if `subscribable` represents regular channel name. */ -bool _pubnub_subscribable_is_cg(const pubnub_subscribable_t* subscribable); +bool pubnub_subscribable_is_cg_(const pubnub_subscribable_t* subscribable); /** * @brief Clean up resources used by subscribable. @@ -197,8 +209,8 @@ bool _pubnub_subscribable_is_cg(const pubnub_subscribable_t* subscribable); * @param subscribable Pointer to the subscribable, which should free up * resources. */ -void _pubnub_subscribable_free(pubnub_subscribable_t* subscribable); -#else +void pubnub_subscribable_free_(pubnub_subscribable_t* subscribable); +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H \ No newline at end of file +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_INTERNAL_H \ No newline at end of file diff --git a/core/pubnub_subscribe_event_engine_types.h b/core/pubnub_subscribe_event_engine_types.h index 9efbd008..bf30fa68 100644 --- a/core/pubnub_subscribe_event_engine_types.h +++ b/core/pubnub_subscribe_event_engine_types.h @@ -1,3 +1,4 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H #define PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE @@ -8,6 +9,8 @@ * @brief Public Subscribe Event Engine types. */ +#include + // ---------------------------------------------- // Types forwarding @@ -56,7 +59,7 @@ typedef struct pubnub_subscription pubnub_subscription_t; * @see pubnub_subscription_options_defopts */ typedef struct pubnub_subscription_options { - // Whether presence events should be received or not. + /** Whether presence events should be received or not. */ bool receive_presence_events; } pubnub_subscription_options_t; @@ -71,7 +74,7 @@ typedef struct pubnub_subscription_options { * @see pubnub_subscribe_cursor */ typedef struct { - // PubNub high-precision timestamp. + /** PubNub high-precision timestamp. */ char timetoken[20]; /** * @brief Data center region for which `timetoken` has been generated. @@ -81,23 +84,26 @@ typedef struct { int region; } pubnub_subscribe_cursor_t; -// PubNub subscription statuses. +/** PubNub subscription statuses. */ typedef enum { - // PubNub client subscribe and ready to receive real-time updates. + /** PubNub client subscribe and ready to receive real-time updates. */ SUBSCRIPTION_STATUS_CONNECTED, - // PubNub client were unable to subscribe to receive real-time updates. + /** PubNub client were unable to subscribe to receive real-time updates. */ SUBSCRIPTION_STATUS_CONNECTION_ERROR, - // PubNub client has been disconnected because of some error. + /** PubNub client has been disconnected because of some error. */ SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY, /** * @brief PubNub client has been intentionally temporarily disconnected from * the real-time updates. */ SUBSCRIPTION_STATUS_DISCONNECTED, - // PubNub client has been unsubscribed from all real-time update sources. + /** + * @brief PubNub client has been unsubscribed from all real-time update + * sources. + */ SUBSCRIPTION_STATUS_SUBSCRIPTION_CHANGED } pubnub_subscription_status; -#else +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H \ No newline at end of file +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_ENGINE_TYPES_H \ No newline at end of file diff --git a/core/pubnub_subscribe_event_listener.c b/core/pubnub_subscribe_event_listener.c index af629cbb..700f7537 100644 --- a/core/pubnub_subscribe_event_listener.c +++ b/core/pubnub_subscribe_event_listener.c @@ -1,7 +1,11 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_subscribe_event_listener.h" + + +#include "pbcc_memory_utils.h" #include "core/pubnub_subscribe_event_engine_internal.h" -#include "core/pubnub_internal_common.h" #include "core/pubnub_log.h" +#include "pubnub_internal.h" #include "lib/pbstrdup.h" @@ -24,8 +28,8 @@ * @param callback Real-time update handling listener function. * @return Result of listeners list update operation. */ -static enum pubnub_res _pubnub_subscribe_manage_listener( - const pbcc_event_listener_t* event_listener, +static enum pubnub_res pubnub_subscribe_manage_listener_( + pbcc_event_listener_t* event_listener, bool add, pubnub_subscribe_listener_type type, const void* subscription, @@ -41,7 +45,7 @@ static enum pubnub_res _pubnub_subscribe_manage_listener( * insufficient memory error. The returned pointer must be passed to the * `pbarray_free` to avoid a memory leak. */ -static pbarray_t* _pubnub_subscribe_subscribable_names( +static pbarray_t* pubnub_subscribe_subscribable_names_( pbhash_set_t* subscribables); @@ -98,19 +102,21 @@ enum pubnub_res pubnub_subscribe_add_subscription_listener( const pubnub_subscribe_listener_type type, const pubnub_subscribe_message_callback_t callback) { - pbhash_set_t* subs = _pubnub_subscription_subscribables(subscription, NULL); + pbhash_set_t* subs = pubnub_subscription_subscribables_(subscription, NULL); if (NULL == subs) { return PNR_OUT_OF_MEMORY; } - const pbcc_event_listener_t* event_listener = + pbcc_event_listener_t* event_listener = pbcc_subscribe_ee_event_listener(subscription->ee); - const enum pubnub_res rslt = _pubnub_subscribe_manage_listener( + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( event_listener, true, type, subscription, subs, callback); - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); return rslt; } @@ -120,19 +126,21 @@ enum pubnub_res pubnub_subscribe_remove_subscription_listener( const pubnub_subscribe_listener_type type, const pubnub_subscribe_message_callback_t callback) { - pbhash_set_t* subs = _pubnub_subscription_subscribables(subscription, NULL); + pbhash_set_t* subs = pubnub_subscription_subscribables_(subscription, NULL); if (NULL == subs) { return PNR_OUT_OF_MEMORY; } - const pbcc_event_listener_t* event_listener = + pbcc_event_listener_t* event_listener = pbcc_subscribe_ee_event_listener(subscription->ee); - const enum pubnub_res rslt = _pubnub_subscribe_manage_listener( + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( event_listener, false, type, subscription, subs, callback); - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); return rslt; } @@ -143,19 +151,21 @@ enum pubnub_res pubnub_subscribe_add_subscription_set_listener( const pubnub_subscribe_message_callback_t callback) { pbhash_set_t* subs = - _pubnub_subscription_set_subscribables(subscription_set); + pubnub_subscription_set_subscribables_(subscription_set); if (NULL == subs) { return PNR_OUT_OF_MEMORY; } - const pbcc_event_listener_t* event_listener = + pbcc_event_listener_t* event_listener = pbcc_subscribe_ee_event_listener(subscription_set->ee); - const enum pubnub_res rslt = _pubnub_subscribe_manage_listener( + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( event_listener, true, type, subscription_set, subs, callback); - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); return rslt; } @@ -166,25 +176,27 @@ enum pubnub_res pubnub_subscribe_remove_subscription_set_listener( const pubnub_subscribe_message_callback_t callback) { pbhash_set_t* subs = - _pubnub_subscription_set_subscribables(subscription_set); + pubnub_subscription_set_subscribables_(subscription_set); if (NULL == subs) { return PNR_OUT_OF_MEMORY; } - const pbcc_event_listener_t* event_listener = + pbcc_event_listener_t* event_listener = pbcc_subscribe_ee_event_listener(subscription_set->ee); - const enum pubnub_res rslt = _pubnub_subscribe_manage_listener( + const enum pubnub_res rslt = pubnub_subscribe_manage_listener_( event_listener, false, type, subscription_set, subs, callback); - pbhash_set_free_with_destructor(subs, _pubnub_subscribable_free); + pbhash_set_free_with_destructor( + &subs, + (pbhash_set_element_free)pubnub_subscribable_free_); return rslt; } -enum pubnub_res _pubnub_subscribe_manage_listener( - const pbcc_event_listener_t* event_listener, +enum pubnub_res pubnub_subscribe_manage_listener_( + pbcc_event_listener_t* event_listener, const bool add, const pubnub_subscribe_listener_type type, const void* subscription, @@ -193,11 +205,11 @@ enum pubnub_res _pubnub_subscribe_manage_listener( { if (NULL == subscribables) { return PNR_OUT_OF_MEMORY; } - // Get list of names for which listeners should be updated. - pbarray_t* names = _pubnub_subscribe_subscribable_names(subscribables); + /** Get list of names for which listeners should be updated. */ + pbarray_t* names = pubnub_subscribe_subscribable_names_(subscribables); if (NULL == names) { return PNR_OUT_OF_MEMORY; } - enum pubnub_res rslt = PNR_OK;; + enum pubnub_res rslt = PNR_OK; if (add) { rslt = pbcc_event_listener_add_subscription_object_listener( event_listener, @@ -215,14 +227,17 @@ enum pubnub_res _pubnub_subscribe_manage_listener( callback); } - // If listeners added successfully then names memory managed by event listener. - if (PNR_OK != rslt || !add) { pbarray_free_with_destructor(names, free); } - else { pbarray_free(names); } + /** + * If listeners added successfully then names memory managed by event + * listener. + */ + if (PNR_OK != rslt || !add) { pbarray_free_with_destructor(&names, free); } + else { pbarray_free(&names); } return rslt; } -pbarray_t* _pubnub_subscribe_subscribable_names(pbhash_set_t* subscribables) +pbarray_t* pubnub_subscribe_subscribable_names_(pbhash_set_t* subscribables) { size_t count; pubnub_subscribable_t** subs = (pubnub_subscribable_t**) @@ -255,7 +270,7 @@ pbarray_t* _pubnub_subscribe_subscribable_names(pbhash_set_t* subscribables) "allocate memory for subscribable names array " "element\n"); if (NULL != name) { free(name); } - pbarray_free_with_destructor(names, free); + pbarray_free_with_destructor(&names, free); free(subs); return NULL; } diff --git a/core/pubnub_subscribe_event_listener.h b/core/pubnub_subscribe_event_listener.h index 4952f1af..acdd562e 100644 --- a/core/pubnub_subscribe_event_listener.h +++ b/core/pubnub_subscribe_event_listener.h @@ -1,9 +1,13 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_H #define PUBNUB_SUBSCRIBE_EVENT_LISTENER_H #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #if !PUBNUB_USE_SUBSCRIBE_V2 #error Subscribe event engine requires subscribe v2 API, so you must define PUBNUB_USE_SUBSCRIBE_V2=1 -#endif +#endif // #if !PUBNUB_USE_SUBSCRIBE_V2 +#ifndef PUBNUB_CALLBACK_API +#error Subscribe event engine requires callback based PubNub context, so you must define PUBNUB_CALLBACK_API +#endif // #ifndef PUBNUB_CALLBACK_API /** @@ -158,7 +162,7 @@ PUBNUB_EXTERN enum pubnub_res pubnub_subscribe_remove_subscription_set_listener( const pubnub_subscription_set_t* subscription_set, pubnub_subscribe_listener_type type, pubnub_subscribe_message_callback_t callback); -#else +#else // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE #error To use subscribe event engine API you must define PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=1 -#endif // PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE -#endif //PUBNUB_SUBSCRIBE_EVENT_ENGINE_H \ No newline at end of file +#endif // #if PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_H \ No newline at end of file diff --git a/core/pubnub_subscribe_event_listener_types.h b/core/pubnub_subscribe_event_listener_types.h index 7d83b40c..8d9cfe56 100644 --- a/core/pubnub_subscribe_event_listener_types.h +++ b/core/pubnub_subscribe_event_listener_types.h @@ -1,3 +1,4 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H #define PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H @@ -16,36 +17,70 @@ // Types // ---------------------------------------------- -// PubNub subscribe event listener events sources. -typedef enum { - // Listener to handle real-time messages. +/** PubNub subscribe event listener events sources. */ +typedef enum +{ + /** Listener to handle real-time messages. */ LISTENER_ON_MESSAGE, - // Listener to handle real-time signals. + /** Listener to handle real-time signals. */ LISTENER_ON_SIGNAL, - // Listener to handle message action real-time updates. + /** Listener to handle message action real-time updates. */ LISTENER_ON_MESSAGE_ACTION, - // Listener to handle App Context real-time updates. + /** Listener to handle App Context real-time updates. */ LISTENER_ON_OBJECTS, - // Listener to handle real-time files sharing events. + /** Listener to handle real-time files sharing events. */ LISTENER_ON_FILES } pubnub_subscribe_listener_type; +/** PubNub subscription status change data object. */ +typedef struct +{ + /** + * @brief Error details in case of error. + * + * In case of `SUBSCRIPTION_STATUS_CONNECTION_ERROR` and + * `SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY` may contain additional + * information about reasons of failure. + */ + const enum pubnub_res reason; + /** + * @brief Affected channels. + * + * Byte string with comma-separated / `NULL` channel identifiers which have + * been used with recent operation. + * + * \b Warning: Memory managed by PubNub context and can be freed after + * callback function return. + */ + const char* channels; + /** + * @brief Affected channel groups. + * + * Byte string with comma-separated / `NULL` channel group identifiers which + * have been used with recent operation. + * + * \b Warning: Memory managed by PubNub context and can be freed after + * callback function return. + */ + const char* channel_groups; +} pubnub_subscription_status_data_t; + /** * @brief PubNub subscribe status change function definition. * - * @param pb Pointer to the PubNub context for which subscribe status did - * change. - * @param status PubNub client new subscribe status. - * @param reason In case of `SUBSCRIPTION_STATUS_CONNECTION_ERROR` and - * `SUBSCRIPTION_STATUS_DISCONNECTED_UNEXPECTEDLY` may contain - * additional information about reasons of failure. + * @param pb Pointer to the PubNub context for which subscribe status + * did change. + * @param status PubNub client new subscribe status. + * @param status_data Additional information with details to the recent + * subscription status change. * * @see pubnub_subscription_status + * @see pubnub_subscription_status_data_t */ typedef void (*pubnub_subscribe_status_callback_t)( - const pubnub_t* pb, + const pubnub_t* pb, pubnub_subscription_status status, - enum pubnub_res reason); + pubnub_subscription_status_data_t status_data); /** * @brief PubNub subscribe real-time updates (messages) function definition. @@ -61,6 +96,6 @@ typedef void (*pubnub_subscribe_status_callback_t)( * @see pubnub_subscription_set_t */ typedef void (*pubnub_subscribe_message_callback_t)( - const pubnub_t* pb, + const pubnub_t* pb, struct pubnub_v2_message message); -#endif //PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H \ No newline at end of file +#endif // #ifndef PUBNUB_SUBSCRIBE_EVENT_LISTENER_TYPES_H diff --git a/core/samples/subscribe_event_engine_sample.c b/core/samples/subscribe_event_engine_sample.c new file mode 100644 index 00000000..890b3c7d --- /dev/null +++ b/core/samples/subscribe_event_engine_sample.c @@ -0,0 +1,28 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ +#include "pubnub_callback.h" +#include "core/pubnub_subscribe_event_engine.h" + +#include + +int main() +{ + const char* publish_key = getenv("PUBNUB_PUBLISH_KEY"); + if (NULL == publish_key) { publish_key = "demo"; } + const char* subscribe_key = getenv("PUBNUB_SUBSCRIBE_KEY"); + if (NULL == subscribe_key) { subscribe_key = "demo"; } + + pubnub_t* pubnub = pubnub_alloc(); + pubnub_init(pubnub, publish_key, subscribe_key); + pubnub_set_user_id(pubnub, "demo"); + + pubnub_channel_t* channel = pubnub_channel_alloc(pubnub, "my_channel"); + pubnub_subscription_t* subscription = + pubnub_subscription_alloc((pubnub_entity_t*)channel, NULL); + + pubnub_subscription_free(&subscription); + pubnub_entity_free((void**)&channel); + + puts("Pubnub subscribe event engine demo is over."); + + return 0; +} \ No newline at end of file diff --git a/freertos/pubnub_ntf_callback_freertos.c b/freertos/pubnub_ntf_callback_freertos.c index 04c77b8c..a6c9e15f 100644 --- a/freertos/pubnub_ntf_callback_freertos.c +++ b/freertos/pubnub_ntf_callback_freertos.c @@ -1,6 +1,11 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_ntf_callback.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_internal.h" #include "pubnub_assert.h" #include "pubnub_log.h" @@ -291,9 +296,39 @@ void pbntf_update_socket(pubnub_t *pb, pb_socket_t socket) void pbntf_trans_outcome(pubnub_t *pb) { PBNTF_TRANS_OUTCOME_COMMON(pb); - if (pb->cb != NULL) { - pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); +#if PUBNUB_USE_RETRY_CONFIGURATION + pubnub_mutex_lock(pb->monitor); + uint16_t delay = 0; + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + delay = pubnub_retry_configuration_delay_(pb); + } + pubnub_mutex_unlock(pb->monitor); + + if (delay > 0) { + pubnub_mutex_lock(pb->monitor); + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + + if (NULL != pb->core.retry_timer) { + pubnub_mutex_unlock(pb->monitor); + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + } else { pubnub_mutex_unlock(pb->monitor); } + } else { + pubnub_mutex_lock(pb->monitor); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_stop(pb->core.retry_timer); + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } + pubnub_mutex_unlock(pb->monitor); + +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + if (pb->cb != NULL) { + pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); + } +#if PUBNUB_USE_RETRY_CONFIGURATION } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } diff --git a/lib/pbarray.c b/lib/pbarray.c index a0935058..8ecf4260 100644 --- a/lib/pbarray.c +++ b/lib/pbarray.c @@ -1,9 +1,9 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ - #include "pbarray.h" #include #include + #include "core/pubnub_mutex.h" #include "lib/pbref_counter.h" @@ -12,20 +12,26 @@ // Types // ---------------------------------- +/** + * @brief Single array entry definition. + * + * To avoid potential segmentation failure on a secondary attempt to free + * resources used by the entry, non-raw pointers have been chosen. + */ typedef struct pbarray_entry { - // Pointer to the current entry value. - const void* value; - // Object references counter. + /** Pointer to the current entry value. */ + void* value; + /** Object references counter. */ pbref_counter_t* counter; } pbarray_entry_t; -// Auto-resizable array definition. +/** Auto-resizable array definition. */ struct pbarray { - // Array resize strategy. + /** Array resize strategy. */ pbarray_resize_strategy resize_strategy; - // Type of data which will be stored in array. + /** Type of data which will be stored in array. */ pbarray_content_type content_type; - // Stored elements `free` function. + /** Stored elements `free` function. */ pbarray_element_free free; /** * @brief Initial length of array. @@ -33,13 +39,17 @@ struct pbarray { * Reference length which will be used by new size computation function. */ size_t initial_length; - // Pointer to the pointers to the entries (`pbarray_entry_t`). + /** + * @brief Pointer to the pointers to the entries. + * + * @see pbarray_entry_t + */ void** elements; - // Current array capacity. + /** Current array capacity. */ size_t length; - // Number of elements stored in an array. + /** Number of elements stored in an array. */ size_t count; - // Shared resources access lock. + /** Shared resources access lock. */ pubnub_mutex_t mutw; }; @@ -51,24 +61,30 @@ struct pbarray { /** * @brief Remove element from array. * - * \b Important: The element destruct function (if provided during array - * allocation) will be used for the removed `element` (only if it existed in - * array). + * \b Important: If provided during array allocation, the element destruct + * function will be used for the matched element only if there are no other + * references to it (not shared with other arrays after `pbarray_merge`).
+ * \b Important: The `element` pointer will be NULLified if the memory address + * and value of the `element` match the elements that have been freed + * (no references). * * @note Remove operation is expensive because it requires other elements to * shift their position in array to close the “gap”. * - * @param array Pointer to the array from which element should be - * removed. - * @param element Pointer to the element which should be removed. - * @param all_occurrences Whether all `element` occurrences should be removed or - * not. + * @param array Pointer to the array from which element should be + * removed. + * @param element Pointer to the element which should be removed. + * @param all_occurrences Whether all `element` occurrences should be + * removed or not. + * @param [in,out] freed_value Whether entry value actually has been freed + * (no references) or not. * @return `PBAR_OK` in case if new element has been removed. */ -static pbarray_res _pbarray_remove( - pbarray_t* array, - const void* element, - bool all_occurrences); +static pbarray_res pbarray_remove_( + pbarray_t* array, + void** element, + bool all_occurrences, + bool* freed_value); /** * @brief Compute size, which depends on whether the array increases or shrinks. @@ -77,7 +93,7 @@ static pbarray_res _pbarray_remove( * @param increase Whether array should be increased in size or not. * @return New array size. */ -static size_t _pbarray_target_length(const pbarray_t* array, bool increase); +static size_t pbarray_target_length_(const pbarray_t* array, bool increase); /** * @brief Resize array to fit new elements or shrink to preserve memory. @@ -87,7 +103,7 @@ static size_t _pbarray_target_length(const pbarray_t* array, bool increase); * @param increase Whether array should be increased in size or not. * @return `PBAR_OK` in case if array resized or there were no need in resize. */ -static pbarray_res _pbarray_resize(pbarray_t* array, bool increase); +static pbarray_res pbarray_resize_(pbarray_t* array, bool increase); /** * @brief Check whether elements are equal. @@ -99,7 +115,7 @@ static pbarray_res _pbarray_resize(pbarray_t* array, bool increase); * from array. * @return `true` in case if two elements are equal. */ -static bool _pbarray_element_is_equal( +static bool pbarray_element_is_equal_( const pbarray_t* array, const void* element1, const void* element2); @@ -107,17 +123,12 @@ static bool _pbarray_element_is_equal( /** * @brief Create an array entry object. * - * @param value Pointer to the value which should be safely stored in an - * array. - * @param counter Pointer to the `value` reference counter (if entry for a copy - * from another array). + * @param value Pointer to the value which should be safely stored in an array. * @return Pointer to the ready to use array entry object or `NULL` in case of * insufficient memory error. The returned pointer must be passed to the * `_pbarray_entry_free` to avoid a memory leak. */ -static pbarray_entry_t* _pbarray_entry_alloc( - const void* value, - pbref_counter_t* counter); +static pbarray_entry_t* pbarray_entry_alloc_(void* value); /** * @brief Add entry with user-provided value at specific index in array. @@ -125,12 +136,14 @@ static pbarray_entry_t* _pbarray_entry_alloc( * @param array Pointer to the array which will store the entry. * @param index Index into which `entry` should be placed. * @param entry Pointer to the entry which holds user-provided value. + * @param clone Whether `entry` clone should be added into array instead. * @return Entry addition operation result. */ -static pbarray_res _pbarray_add_entry_at( +static pbarray_res pbarray_add_entry_at_( pbarray_t* array, size_t index, - pbarray_entry_t* entry); + pbarray_entry_t* entry, + bool clone); /** * @brief Remove entry by index from array. @@ -142,27 +155,33 @@ static pbarray_res _pbarray_add_entry_at( * @note Remove operation is expensive because it requires other elements to * shift their position in array to close the “gap”. * - * @param array Pointer to the array from which entry should be removed. - * @param index Index of the `entry` which should be removed. - * @param free_entry Whether entry value destructor should be used or not. + * @param array Pointer to the array from which entry should be + * removed. + * @param index Index of the `entry` which should be removed. + * @param free_value Whether entry value destructor should be used or + * not. + * @param [in,out] freed_value Whether entry value actually has been freed + * (no references) or not. * @return Entry removal operation result. */ -static pbarray_res _pbarray_remove_entry_at( +static pbarray_res pbarray_remove_entry_at_( pbarray_t* array, size_t index, - bool free_entry); + bool free_value, + bool* freed_value); /** * @brief Clean up resources used by entry object. * * @param array Pointer to the array which stored 'entry'. * @param entry Pointer to the entry which should be to free up resources. - * @param free_entry Whether entry value destructor should be used or not. + * @param free_value Whether entry value destructor should be used or not. + * @return `true` if entry value actually has been freed. */ -static void _pbarray_entry_free( +static bool pbarray_entry_free_( const pbarray_t* array, pbarray_entry_t* entry, - bool free_entry); + bool free_value); // ---------------------------------- @@ -195,15 +214,11 @@ pbarray_t* pbarray_alloc( return array; } -pbarray_t* pbarray_copy(const pbarray_t* array) +pbarray_t* pbarray_copy(pbarray_t* array) { - pubnub_mutex_lock(array->mutw); - const size_t count = array->count; - if (0 == count) { - pubnub_mutex_unlock(array->mutw); - return NULL; - } + if (NULL == array->free) { return NULL; } + pubnub_mutex_lock(array->mutw); pbarray_t* copy = pbarray_alloc(array->length, array->resize_strategy, array->content_type, @@ -212,28 +227,22 @@ pbarray_t* pbarray_copy(const pbarray_t* array) pubnub_mutex_unlock(array->mutw); return NULL; } - copy->count = count; + copy->count = array->count; - for (size_t i = 0; i < count; ++i) { - const pbarray_entry_t* entry = array->elements[i]; - pbarray_entry_t* entry_to_add = _pbarray_entry_alloc( - entry->value, - entry->counter); + for (size_t i = 0; i < array->count; ++i) { + pbarray_entry_t* entry = array->elements[i]; - if (NULL == entry_to_add) { - pubnub_mutex_unlock(array->mutw); - pbarray_free(copy); - return NULL; + if (PBAR_OK != pbarray_add_entry_at_(copy, i, entry, true)) { + pbarray_free(©); + break; } - - _pbarray_add_entry_at(copy, i, entry_to_add); } pubnub_mutex_unlock(array->mutw); return copy; } -size_t pbarray_count(const pbarray_t* array) +size_t pbarray_count(pbarray_t* array) { pubnub_mutex_lock(array->mutw); const size_t count = array->count; @@ -242,13 +251,13 @@ size_t pbarray_count(const pbarray_t* array) return count; } -bool pbarray_contains(const pbarray_t* array, const void* element) +bool pbarray_contains(pbarray_t* array, const void* element) { pubnub_mutex_lock(array->mutw); for (int i = 0; i < array->count; ++i) { const pbarray_entry_t* entry = array->elements[i]; - if (_pbarray_element_is_equal(array, entry->value, element)) { + if (pbarray_element_is_equal_(array, entry->value, element)) { pubnub_mutex_unlock(array->mutw); return true; } @@ -258,7 +267,7 @@ bool pbarray_contains(const pbarray_t* array, const void* element) return false; } -const void** pbarray_elements(const pbarray_t* array, size_t* count) +const void** pbarray_elements(pbarray_t* array, size_t* count) { pubnub_mutex_lock(array->mutw); const size_t cnt = array->count; @@ -271,78 +280,81 @@ const void** pbarray_elements(const pbarray_t* array, size_t* count) } for (size_t i = 0; i < cnt; ++i) { - elements[i] = (void*)((pbarray_entry_t*)array->elements[i])->value; + elements[i] = ((pbarray_entry_t*)array->elements[i])->value; } pubnub_mutex_unlock(array->mutw); return elements; } -pbarray_res pbarray_add(pbarray_t* array, const void* element) +pbarray_res pbarray_add(pbarray_t* array, void* element) { pubnub_mutex_lock(array->mutw); - pbarray_entry_t* entry = _pbarray_entry_alloc(element, NULL); + pbarray_entry_t* entry = pbarray_entry_alloc_(element); if (NULL == entry) { pubnub_mutex_unlock(array->mutw); return PBAR_OUT_OF_MEMORY; } const pbarray_res result = - _pbarray_add_entry_at(array, array->count, entry); - if (PBAR_OK != result) { _pbarray_entry_free(array, entry, false); } + pbarray_add_entry_at_(array, array->count, entry, false); + if (PBAR_OK != result) { pbarray_entry_free_(array, entry, false); } pubnub_mutex_unlock(array->mutw); return result; } -pbarray_res pbarray_insert( +pbarray_res pbarray_insert_at( pbarray_t* array, - const void* element, + void* element, const size_t idx) { pubnub_mutex_lock(array->mutw); - pbarray_entry_t* entry = _pbarray_entry_alloc(element, NULL); + pbarray_entry_t* entry = pbarray_entry_alloc_(element); if (NULL == entry) { pubnub_mutex_unlock(array->mutw); return PBAR_OUT_OF_MEMORY; } - const pbarray_res result = _pbarray_add_entry_at(array, idx, entry); - if (PBAR_OK != result) { _pbarray_entry_free(array, entry, false); } + const pbarray_res result = pbarray_add_entry_at_(array, idx, entry, false); + if (PBAR_OK != result) { pbarray_entry_free_(array, entry, false); } pubnub_mutex_unlock(array->mutw); return result; } -pbarray_res pbarray_merge(pbarray_t* array, const pbarray_t* other_array) +pbarray_res pbarray_merge(pbarray_t* array, pbarray_t* other_array) { if (NULL == other_array) { return PBAR_OK; } pubnub_mutex_lock(array->mutw); pbarray_res result = PBAR_OK; + pubnub_mutex_lock(other_array->mutw); for (size_t i = 0; i < other_array->count; ++i) { - const pbarray_entry_t* entry = other_array->elements[i]; - pbarray_entry_t* entry_to_add = _pbarray_entry_alloc( - entry->value, - entry->counter); + pbarray_entry_t* entry = other_array->elements[i]; + result = pbarray_add_entry_at_(array, array->count, entry, true); - if (NULL == entry_to_add) { result = PBAR_OUT_OF_MEMORY; } - else { result = _pbarray_add_entry_at(array, i, entry_to_add); } if (PBAR_OK != result) { break; } } + pubnub_mutex_unlock(other_array->mutw); pubnub_mutex_unlock(array->mutw); return result; } pbarray_res pbarray_remove( - pbarray_t* array, - const void* element, - const bool all_occurrences) + pbarray_t* array, + void** element, + const bool all_occurrences) { pubnub_mutex_lock(array->mutw); - const pbarray_res result = _pbarray_remove(array, element, all_occurrences); + bool freed = false; + const pbarray_res result = + pbarray_remove_(array, element, all_occurrences, &freed); + + /** Nullify element pointer if memory actually has been freed. */ + if (freed && NULL != element && NULL != *element) { *element = NULL; } pubnub_mutex_unlock(array->mutw); return result; @@ -356,51 +368,55 @@ pbarray_res pbarray_remove_element_at(pbarray_t* array, const size_t idx) return idx >= array->count ? PBAR_INDEX_OUT_OF_RANGE : PBAR_OK; } - _pbarray_remove_entry_at(array, idx, true); + pbarray_remove_entry_at_(array, idx, true, NULL); pubnub_mutex_unlock(array->mutw); return PBAR_OK; } -pbarray_res pbarray_remove(pbarray_t* array) +pbarray_res pbarray_remove_all(pbarray_t* array) { pubnub_mutex_lock(array->mutw); for (int i = 0; i < array->count - 1; ++i) { - _pbarray_entry_free(array, array->elements[i], true); + pbarray_entry_free_(array, array->elements[i], true); } array->count = 0; + pbarray_resize_(array, false); pubnub_mutex_unlock(array->mutw); return PBAR_OK; } void pbarray_subtract( - pbarray_t* array, - const pbarray_t* other_array, - const bool all_occurrences) + pbarray_t* array, + pbarray_t* other_array, + const bool all_occurrences) { if (NULL == other_array) { return; } pubnub_mutex_lock(array->mutw); + pubnub_mutex_lock(other_array->mutw); for (size_t i = 0; i < other_array->count; ++i) { const pbarray_entry_t* other_entry = other_array->elements[i]; for (int j = 0; j < array->count;) { const pbarray_entry_t* entry = array->elements[j]; - if (_pbarray_element_is_equal(array, + if (pbarray_element_is_equal_(array, entry->value, other_entry->value)) { - _pbarray_remove_entry_at(array, j, true); + pbarray_remove_entry_at_(array, j, true, NULL); if (!all_occurrences) { break; } } else { j++; } } } + pbarray_resize_(array, false); + pubnub_mutex_unlock(other_array->mutw); pubnub_mutex_unlock(array->mutw); } -const void* pbarray_element_at(const pbarray_t* array, const size_t idx) +const void* pbarray_element_at(pbarray_t* array, const size_t idx) { pubnub_mutex_lock(array->mutw); if (0 == array->count || idx >= array->count) { @@ -414,7 +430,7 @@ const void* pbarray_element_at(const pbarray_t* array, const size_t idx) return entry->value; } -const void* pbarray_first(const pbarray_t* array) +const void* pbarray_first(pbarray_t* array) { pubnub_mutex_lock(array->mutw); const size_t count = array->count; @@ -424,7 +440,7 @@ const void* pbarray_first(const pbarray_t* array) return 0 == count ? NULL : entry->value; } -const void* pbarray_last(const pbarray_t* array) +const void* pbarray_last(pbarray_t* array) { pubnub_mutex_lock(array->mutw); const size_t count = array->count; @@ -442,8 +458,8 @@ const void* pbarray_pop_first(pbarray_t* array) } const pbarray_entry_t* entry = array->elements[0]; - const void* value = entry->value; - _pbarray_remove_entry_at(array, 0, false); + const void* value = entry->value; + pbarray_remove_entry_at_(array, 0, false, NULL); pubnub_mutex_unlock(array->mutw); return value; @@ -458,60 +474,74 @@ const void* pbarray_pop_last(pbarray_t* array) } const pbarray_entry_t* entry = array->elements[array->count - 1]; - const void* value = entry->value; - _pbarray_remove_entry_at(array, array->count - 1, false); + const void* value = entry->value; + pbarray_remove_entry_at_(array, array->count - 1, false, NULL); pubnub_mutex_unlock(array->mutw); return value; } -void pbarray_free(pbarray_t* array) +void pbarray_free(pbarray_t** array) { - pbarray_free_with_destructor(array, array->free); + pbarray_free_with_destructor(array, (*array)->free); } void pbarray_free_with_destructor( - pbarray_t* array, + pbarray_t** array, const pbarray_element_free free_fn) { - pubnub_mutex_lock(array->mutw); - if (NULL != free_fn) { array->free = free_fn; } - for (int i = 0; i < array->count; ++i) { - _pbarray_entry_free(array, array->elements[i], true); + pubnub_mutex_lock((*array)->mutw); + if (NULL != free_fn) { (*array)->free = free_fn; } + for (int i = 0; i < (*array)->count; ++i) { + pbarray_entry_free_(*array, (*array)->elements[i], true); } - pubnub_mutex_unlock(array->mutw); - pubnub_mutex_destroy(array->mutw); - free(array); + pubnub_mutex_unlock((*array)->mutw); + pubnub_mutex_destroy((*array)->mutw); + free(*array); + *array = NULL; } -pbarray_res _pbarray_remove( - pbarray_t* array, - const void* element, - const bool all_occurrences) +pbarray_res pbarray_remove_( + pbarray_t* array, + void** element, + const bool all_occurrences, + bool* freed_value) { - if (NULL == element || 0 == array->count) { return PBAR_NOT_FOUND; } + if (NULL == element || NULL == *element || 0 == array->count) + return PBAR_NOT_FOUND; - bool found = false; + bool freed_match = false; + bool found = false; + /** Whether single entry value is complete match of the element or not. */ + bool complete_match = false; + /** Whether single entry value freed or not. */ + bool freed = false; for (int i = 0; i < array->count;) { const pbarray_entry_t* entry = array->elements[i]; - if (_pbarray_element_is_equal(array, entry->value, element)) { - _pbarray_remove_entry_at(array, i, true); - found = true; + if (pbarray_element_is_equal_(array, entry->value, *element)) { + complete_match = entry->value == *element; + found = true; + pbarray_remove_entry_at_(array, i, true, &freed); + + if (complete_match && !freed_match) { freed_match = freed; } if (!all_occurrences) { break; } } else { i++; } } + pbarray_resize_(array, false); - _pbarray_resize(array, false); + /** Nullify element pointer if memory actually has been freed. */ + if (freed_match) { *element = NULL; } + if (NULL != freed_value) { *freed_value = freed_match; } return found ? PBAR_OK : PBAR_NOT_FOUND; } -size_t _pbarray_target_length(const pbarray_t* array, const bool increase) +size_t pbarray_target_length_(const pbarray_t* array, const bool increase) { - int resize_len = 0; + size_t resize_len = 0; if (array->resize_strategy == PBARRAY_RESIZE_CONSERVATIVE) { resize_len = 1; } @@ -519,7 +549,7 @@ size_t _pbarray_target_length(const pbarray_t* array, const bool increase) resize_len = increase ? array->length : array->initial_length; } else if (array->resize_strategy == PBARRAY_RESIZE_BALANCED) { - resize_len = array->initial_length * .5; + resize_len = array->initial_length / 2; } if (!increase) { @@ -538,9 +568,9 @@ size_t _pbarray_target_length(const pbarray_t* array, const bool increase) return array->length + resize_len * (increase ? 1 : -1); } -pbarray_res _pbarray_resize(pbarray_t* array, const bool increase) +pbarray_res pbarray_resize_(pbarray_t* array, const bool increase) { - const size_t length = _pbarray_target_length(array, increase); + const size_t length = pbarray_target_length_(array, increase); if (0 == length) { return PBAR_FIXED_SIZE; } if (length != array->length) { @@ -555,7 +585,7 @@ pbarray_res _pbarray_resize(pbarray_t* array, const bool increase) return PBAR_OK; } -bool _pbarray_element_is_equal( +bool pbarray_element_is_equal_( const pbarray_t* array, const void* element1, const void* element2) @@ -569,58 +599,59 @@ bool _pbarray_element_is_equal( return false; } -pbarray_entry_t* _pbarray_entry_alloc( - const void* value, - pbref_counter_t* counter) +pbarray_entry_t* pbarray_entry_alloc_(void* value) { pbarray_entry_t* entry = malloc(sizeof(pbarray_entry_t)); if (NULL == entry) { return NULL; } - if (NULL == counter) { - entry->counter = pbref_counter_alloc(); - entry->value = value; - } - else { entry->counter = counter; } + entry->counter = pbref_counter_alloc(); + entry->value = value; return entry; } -pbarray_res _pbarray_add_entry_at( +pbarray_res pbarray_add_entry_at_( pbarray_t* array, const size_t index, - pbarray_entry_t* entry) + pbarray_entry_t* entry, + const bool clone) { if (index > array->length) { return PBAR_INDEX_OUT_OF_RANGE; } - const pbarray_res resize_result = _pbarray_resize(array, true); + const pbarray_res resize_result = pbarray_resize_(array, true); if (PBAR_OK != resize_result) { return resize_result; } - // Check whether addition is done not to the end of the array to shift - // other entries position. + /** + * Check whether addition is done not to the end of the array to shift + * other entries position. + */ if (array->count != index) { - for (int i = array->count; i > index; --i) { + for (size_t i = array->count; i > index; --i) { array->elements[i] = array->elements[i - 1]; } } + /** For safe sharing we need to increase counter. */ + if (clone) { pbref_counter_increment(entry->counter); } - pbref_counter_increment(entry->counter); array->elements[index] = (void*)entry; array->count++; return PBAR_OK; } -pbarray_res _pbarray_remove_entry_at( +pbarray_res pbarray_remove_entry_at_( pbarray_t* array, const size_t index, - const bool free_entry) + const bool free_value, + bool* freed_value) { pbarray_entry_t* entry = array->elements[index]; if (NULL == entry) { return PBAR_NOT_FOUND; } - _pbarray_entry_free(array, entry, free_entry); + const bool freed = pbarray_entry_free_(array, entry, free_value); + if (NULL != freed_value) { *freed_value = freed; } - for (int j = index; j < array->count - 1; ++j) { + for (size_t j = index; j < array->count - 1; ++j) { array->elements[j] = array->elements[j + 1]; } array->count--; @@ -628,16 +659,22 @@ pbarray_res _pbarray_remove_entry_at( return PBAR_OK; } -void _pbarray_entry_free( +bool pbarray_entry_free_( const pbarray_t* array, pbarray_entry_t* entry, - const bool free_entry) + const bool free_value) { - if (NULL == entry) { return; } + if (NULL == entry) { return true; } + + bool freed = false; if (0 == pbref_counter_free(entry->counter)) { - if (free_entry && NULL != array->free) - array->free((void*)entry->value); + if (free_value && NULL != array->free) { + array->free(entry->value); + freed = true; + } + free(entry); } - free(entry); + + return freed; } \ No newline at end of file diff --git a/lib/pbarray.h b/lib/pbarray.h index 04761856..ef4191b4 100644 --- a/lib/pbarray.h +++ b/lib/pbarray.h @@ -1,5 +1,4 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ - #ifndef PBARRAY_H #define PBARRAY_H @@ -17,7 +16,8 @@ // Types // ---------------------------------- -typedef enum { +typedef enum +{ /** * @brief Array with static length. * @@ -57,7 +57,8 @@ typedef enum { * * Information about array content lets some functions work more efficiently. */ -typedef enum { +typedef enum +{ /** * @brief Array used to store generic data as pointers. * @@ -72,8 +73,9 @@ typedef enum { PBARRAY_CHAR_CONTENT_TYPE } pbarray_content_type; -// Array result codes. -typedef enum { +/** Array result codes. */ +typedef enum +{ /** * @brief Success. * @@ -114,10 +116,12 @@ typedef enum { * @brief Array element `free` function prototype. * * Function which will be used when hash set is freed using 'pbarray_free'. + * + * @note Element destructor function will `NULL`ify provided pointer. */ typedef void (*pbarray_element_free)(void*); -// Auto-resizable array type definition. +/** Auto-resizable array type definition. */ typedef struct pbarray pbarray_t; @@ -127,6 +131,25 @@ typedef struct pbarray pbarray_t; /** * @brief Create auto-resizable array. + * @code + * // Example: Create a static sized array. + * pbarray_t* array = pbarray_alloc(1, + * PBARRAY_RESIZE_NONE, + * PBARRAY_CHAR_CONTENT_TYPE, + * free); + * pbarray_add(array, "hello"); + * // This call will fail because PBARRAY_RESIZE_NONE strategy has been used. + * pbarray_add(array, " there!"); + * @endcode + * @code + * // Example: Create array with dynamic length. + * pbarray_t* array = pbarray_alloc(1, + * PBARRAY_RESIZE_OPTIMISTIC, + * PBARRAY_CHAR_CONTENT_TYPE, + * free); + * pbarray_add(array, "hello"); + * pbarray_add(array, " there!"); + * @endcode * * @param length Initial length of the array. * @param resize_strategy Strategy which should be used when a new element @@ -137,22 +160,54 @@ typedef struct pbarray pbarray_t; * @return Pointer to the ready to use resizable array or `NULL` in case of * insufficient memory error. The returned pointer must be passed to the * `pbarray_free` to avoid a memory leak. + * + * @see pbarray_add + * @see pbarray_free */ pbarray_t* pbarray_alloc( - size_t length, + size_t length, pbarray_resize_strategy resize_strategy, - pbarray_content_type content_type, - pbarray_element_free free_fn); + pbarray_content_type content_type, + pbarray_element_free free_fn); /** - * @brief Create shallow copy. + * @brief Create shallow array copy. + * @code + * pbarray_add(array_orig, "hello"); + * pbarray_add(array_orig, " there!"); + * pbarray_t* array_copy = pbarray_copy(array_orig); + * + * // Prints: Contains 'hello' in 'array_copy'? true + * printf("Contains 'hello' in 'array_copy'? %s", + * pbarray_contains(array_copy, "hello") ? "true" : "false"); + * + * // Prints: Contains ' there!' in 'array_copy'? true + * printf("Contains ' there!' in 'array_copy'? %s", + * pbarray_contains(array_copy, " there!") ? "true" : "false"); + * @endcode + * + * \b Important: Copies store shared object, which never shouldn't be freed + * directly to avoid runtime segmentation fault error. This implementation uses + * reference counting and actual data will be freed only if all arrays freed or + * entry has been removed from all arrays.
+ * \b Important: Make sure to have element destructor specified for the source + * array or in case of insufficient memory error already copied entries won't be + * able to free up used resource. * * @param array Pointer to the array from which shallow copy should be created. - * @return Pointer to the shallow `array` copy or `NULL` if case of insufficient + * @return Pointer to the `array` copy or `NULL` if case of insufficient * memory error. The returned pointer must be passed to the * `pbarray_free` to avoid a memory leak. + * + * @see pbarray_remove + * @see pbarray_remove_element_at + * @see pbarray_remove_all + * @see pbarray_pop_first + * @see pbarray_pop_last + * @see pbarray_free + * @see pbarray_free_with_destructor */ -pbarray_t* pbarray_copy(const pbarray_t* array); +pbarray_t* pbarray_copy(pbarray_t* array); /** * @brief Number of elements in array. @@ -161,7 +216,7 @@ pbarray_t* pbarray_copy(const pbarray_t* array); * retrieved. * @return Number of elements added to the array. */ -size_t pbarray_count(const pbarray_t* array); +size_t pbarray_count(pbarray_t* array); /** * @brief Check whether element has been added to the array or not. @@ -171,13 +226,12 @@ size_t pbarray_count(const pbarray_t* array); * @param element Pointer to the element which should be checked. * @return `true` in case if `element` already exists in the `array`. */ -bool pbarray_contains(const pbarray_t* array, const void* element); +bool pbarray_contains(pbarray_t* array, const void* element); /** - * @brief Array elements. - * - * Shallow copy of the elements in array. + * @brief Get array elements. * @code + * // Example: Shallow copy of the elements in array. * size_t count; * void** elements = pbarray_elements(array, &count); * if (NULL == elements) { @@ -192,6 +246,12 @@ bool pbarray_contains(const pbarray_t* array, const void* element); * if (NULL != elements) { free(elements); } * @endcode * + * \b Warning: Returned pointer to the array's element pointers share entries + * with `array` and they shouldn't be manually freed.
+ * \b Warning: Returned pointer to the array's element pointers, and could be + * invalid if `array` or all (some) elements are freed before values will be + * used. + * * @param array Pointer to the array, from which list of element pointers * should be retrieved. * @param [out] count Parameter will hold the count of returned elements. @@ -199,9 +259,7 @@ bool pbarray_contains(const pbarray_t* array, const void* element); * insufficient memory error. The returned pointer must be passed to the * `free` to avoid a memory leak. */ -const void** pbarray_elements( - const pbarray_t* array, - size_t* count); +const void** pbarray_elements(pbarray_t* array, size_t* count); /** * @brief Add a new element to the end of the array. @@ -210,7 +268,7 @@ const void** pbarray_elements( * @param element Pointer to the element which should be added. * @return `PBAR_OK` in case if new element has been added. */ -pbarray_res pbarray_add(pbarray_t* array, const void* element); +pbarray_res pbarray_add(pbarray_t* array, void* element); /** * @brief Insert a new element at a specific location in the array. @@ -224,7 +282,7 @@ pbarray_res pbarray_add(pbarray_t* array, const void* element); * specified index, and after it will be pushed to the right. * @return `PBAR_OK` in case if new element has been inserted. */ -pbarray_res pbarray_insert(pbarray_t* array, const void* element, size_t idx); +pbarray_res pbarray_insert_at(pbarray_t* array, void* element, size_t idx); /** * @brief Add elements from another array to the end of the array. @@ -233,30 +291,32 @@ pbarray_res pbarray_insert(pbarray_t* array, const void* element, size_t idx); * @param other_array Pointer to the array with elements which should be added. * @return `PBAR_OK` in case if new elements has been added. */ -pbarray_res pbarray_merge(pbarray_t* array, const pbarray_t* other_array); +pbarray_res pbarray_merge(pbarray_t* array, pbarray_t* other_array); /** * @brief Remove element from array. * - * \b Important: The element destruct function (if provided during array - * allocation) will be used for the removed `element` (only if it existed in - * array). + * \b Important: If provided during array allocation, the element destruct + * function will be used for the matched element only if there are no other + * references to it (not shared with other arrays after `pbarray_merge`).
+ * \b Important: The `element` pointer will be NULLified if the memory address + * and value of the `element` match the elements that have been freed + * (no references). * * @note Remove operation is expensive because it requires other elements to * shift their position in array to close the “gap”. * * @param array Pointer to the array from which element should be * removed. - * @param element Pointer to the element which should be removed.e removed or - * not. + * @param element Pointer to the element which should be removed or not. * @param all_occurrences Whether all `element` occurrences should be removed or * not. * @return `PBAR_OK` in case if element has been removed. */ pbarray_res pbarray_remove( - pbarray_t* array, - const void* element, - bool all_occurrences); + pbarray_t* array, + void** element, + bool all_occurrences); /** * @brief Remove element at specific index from array. @@ -297,9 +357,9 @@ pbarray_res pbarray_remove_all(pbarray_t* array); * should be removed from `array` or not. */ void pbarray_subtract( - pbarray_t* array, - const pbarray_t* other_array, - bool all_occurrences); + pbarray_t* array, + pbarray_t* other_array, + bool all_occurrences); /** * @brief Get element from `array` at specified index. @@ -309,7 +369,7 @@ void pbarray_subtract( * @return Pointer to the element from array at specified index or `NULL` if it * is empty or smaller than provided `idx`. */ -const void* pbarray_element_at(const pbarray_t* array, size_t idx); +const void* pbarray_element_at(pbarray_t* array, size_t idx); /** * @brief First element in `array`. @@ -317,7 +377,7 @@ const void* pbarray_element_at(const pbarray_t* array, size_t idx); * @param array Pointer to the array from which element should be retrieved. * @return Pointer to the first element in array or `NULL` if it is empty. */ -const void* pbarray_first(const pbarray_t* array); +const void* pbarray_first(pbarray_t* array); /** * @brief Last element in `array`. @@ -325,7 +385,7 @@ const void* pbarray_first(const pbarray_t* array); * @param array Pointer to the array from which element should be retrieved. * @return Pointer to the last element in array or `NULL` if it is empty. */ -const void* pbarray_last(const pbarray_t* array); +const void* pbarray_last(pbarray_t* array); /** * @brief Remove first element from `array`. @@ -349,18 +409,20 @@ const void* pbarray_pop_last(pbarray_t* array); * @brief Clean up resources used by `array`. * * @note User is responsible for reclaiming resources used by the elements if - * element destructing function is not provided + * element destructing function is not provided. + * @note Function will `NULL`ify provided array pointer. * * @param array Pointer to the array, which should free up resources used for it * and stored elements. */ -void pbarray_free(pbarray_t* array); +void pbarray_free(pbarray_t** array); /** * @brief Clean up resources used by `array`. * * @note User is responsible for reclaiming resources used by the elements if - * element destructing function is not provided + * element destructing function is not provided. + * @note Function will NULLify provided array pointer. * * @param array Pointer to the array, which should free up resources used for * it and stored elements. @@ -368,6 +430,6 @@ void pbarray_free(pbarray_t* array); * leave memory management to the array user. */ void pbarray_free_with_destructor( - pbarray_t* array, + pbarray_t** array, pbarray_element_free free_fn); -#endif //PBARRAY_H \ No newline at end of file +#endif // #ifndef PBARRAY_H diff --git a/lib/pbarray_unit_test.c b/lib/pbarray_unit_test.c index 8fdb3c8f..9a009107 100644 --- a/lib/pbarray_unit_test.c +++ b/lib/pbarray_unit_test.c @@ -190,7 +190,7 @@ Ensure(pbarray, should_insert_element_at_index_when_index_in_range) pbarray_add(array, "hello twice"); pbarray_add(array, "hello again"); - assert_that(pbarray_insert(array, "hello impostor", 1), + assert_that(pbarray_insert_at(array, "hello impostor", 1), is_equal_to(PBAR_OK)); assert_that(pbarray_count(array), is_equal_to(4)); assert_that(pbarray_element_at(array, 0), is_equal_to_string("hello once")); @@ -209,7 +209,7 @@ Ensure(pbarray, should_insert_element_at_index_when_index_equal_length) pbarray_add(array, "hello once"); pbarray_add(array, "hello twice"); - assert_that(pbarray_insert(array, "hello again", pbarray_count(array)), + assert_that(pbarray_insert_at(array, "hello again", pbarray_count(array)), is_equal_to(PBAR_OK)); assert_that(pbarray_count(array), is_equal_to(3)); assert_that(pbarray_element_at(array, 2), @@ -225,7 +225,7 @@ Ensure(pbarray, should_not_insert_element_at_index_when_index_out_of_range) pbarray_add(array, "hello once"); pbarray_add(array, "hello twice"); - assert_that(pbarray_insert(array, "hello again", 4), + assert_that(pbarray_insert_at(array, "hello again", 4), is_equal_to(PBAR_INDEX_OUT_OF_RANGE)); assert_that(pbarray_count(array), is_equal_to(2)); } diff --git a/lib/pbhash_set.c b/lib/pbhash_set.c index a2e838d7..1be1cc8e 100644 --- a/lib/pbhash_set.c +++ b/lib/pbhash_set.c @@ -1,9 +1,9 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ - #include "pbhash_set.h" #include #include + #include "core/pubnub_mutex.h" #include "lib/pbref_counter.h" @@ -17,29 +17,30 @@ typedef struct pbhash_set_node { * @brieg Optionally, could be a pointer to the structure which may contain * `value`. */ - const void* containing; - // Pointer to the current node value. - const void* value; - // Object references counter. + void* containing; + /** Pointer to the current node value. */ + void* value; + /** Object references counter. */ pbref_counter_t* counter; - // Pointer to the next node with similar hash value. + /** Pointer to the next node with similar hash value. */ struct pbhash_set_node* next; - // Shared resources access lock. + /** Shared resources access lock. */ pubnub_mutex_t mutw; } pbhash_set_node_t; +/** Hash set definition. */ struct pbhash_set { - // Type of data which will be stored in a hash set. + /** Type of data which will be stored in a hash set. */ pbhash_set_content_type content_type; - // Pointer to the list of node pointers which hold values. + /** Pointer to the list of node pointers which hold values. */ pbhash_set_node_t** buckets; - // A number of buckets can be maintained by hash set. + /** A number of buckets can be maintained by hash set. */ size_t bucket_length; - // How many currently stored in a hash set. + /** How many currently stored in a hash set. */ size_t elements_count; - // Element entries destructor. + /** Element entries destructor. */ pbhash_set_element_free free; - // Shared resources access lock. + /** Shared resources access lock. */ pubnub_mutex_t mutw; }; @@ -53,8 +54,9 @@ struct pbhash_set { * * @param set Pointer to the hash set, from which node should be removed. * @param node Pointer to the node which should be freed. + * @return `true` if node's value has been freed. */ -static void _pbhash_set_free_node( +static bool pbhash_set_free_node_( const pbhash_set_t* set, pbhash_set_node_t* node); @@ -72,7 +74,7 @@ static void _pbhash_set_free_node( * not. * @return Result of `node` addition to the hash set. */ -static pbhash_set_res _pbhash_set_add_node( +static pbhash_set_res pbhash_set_add_node_( pbhash_set_t* set, pbhash_set_node_t* node, size_t element_hash, @@ -86,7 +88,7 @@ static pbhash_set_res _pbhash_set_add_node( * @param element Pointer to the element for which hash should be computed. * @return Content type-dependant `element` hash value. */ -static size_t _pbhash_set_element_hash( +static size_t pbhash_set_element_hash_( const pbhash_set_t* set, const void* element); @@ -102,7 +104,7 @@ static size_t _pbhash_set_element_hash( * @param element_hash Computed hash of bucket to store `element` in it. * @return `PBHSR_NOT_FOUND` in case if `element` not found in the hash set. */ -static pbhash_set_res _pbhash_set_contains( +static pbhash_set_res pbhash_set_contains_( const pbhash_set_t* set, const void* element, size_t element_hash); @@ -117,7 +119,7 @@ static pbhash_set_res _pbhash_set_contains( * * @see pbhash_set_add */ -static pbhash_set_res _pbhash_set_match_element( +static pbhash_set_res pbhash_set_match_element_( const pbhash_set_t* set, const void* element); @@ -135,7 +137,7 @@ static pbhash_set_res _pbhash_set_match_element( * compare. * @return `true` in case if two elements match each other. */ -static bool _pbhash_set_is_equal_elements( +static bool pbhash_set_is_equal_elements_( const pbhash_set_t* set, const void* element1, const void* element2); @@ -170,12 +172,12 @@ pbhash_set_t* pbhash_set_alloc( pbhash_set_res pbhash_set_add( pbhash_set_t* set, - const void* element, - const void* containing) + void* element, + void* containing) { pubnub_mutex_lock(set->mutw); - const size_t hash = _pbhash_set_element_hash(set, element); - pbhash_set_res rslt = _pbhash_set_contains(set, element, hash); + const size_t hash = pbhash_set_element_hash_(set, element); + pbhash_set_res rslt = pbhash_set_contains_(set, element, hash); if (PBHSR_NOT_FOUND != rslt) { pubnub_mutex_unlock(set->mutw); @@ -193,7 +195,7 @@ pbhash_set_res pbhash_set_add( node->containing = containing; node->value = element; node->next = NULL; - rslt = _pbhash_set_add_node(set, node, hash, false); + rslt = pbhash_set_add_node_(set, node, hash, false); pubnub_mutex_unlock(set->mutw); return rslt; @@ -201,18 +203,28 @@ pbhash_set_res pbhash_set_add( pbhash_set_res pbhash_set_remove( pbhash_set_t* set, - const void* element) + void** element, + void** containing) { pubnub_mutex_lock(set->mutw); - const size_t hash = _pbhash_set_element_hash(set, element); + if (NULL == element || NULL == *element || 0 == set->elements_count) { + pubnub_mutex_unlock(set->mutw); + return PBHSR_NOT_FOUND; + } + + const size_t hash = pbhash_set_element_hash_(set, *element); pbhash_set_node_t* node = set->buckets[hash]; pbhash_set_node_t* prev = NULL; while (NULL != node) { - if (_pbhash_set_is_equal_elements(set, node->value, element)) { + if (pbhash_set_is_equal_elements_(set, node->value, *element)) { if (NULL == prev) { set->buckets[hash] = node->next; } else { prev->next = node->next; } - _pbhash_set_free_node(set, node); + if (pbhash_set_free_node_(set, node)) { + if (NULL != containing) { *containing = NULL; } + else { *element = NULL; } + } + set->elements_count--; pubnub_mutex_unlock(set->mutw); return PBHSR_OK; } @@ -226,9 +238,9 @@ pbhash_set_res pbhash_set_remove( } pbhash_set_res pbhash_set_union( - pbhash_set_t* set, - const pbhash_set_t* other_set, - pbhash_set_t** duplicates_set) + pbhash_set_t* set, + pbhash_set_t* other_set, + pbhash_set_t** duplicates_set) { pbhash_set_res rslt = PBHSR_OK; @@ -238,14 +250,15 @@ pbhash_set_res pbhash_set_union( NULL); } + pubnub_mutex_lock(set->mutw); pubnub_mutex_lock(other_set->mutw); for (int i = 0; i < other_set->bucket_length; ++i) { pbhash_set_node_t* node = other_set->buckets[i]; while (NULL != node && PBHSR_OUT_OF_MEMORY != rslt) { - const size_t hash = _pbhash_set_element_hash(set, node->value); + const size_t hash = pbhash_set_element_hash_(set, node->value); if (PBHSR_NOT_FOUND == - _pbhash_set_contains(set, node->value, hash)) { - rslt = _pbhash_set_add_node(set, node, hash, true); + pbhash_set_contains_(set, node->value, hash)) { + rslt = pbhash_set_add_node_(set, node, hash, true); } else if (NULL != duplicates_set) { pbhash_set_add(*duplicates_set, node->value, node->containing); @@ -255,6 +268,7 @@ pbhash_set_res pbhash_set_union( } } pubnub_mutex_unlock(other_set->mutw); + pubnub_mutex_unlock(set->mutw); return rslt; } @@ -270,7 +284,7 @@ pbhash_set_res pbhash_set_subtract( const pbhash_set_node_t* node = other_set->buckets[i]; while (NULL != node) { const pbhash_set_node_t* next = node->next; - rslt = pbhash_set_remove(set, (void*)node->value); + rslt = pbhash_set_remove(set, node->value, node->containing); node = next; } } @@ -279,16 +293,16 @@ pbhash_set_res pbhash_set_subtract( return rslt; } -const void* pbhash_set_element(const pbhash_set_t* set, const void* element) +const void* pbhash_set_element(pbhash_set_t* set, const void* element) { const void* matched_element = element; pubnub_mutex_lock(set->mutw); - const size_t hash = _pbhash_set_element_hash(set, element); + const size_t hash = pbhash_set_element_hash_(set, element); const pbhash_set_node_t* node = set->buckets[hash]; bool found = false; while (NULL != node) { - if (_pbhash_set_is_equal_elements(set, node->value, element)) { + if (pbhash_set_is_equal_elements_(set, node->value, element)) { if (NULL != node->containing) { matched_element = node->containing; } @@ -304,26 +318,46 @@ const void* pbhash_set_element(const pbhash_set_t* set, const void* element) return matched_element; } -bool pbhash_set_contains(const pbhash_set_t* set, const void* element) +bool pbhash_set_contains(pbhash_set_t* set, const void* element) { - pubnub_mutex_lock(set->mutw); - const size_t hash = _pbhash_set_element_hash(set, element); - const bool contains = PBHSR_NOT_FOUND != _pbhash_set_contains( - set, - element, - hash); + const size_t hash = pbhash_set_element_hash_(set, element); + const bool contains = PBHSR_NOT_FOUND != pbhash_set_contains_( + set, + element, + hash); pubnub_mutex_unlock(set->mutw); return contains; } +bool pbhash_set_is_equal(pbhash_set_t* set, pbhash_set_t* other_set) +{ + pubnub_mutex_lock(set->mutw); + pubnub_mutex_lock(other_set->mutw); + bool equal = set->content_type == other_set->content_type; + equal = equal && set->elements_count == other_set->elements_count; + if (equal) { + size_t count; + const void** elements = pbhash_set_elements(set, &count); + for (size_t i = 0; i < count; ++i) { + equal = pbhash_set_contains(other_set, elements[i]); + if (!equal) { break; } + } + free(elements); + } + pubnub_mutex_unlock(other_set->mutw); + pubnub_mutex_unlock(set->mutw); + + return equal; +} + pbhash_set_res pbhash_set_match_element( - const pbhash_set_t* set, - const void* element) + pbhash_set_t* set, + const void* element) { pubnub_mutex_lock(set->mutw); - const pbhash_set_res rslt = _pbhash_set_match_element(set, element); + const pbhash_set_res rslt = pbhash_set_match_element_(set, element); pubnub_mutex_unlock(set->mutw); return rslt; @@ -349,24 +383,22 @@ const void** pbhash_set_elements(pbhash_set_t* set, size_t* count) pubnub_mutex_lock(set->mutw); if (NULL != count) { *count = set->elements_count; } - if (0 == set->elements_count) { + const void** elements = calloc( + 0 == set->elements_count ? 1 : set->elements_count, + sizeof(void*)); + if (NULL == elements || 0 == set->elements_count) { pubnub_mutex_unlock(set->mutw); - return NULL; + return elements; } - const void** elements = calloc(set->elements_count, sizeof(void*)); int element_index = 0; - if (NULL == elements) { - pubnub_mutex_unlock(set->mutw); - return NULL; - } for (int i = 0; i < set->bucket_length; ++i) { const pbhash_set_node_t* node = set->buckets[i]; while (NULL != node) { elements[element_index++] = NULL != node->containing - ? node->containing - : node->value; + ? node->containing + : node->value; node = node->next; } } @@ -382,7 +414,7 @@ void pbhash_set_remove_all(pbhash_set_t* set) pbhash_set_node_t* node = set->buckets[i]; while (NULL != node) { pbhash_set_node_t* next = node->next; - _pbhash_set_free_node(set, node); + pbhash_set_free_node_(set, node); node = next; } } @@ -391,55 +423,65 @@ void pbhash_set_remove_all(pbhash_set_t* set) pubnub_mutex_unlock(set->mutw); } -void pbhash_set_free(pbhash_set_t* set) +void pbhash_set_free(pbhash_set_t** set) { - pbhash_set_free_with_destructor(set, set->free); + if (NULL == set || NULL == *set) { return; } + + pbhash_set_free_with_destructor(set, (*set)->free); } void pbhash_set_free_with_destructor( - pbhash_set_t* set, + pbhash_set_t** set, const pbhash_set_element_free free_fn) { - pubnub_mutex_lock(set->mutw); - if (NULL != free_fn) { set->free = free_fn; } - for (size_t i = 0; i < set->bucket_length; ++i) { - pbhash_set_node_t* node = set->buckets[i]; + if (NULL == set || NULL == *set) { return; } + + pubnub_mutex_lock((*set)->mutw); + if (NULL != free_fn) { (*set)->free = free_fn; } + for (size_t i = 0; i < (*set)->bucket_length; ++i) { + pbhash_set_node_t* node = (*set)->buckets[i]; while (NULL != node) { pbhash_set_node_t* next = node->next; - _pbhash_set_free_node(set, node); + pbhash_set_free_node_(*set, node); node = next; } } - free(set->buckets); - pubnub_mutex_unlock(set->mutw); - pubnub_mutex_destroy(set->mutw); - - free(set); + free((*set)->buckets); + pubnub_mutex_unlock((*set)->mutw); + pubnub_mutex_destroy((*set)->mutw); + free(*set); + *set = NULL; } -pbhash_set_res _pbhash_set_match_element( +pbhash_set_res pbhash_set_match_element_( const pbhash_set_t* set, const void* element) { - const size_t hash = _pbhash_set_element_hash(set, element); - return _pbhash_set_contains(set, element, hash); + const size_t hash = pbhash_set_element_hash_(set, element); + return pbhash_set_contains_(set, element, hash); } -void _pbhash_set_free_node(const pbhash_set_t* set, pbhash_set_node_t* node) +bool pbhash_set_free_node_(const pbhash_set_t* set, pbhash_set_node_t* node) { - // Ensure that node doesn't have any other references before `free`. + if (NULL == node) { return true; } + + bool removed = false; + + /** Ensure that node doesn't have any other references before `free`. */ if (0 == pbref_counter_free(node->counter)) { if (NULL != set->free) { - if (node->containing) { set->free((void*)node->containing); } - else { set->free((void*)node->value); } + if (node->containing) { set->free(node->containing); } + else { set->free(node->value); } + removed = true; } } free(node); + return removed; } -pbhash_set_res _pbhash_set_add_node( +pbhash_set_res pbhash_set_add_node_( pbhash_set_t* set, pbhash_set_node_t* node, const size_t element_hash, @@ -451,17 +493,15 @@ pbhash_set_res _pbhash_set_add_node( node_to_add = malloc(sizeof(pbhash_set_node_t)); if (NULL == node_to_add) { return PBHSR_OUT_OF_MEMORY; } - // Sharing source data with a copy. + /** Sharing source data with a copy. */ pubnub_mutex_init(node_to_add->mutw); - pubnub_mutex_lock(node->mutw); node_to_add->counter = node->counter; node_to_add->containing = node->containing; node_to_add->value = node->value; node_to_add->next = NULL; - pubnub_mutex_unlock(node->mutw); + pbref_counter_increment(node_to_add->counter); } - pbref_counter_increment(node->counter); node_to_add->next = set->buckets[element_hash]; set->buckets[element_hash] = node_to_add; set->elements_count++; @@ -469,7 +509,7 @@ pbhash_set_res _pbhash_set_add_node( return PBHSR_OK; } -size_t _pbhash_set_element_hash(const pbhash_set_t* set, const void* element) +size_t pbhash_set_element_hash_(const pbhash_set_t* set, const void* element) { size_t hash = 0; @@ -487,7 +527,7 @@ size_t _pbhash_set_element_hash(const pbhash_set_t* set, const void* element) return hash; } -pbhash_set_res _pbhash_set_contains( +pbhash_set_res pbhash_set_contains_( const pbhash_set_t* set, const void* element, const size_t element_hash) @@ -495,7 +535,7 @@ pbhash_set_res _pbhash_set_contains( const pbhash_set_node_t* node = set->buckets[element_hash]; while (NULL != node) { - if (_pbhash_set_is_equal_elements(set, node->value, element)) { + if (pbhash_set_is_equal_elements_(set, node->value, element)) { if (node->value == element) { return PBHSR_EXACT_MATCH_EXISTS; } return PBHSR_VALUE_EXISTS; } @@ -506,7 +546,7 @@ pbhash_set_res _pbhash_set_contains( return PBHSR_NOT_FOUND; } -bool _pbhash_set_is_equal_elements( +bool pbhash_set_is_equal_elements_( const pbhash_set_t* set, const void* element1, const void* element2) diff --git a/lib/pbhash_set.h b/lib/pbhash_set.h index 6493bdfd..5d7d0e02 100644 --- a/lib/pbhash_set.h +++ b/lib/pbhash_set.h @@ -1,5 +1,4 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ - #ifndef PBHASH_SET_H #define PBHASH_SET_H @@ -22,7 +21,8 @@ * * The type of data affects hash computation algorithm. */ -typedef enum { +typedef enum +{ /** * @brief Hash set used for generic data storage. * @@ -38,8 +38,9 @@ typedef enum { PBHASH_SET_CHAR_CONTENT_TYPE } pbhash_set_content_type; -// Hash set result codes. -typedef enum { +/** Hash set result codes. */ +typedef enum +{ /** * @brief Success. * @@ -85,10 +86,12 @@ typedef enum { * @brief Element `free` function prototype. * * Function which will be used when a hash set is freed using `pbhash_set_free`. + * + * @note Element destructor function will `NULL`ify provided pointer. */ typedef void (*pbhash_set_element_free)(void*); -// Hash set definition. +/** Hash set definition. */ typedef struct pbhash_set pbhash_set_t; @@ -101,7 +104,8 @@ typedef struct pbhash_set pbhash_set_t; * * \b Example: * @code - * // Allocate hash for unique strings which will call `free()` on entry remove. + * // Allocate hash for unique strings which will call `free_ptr()` on entry + * // remove. * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, free); * @endcode * @@ -118,7 +122,7 @@ typedef struct pbhash_set pbhash_set_t; * memory leak. */ pbhash_set_t* pbhash_set_alloc( - size_t length, + size_t length, pbhash_set_content_type content_type, pbhash_set_element_free free_fn); @@ -186,15 +190,15 @@ pbhash_set_t* pbhash_set_alloc( */ pbhash_set_res pbhash_set_add( pbhash_set_t* set, - const void* element, - const void* containing); + void* element, + void* containing); /** * @brief Remove an element from a hash set. * * Remove value previously added to the hash set: * @code - * pbhash_set_res result = pbhash_set_remove(set, my_value); + * pbhash_set_res result = pbhash_set_remove(set, my_value, NULL); * if (PBHSR_OK == result) { * // `my_value` has been removed from a hash set. * } else if (PBHSR_NOT_FOUND == result) { @@ -216,26 +220,33 @@ pbhash_set_res pbhash_set_add( * pbhash_set_add(set, user->name, user); * * // Remove structured data by `name`: - * if (PBHSR_OK == pbhash_set_remove(set, user->name)) { + * if (PBHSR_OK == pbhash_set_remove(set, &user->name, &user)) { * // `user` has been removed from hash `set` using same field value which * // has been used to add it. * } * @endcode * - * \b Warning: The element destruct function (if provided during set allocation) - * will be applied to the removed `element`. If the same `element` (by value and - * pointer) is passed to the `pbhash_set_add` or `pbhash_set_union` functions, - * then it will be freed and shouldn't be used after `pbhash_set_remove`. + * \b Important: If provided during set allocation, the element destruct + * function will be used for the matched element only if there are no other + * references to it (not shared with other sets after `pbhash_set_union`).
+ * \b Important: The `element` pointer will be NULLified if the memory address + * and value of the `element` match the elements that have been freed + * (no references). * - * @param set Pointer to the hash set from which new element should be - * removed. - * @param element Pointer to the element which should be removed. + * @param set Pointer to the hash set from which new element should be + * removed. + * @param element Pointer to the element which should be removed. + * @param containing Pointer to the object which contains an `element`. `NULL` + * can be used if non-structured data is stored. * @return Result of `element` removal from the hash set. * * @see pbhash_set_add * @see pbhash_set_union */ -pbhash_set_res pbhash_set_remove(pbhash_set_t* set, const void* element); +pbhash_set_res pbhash_set_remove( + pbhash_set_t* set, + void** element, + void** containing); /** * @brief Union `other hash` set entries with source set. @@ -267,10 +278,10 @@ pbhash_set_res pbhash_set_remove(pbhash_set_t* set, const void* element); * } * @endcode * - * @note Hash won't let free up (call element destructor) on elements which are - * shared with another set. Destructor will be applied only element not - * shared between sets anymore.
\b Important: a hash set won't be able - * to protect data from direct release with `free`. + * @note Hash set won't let free up (call element destructor) on elements which + * are shared with another set. Destructor will be applied only element + * not shared between sets anymore.
\b Important: a hash set won't be + * able to protect data from direct release with `free`. * * @param set Pointer to the source hash set into which should * be merged entries from `other` set. @@ -284,9 +295,9 @@ pbhash_set_res pbhash_set_remove(pbhash_set_t* set, const void* element); * @see pbhash_set_subtract */ pbhash_set_res pbhash_set_union( - pbhash_set_t* set, - const pbhash_set_t* other_set, - pbhash_set_t** duplicates_set); + pbhash_set_t* set, + pbhash_set_t* other_set, + pbhash_set_t** duplicates_set); /** * @brief Subtract `other hash` set entries from source set. @@ -323,7 +334,7 @@ pbhash_set_res pbhash_set_subtract(pbhash_set_t* set, pbhash_set_t* other_set); * value, element itself if stored as non-structured data or `NULL` in * case if `element` can't be found. */ -const void* pbhash_set_element(const pbhash_set_t* set, const void* element); +const void* pbhash_set_element(pbhash_set_t* set, const void* element); /** * @brief Check whether `element` already added to the `set`. @@ -374,7 +385,21 @@ const void* pbhash_set_element(const pbhash_set_t* set, const void* element); * * @see pbhash_set_add */ -bool pbhash_set_contains(const pbhash_set_t* set, const void* element); +bool pbhash_set_contains(pbhash_set_t* set, const void* element); + +/** + * @brief Check whether `set` content is equal to the other set content. + * + * @note Check will be done only by value (not `containing` object). + * + * @param set Pointer to the source hash set from which content will be + * compared to the other set. + * @param other_set Pointer to the hash set with values which will be used + * in comparison with the `hash`. + * @return `true` if both sets has similar content type, length and set of + * objects. + */ +bool pbhash_set_is_equal(pbhash_set_t* set, pbhash_set_t* other_set); /** * @brief Identify type of the `element` match in the `set`. @@ -407,8 +432,8 @@ bool pbhash_set_contains(const pbhash_set_t* set, const void* element); * @see pbhash_set_add */ pbhash_set_res pbhash_set_match_element( - const pbhash_set_t* set, - const void* element); + pbhash_set_t* set, + const void* element); /** * @brief Retrieve a type of data stored in hash `set`. @@ -424,8 +449,8 @@ pbhash_set_content_type pbhash_set_type(const pbhash_set_t* set); * @brief Get the number of elements in hash set. * @code * pbhash_set_t* set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, NULL); - * pbhash_set_remove(set, "hello once"); - * pbhash_set_remove(set, "hello twice"); + * pbhash_set_remove(set, "hello once", NULL); + * pbhash_set_remove(set, "hello twice", NULL); * // Output: Number of elements: 2 * printf("Number of elements: %d\n", pbhash_set_count(set)); * @endcode @@ -460,21 +485,24 @@ void pbhash_set_remove_all(pbhash_set_t* set); * element destructing function is not provided. Destructing function will * be used on `containing` object if it has been provided along with added * `element`. + * @note Function will NULLify provided array pointer. * * @param set Pointer to the hash set, which should free up resources used for * it and stored elements. */ -void pbhash_set_free(pbhash_set_t* set); +void pbhash_set_free(pbhash_set_t** set); /** * @brief Clean up resources used by `set` with custom element destructor. * + * @note Function will NULLify provided array pointer. + * * @param set Pointer to the hash set, which should free up resources used * for it and stored elements. * @param free_fn Elements `free` function. This value could be set to `NULL` * and leave memory management to the hash set user. */ void pbhash_set_free_with_destructor( - pbhash_set_t* set, + pbhash_set_t** set, pbhash_set_element_free free_fn); -#endif //PBHASH_SET_H \ No newline at end of file +#endif // #ifndef PBHASH_SET_H diff --git a/lib/pbhash_set_unit_test.c b/lib/pbhash_set_unit_test.c index 485ad50d..d86781c3 100644 --- a/lib/pbhash_set_unit_test.c +++ b/lib/pbhash_set_unit_test.c @@ -181,7 +181,7 @@ Ensure(pbhash_set, should_remove_string) pbhash_set_add(set, "hello once", NULL); pbhash_set_add(set, "hello twice", NULL); - assert_that(pbhash_set_remove(set, "hello twice"), is_equal_to(PBHSR_OK)); + assert_that(pbhash_set_remove(set, "hello twice", NULL), is_equal_to(PBHSR_OK)); assert_that(elementFreeCounter, is_equal_to(1)); } @@ -190,7 +190,7 @@ Ensure(pbhash_set, should_not_remove_string_when_not_found) set = pbhash_set_alloc(10, PBHASH_SET_CHAR_CONTENT_TYPE, freeElement); pbhash_set_add(set, "hello once", NULL); - assert_that(pbhash_set_remove(set, "hello twice"), + assert_that(pbhash_set_remove(set, "hello twice", NULL), is_equal_to(PBHSR_NOT_FOUND)); assert_that(elementFreeCounter, is_equal_to(0)); } diff --git a/lib/pbref_counter.c b/lib/pbref_counter.c index f5410a4e..1ae3c057 100644 --- a/lib/pbref_counter.c +++ b/lib/pbref_counter.c @@ -1,5 +1,8 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pbref_counter.h" + #include + #include "core/pubnub_mutex.h" @@ -7,11 +10,11 @@ // Types // ---------------------------------- -// Reference counter type definition. +/** Reference counter type definition. */ struct pbref_counter { - // Number of references to the tracked shared resource. + /** Number of references to the tracked shared resource. */ int count; - // Counter value access lock. + /** Counter value access lock. */ pubnub_mutex_t mutw; }; @@ -22,10 +25,11 @@ struct pbref_counter { pbref_counter_t* pbref_counter_alloc(void) { - pbref_counter_t* refc = calloc(1, sizeof(pbref_counter_t)); + pbref_counter_t* refc = malloc(sizeof(pbref_counter_t)); if (NULL == refc) { return NULL; } pubnub_mutex_init(refc->mutw); + refc->count = 1; return refc; } @@ -41,8 +45,9 @@ size_t pbref_counter_increment(pbref_counter_t* counter) size_t pbref_counter_decrement(pbref_counter_t* counter) { + size_t count = 0; pubnub_mutex_lock(counter->mutw); - const size_t count = --counter->count; + if (0 != counter->count) { count = --counter->count; } pubnub_mutex_unlock(counter->mutw); return count; @@ -52,7 +57,7 @@ size_t pbref_counter_free(pbref_counter_t* counter) { pubnub_mutex_lock(counter->mutw); size_t count = counter->count; - if (0 == count || (counter->count > 0 && 0 == (count = --counter->count))) { + if (count > 0 && 0 == (count = --counter->count)) { pubnub_mutex_unlock(counter->mutw); pubnub_mutex_destroy(counter->mutw); free(counter); diff --git a/lib/pbref_counter.h b/lib/pbref_counter.h index 1975fc37..2e815efd 100644 --- a/lib/pbref_counter.h +++ b/lib/pbref_counter.h @@ -1,3 +1,4 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PBREF_COUNTER_H #define PBREF_COUNTER_H @@ -28,6 +29,10 @@ typedef struct pbref_counter pbref_counter_t; /** * @brief Create a shared resource reference counter. * + * @note Usually, the reference counter allocated along with the object for + * which references should be counted and, for simplicity, will be set + * to \b 1. + * * @return Pointer to the resource reference counter object. */ pbref_counter_t* pbref_counter_alloc(void); @@ -61,4 +66,4 @@ size_t pbref_counter_decrement(pbref_counter_t* counter); * @return Current number of references. */ size_t pbref_counter_free(pbref_counter_t* counter); -#endif //PBREF_COUNTER_H \ No newline at end of file +#endif // #ifndef PBREF_COUNTER_H \ No newline at end of file diff --git a/lib/pbstrdup.c b/lib/pbstrdup.c index 89489b33..3406a846 100644 --- a/lib/pbstrdup.c +++ b/lib/pbstrdup.c @@ -1,4 +1,6 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pbstrdup.h" + #include #include diff --git a/lib/pbstrdup.h b/lib/pbstrdup.h index 3cb0be9c..9382fe36 100644 --- a/lib/pbstrdup.h +++ b/lib/pbstrdup.h @@ -1,3 +1,4 @@ +/* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #ifndef PBSTRDUP_H #define PBSTRDUP_H diff --git a/microchip_harmony/pubnub_ntf_harmony.c b/microchip_harmony/pubnub_ntf_harmony.c index 8ec947ab..253b5664 100644 --- a/microchip_harmony/pubnub_ntf_harmony.c +++ b/microchip_harmony/pubnub_ntf_harmony.c @@ -1,6 +1,11 @@ /* -*- c-file-style:"stroustrup"; indent-tabs-mode: nil -*- */ #include "pubnub_ntf_callback.h" +#if PUBNUB_USE_RETRY_CONFIGURATION +#include "core/pubnub_retry_configuration_internal.h" +#include "core/pubnub_pubsubapi.h" +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + #include "pubnub_internal.h" #include "pubnub_assert.h" #include "pbntf_trans_outcome_common.h" @@ -148,9 +153,39 @@ void pbntf_lost_socket(pubnub_t *pb, pb_socket_t socket) void pbntf_trans_outcome(pubnub_t *pb) { PBNTF_TRANS_OUTCOME_COMMON(pb); - if (pb->cb != NULL) { - pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); +#if PUBNUB_USE_RETRY_CONFIGURATION + pubnub_mutex_lock(pb->monitor); + uint16_t delay = 0; + if (NULL != pb->core.retry_configuration && + pubnub_retry_configuration_retryable_result_(pb)) { + delay = pubnub_retry_configuration_delay_(pb); + } + pubnub_mutex_unlock(pb->monitor); + + if (delay > 0) { + pubnub_mutex_lock(pb->monitor); + if (NULL == pb->core.retry_timer) + pb->core.retry_timer = pbcc_request_retry_timer_alloc(pb); + + if (NULL != pb->core.retry_timer) { + pubnub_mutex_unlock(pb->monitor); + pbcc_request_retry_timer_start(pb->core.retry_timer, delay); + } else { pubnub_mutex_unlock(pb->monitor); } + } else { + pubnub_mutex_lock(pb->monitor); + if (NULL != pb->core.retry_timer) { + pbcc_request_retry_timer_stop(pb->core.retry_timer); + pbcc_request_retry_timer_free(&pb->core.retry_timer); + } + pubnub_mutex_unlock(pb->monitor); + +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION + if (pb->cb != NULL) { + pb->cb(pb, pb->trans, pb->core.last_result, pb->user_data); + } +#if PUBNUB_USE_RETRY_CONFIGURATION } +#endif // #if PUBNUB_USE_RETRY_CONFIGURATION } diff --git a/posix/posix.mk b/posix/posix.mk index abc9cadb..8ef08873 100644 --- a/posix/posix.mk +++ b/posix/posix.mk @@ -10,6 +10,10 @@ ifndef USE_PROXY USE_PROXY = 1 endif +ifndef USE_RETRY_CONFIGURATION +USE_RETRY_CONFIGURATION = 0 +endif + ifndef USE_GZIP_COMPRESSION USE_GZIP_COMPRESSION = 1 endif @@ -22,6 +26,10 @@ ifndef USE_SUBSCRIBE_V2 USE_SUBSCRIBE_V2 = 1 endif +ifndef USE_SUBSCRIBE_EVENT_ENGINE +USE_SUBSCRIBE_EVENT_ENGINE = 0 +endif + ifndef USE_ADVANCED_HISTORY USE_ADVANCED_HISTORY = 1 endif @@ -55,6 +63,11 @@ SOURCEFILES += ../core/pubnub_proxy.c ../core/pubnub_proxy_core.c ../core/pbhttp OBJFILES += pubnub_proxy.o pubnub_proxy_core.o pbhttp_digest.o pbntlm_core.o pbntlm_packer_std.o endif +ifeq ($(USE_RETRY_CONFIGURATION), 1) +SOURCEFILES += ../core/pbcc_request_retry_timer.c ../core/pubnub_retry_configuration.c +OBJFILES += pbcc_request_retry_timer.o pubnub_retry_configuration.o +endif + ifeq ($(USE_GZIP_COMPRESSION), 1) SOURCEFILES += ../lib/miniz/miniz_tdef.c ../lib/miniz/miniz.c ../lib/pbcrc32.c ../core/pbgzip_compress.c OBJFILES += miniz_tdef.o miniz.o pbcrc32.o pbgzip_compress.o @@ -70,6 +83,11 @@ SOURCEFILES += ../core/pbcc_subscribe_v2.c ../core/pubnub_subscribe_v2.c OBJFILES += pbcc_subscribe_v2.o pubnub_subscribe_v2.o endif +ifeq ($(USE_SUBSCRIBE_EVENT_ENGINE), 1) +SOURCEFILES += ../core/pbcc_memory_utils.c ../core/pbcc_event_engine.c ../core/pbcc_subscribe_event_engine.c ../core/pbcc_subscribe_event_engine_effects.c ../core/pbcc_subscribe_event_engine_events.c ../core/pbcc_subscribe_event_engine_states.c ../core/pbcc_subscribe_event_engine_transitions.c ../core/pbcc_subscribe_event_listener.c ../core/pubnub_entities.c ../core/pubnub_subscribe_event_engine.c ../core/pubnub_subscribe_event_listener.c ../lib/pbarray.c ../lib/pbhash_set.c ../lib/pbref_counter.c ../lib/pbstrdup.c +OBJFILES += pbcc_memory_utils.o pbcc_event_engine.o pbcc_subscribe_event_engine.o pbcc_subscribe_event_engine_effects.o pbcc_subscribe_event_engine_events.o pbcc_subscribe_event_engine_states.o pbcc_subscribe_event_engine_transitions.o pbcc_subscribe_event_listener.o pubnub_entities.o pubnub_subscribe_event_engine.o pubnub_subscribe_event_listener.o pbarray.o pbhash_set.o pbref_counter.o pbstrdup.o +endif + ifeq ($(USE_ADVANCED_HISTORY), 1) SOURCEFILES += ../core/pbcc_advanced_history.c ../core/pubnub_advanced_history.c OBJFILES += pbcc_advanced_history.o pubnub_advanced_history.o @@ -106,7 +124,7 @@ OBJFILES += monotonic_clock_get_time_posix.o LDLIBS=-lrt -lpthread endif -CFLAGS =-g -Wall -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) +CFLAGS =-g -Wall -D PUBNUB_THREADSAFE -D PUBNUB_LOG_LEVEL=PUBNUB_LOG_LEVEL_WARNING -D PUBNUB_ONLY_PUBSUB_API=$(ONLY_PUBSUB_API) -D PUBNUB_PROXY_API=$(USE_PROXY) -D PUBNUB_USE_RETRY_CONFIGURATION=$(USE_RETRY_CONFIGURATION) -D PUBNUB_USE_GZIP_COMPRESSION=$(USE_GZIP_COMPRESSION) -D PUBNUB_RECEIVE_GZIP_RESPONSE=$(RECEIVE_GZIP_RESPONSE) -D PUBNUB_USE_SUBSCRIBE_V2=$(USE_SUBSCRIBE_V2) -D PUBNUB_USE_SUBSCRIBE_EVENT_ENGINE=$(USE_SUBSCRIBE_EVENT_ENGINE) -D PUBNUB_USE_OBJECTS_API=$(USE_OBJECTS_API) -D PUBNUB_USE_ACTIONS_API=$(USE_ACTIONS_API) -D PUBNUB_USE_AUTO_HEARTBEAT=$(USE_AUTO_HEARTBEAT) -D PUBNUB_USE_GRANT_TOKEN_API=$(USE_GRANT_TOKEN) -D PUBNUB_USE_REVOKE_TOKEN_API=$(USE_REVOKE_TOKEN) -D PUBNUB_USE_FETCH_HISTORY=$(USE_FETCH_HISTORY) # -g enables debugging, remove to get a smaller executable # -fsanitize-address Use AddressSanitizer @@ -187,6 +205,10 @@ subscribe_publish_callback_sample: ../core/samples/subscribe_publish_callback_sa subscribe_publish_from_callback: ../core/samples/subscribe_publish_from_callback.c pubnub_callback.a $(CC) -o $@ -D PUBNUB_CALLBACK_API $(CFLAGS) $(CFLAGS_CALLBACK) $(INCLUDES) ../core/samples/subscribe_publish_from_callback.c pubnub_callback.a $(LDLIBS) +# Subscribe Event Engine rely on callback +subscribe_event_engine_sample: ../core/samples/subscribe_event_engine_sample.c pubnub_callback.a + $(CC) -o $@ -D PUBNUB_CALLBACK_API $(CFLAGS) $(CFLAGS_CALLBACK) $(INCLUDES) ../core/samples/subscribe_event_engine_sample.c pubnub_callback.a $(LDLIBS) + publish_callback_subloop_sample: ../core/samples/publish_callback_subloop_sample.c pubnub_callback.a $(CC) -o $@ -D PUBNUB_CALLBACK_API $(CFLAGS) $(CFLAGS_CALLBACK) $(INCLUDES) ../core/samples/publish_callback_subloop_sample.c pubnub_callback.a $(LDLIBS)