/* * (c) Copyright, Real-Time Innovations, 2019-2024. * All rights reserved. * * No duplications, whole or partial, manual or electronic, may be made * without express written permission. Any such copies, or * revisions thereof, must display this notice unaltered. * This code contains trade secrets of Real-Time Innovations, Inc. */ #ifndef osapi_context_h #define osapi_context_h #include "log/log_common.h" #include "osapi/osapi_dll.h" #include "osapi/osapi_type.h" #include "osapi/osapi_thread.h" #include "osapi/osapi_heap.h" #include "osapi/osapi_utility.h" #ifdef __cplusplus extern "C" { #endif /** @internal \ingroup RTIOsapiContext * Max size for the representation of the context into a string. */ #define RTI_OSAPI_CONTEXT_STRING_SIZE_MAX 512 /** @internal \ingroup RTIOsapiContext * Default max size used to initialize context stack. */ #define RTI_OSAPI_CONTEXT_STACK_SIZE_DEFAULT 32 /** @internal \ingroup RTIOsapiActivityContext * The Index of the activity and heap context specializations. */ #define RTI_OSAPI_ACTIVITY_CONTEXT_INDEX 0 #define RTI_OSAPI_HEAP_CONTEXT_INDEX 1 /** @internal \ingroup RTIOsapiContext * Entry of the context. */ struct RTIOsapiContextEntry { void *entry; /* If the context is cached in expansion, expansionEnd points to end */ char *expansionEnd; /* * Represent if an entry can be skipped from the string representation of * the context stack. * * We need to cache if the entry is skipped to optimize the generation of * the activity context string: we need to check previous entries skipped * state to make decisions about the new entries we add. * * For example in the activity context the output will change (we will skip * entries) based on the order and content of the entries: * - For example similar activities and different resources: * `R1|A1|R2|A1` -> `R1|A1|R2|A1`. The activities are similar but cannot * be skipped because they belong to different resources. * - For example similar activities and similar resources: * `R1|A1|R1|A1` -> `R1|A1`. The activities and the resources are exactly * the same, so both of them are skipped. */ RTIBool skipped; }; /** @internal \ingroup RTIOsapiContext * The concept of context is a queue of entries associated with a thread. * Those entries can be pushed and popped from the list of entries. * * The context can be instantiate for different specialization: * - Activity context: it will describe the activity that a resource is doing. * - Heap context: it will represent: * - If the allocation is from the pool and in that case the buffer size * and the buffer count. * - The topic and the function name. */ struct RTIOsapiContext { /* List of entries */ struct RTIOsapiContextEntry *entries; RTI_UINT32 maxEntryCount; /* Position on the list */ RTI_UINT32 top; /* Used to cache the context as a string */ char *expansion; /* A bitmap used to store the categories associated with the context */ RTILogCategoryMask category; }; /** @internal \ingroup RTIOsapiContext * Number of type of contexts. * Instantiate different specializations of the context: * - Activity context: it will describe the activity that a resource is doing. * - Heap context: it will represent: If the allocation is from the pool and in * that case the buffer size and the buffer count. The topic and the function *name. *- In the future, we can have more potential context families for instance * network capture context. */ #define RTI_OSAPI_NUMBER_CONTEXTS 3 /** @internal \ingroup RTIOsapiContextSupport * Each node contains the contexts of the thread. */ struct RTIOsapiContextSupportNode { struct RTIOsapiInlineListNode node; struct RTIOsapiContext *contexts[RTI_OSAPI_NUMBER_CONTEXTS]; }; /* ========================================= */ /* ContextSupport */ /* ========================================= */ /* * The Context feature is enabled by calling RTIOsapiContextSupport_enable() and * it is disabled by calling RTIOsapiContextSupport_disable(). * * The lifecycle of the context support is different depending on if using heap * monitoring. * * - If using heap monitoring: * - The Context support is enabled in RTIOsapiHeap_enableMonitoring() * - The Context support is disabled in RTIOsapiHeap_disableMonitoring() * - In case the “Participant Globals” are initialized or finalized, * they will not affect the context support lifecycle. * - If not using heap monitoring: * - The Context support is enabled in * DDS_DomainParticipantGlobals_initializeI() * - The Context support is disabled in * DDS_DomainParticipantGlobals_finalizeI() * - In case the “Participant Globals” are initialized or finalized several * times. The context support will be too. * - Heap Monitoring can not interfere with it because * RTIOsapiHeap_enableMonitoring() should be the first thing to be * done. */ /** @internal \ingroup RTIOsapiContextSupport * @brief Enable the Context feature. * Create the tssFactory, tssKey, tssList and mutex. * It should be called from a single thread. * It should be called before using any functionality of the context. * It is called in: * - DDS_DomainParticipantGlobals_initializeI * - RTIOsapiHeap_enableMonitoring * Increment RTIOsapiContextSupport_g_tssInitializedRefCount, it is usd to make * sure we disable the context support when the refCount is 1. * @param tssFactory \b In. The TSS factory. * In case the tssFactory is null, create a new one. * * @return True if the feature was enabled, False otherwise. */ extern RTIOsapiDllExport RTIBool RTIOsapiContextSupport_enable(RTIOsapiThreadTssFactory *tssFactory); /** @internal \ingroup RTIOsapiContextSupport * @brief Disable the Context feature. * Remove the tssKey, mutex and all the contexts nodes of all the User * threads. * It should be called from a single thread. * Any functionality of the context can not be used after calling this * method. * Decrement RTIOsapiContextSupport_g_tssInitializedRefCount, it is usd to make * sure we disable the context support when the refCount is 1. * It is called in: * - DDS_DomainParticipantGlobals_finalizeI * - RTIOsapiHeap_disableMonitoring */ extern RTIOsapiDllExport void RTIOsapiContextSupport_disable(void); /** @internal \ingroup RTIOsapiContextSupport * @brief Create a specific context and associate with the TSS of the thread. * - It should be called per thread. * - It should be called after RTIOsapiContextSupport_enable. * It is called in: * - DDS_DomainParticipantGlobals_create_worker_per_thread_with_nameI * - RTIOsapiThreadChild_onSpawned * - RTIOsapiThreadFactory_onSpawned * * @param nodeOut \b Out. The node that we will assert for the * thread. Can be NULL. * @param contextIndex \b In. The index of the context. * @param addContextTssToGlobalList \b In. If true we add the context thread * specific storage to a global list. * @param maxEntryCount \b In. The max size of the list of entries. * @param allowContextExpansion \b In. If true allocate the member field to * RTI_OSAPI_CONTEXT_STRING_SIZE_MAX. * * @return True if the context was initalized, False otherwise. */ extern RTIOsapiDllExport RTIBool RTIOsapiContextSupport_assertContextTss( struct RTIOsapiContextSupportNode **nodeOut, RTI_UINT32 contextIndex, RTIBool addContextTssToGlobalList, RTI_UINT32 maxEntryCount, RTIBool allowContextExpansion); /** @internal \ingroup RTIOsapiContextSupport * @brief This function is only used to finalize the contextTss and its * associated RTIOsapiContextSupportNode for non-RTI threads. * Specifically, it is called from * DDS_DomainParticipantFactory_unregister_thread. If * DDS_DomainParticipantFactory_unregister_thread is not called by the user, the * contextTss and associated RTIOsapiContextSupportNode will be removed in * RTIOsapiContextSupport_disable() during RTIOsapiContextSupport_g_tssList * destruction at the end of DDS application lifecycle. * * For RTI threads, this function cannot be used. Instead, * the contextTss and RTIOsapiContextSupportNode are removed by a call to * RTIOsapiContextSupport_finalizeNode done by the thread itself before * finishing. */ extern RTIOsapiDllExport void RTIOsapiContextSupport_finalizeContextTss(void); /** @internal \ingroup RTIOsapiContext * @brief Return the context, of the caller thread, specified by the Id. * @param contextIndex \b In. The index of the context. * * @return the context specified by the Id. */ extern RTIOsapiDllExport struct RTIOsapiContext * RTIOsapiContextSupport_getContext(RTI_UINT32 contextIndex); /* ========================================= */ /* Context */ /* ========================================= */ /* * For the enter methods: * In case the entries do not fit in the list of entries: * - The value context->top will be incremented as many elements the user * expected to enter. * - The entries will not be saved in the queue because there is not space * for them. * We do this in order to keep the number of entries the user has entered. * So, the value of context->top may be greater than context->maxEntryCount. * * For the leave methods: * While the value of context->top is greater than context->maxEntryCount: * - The value of context->top will be decremented as many entries were * expected to be left. * Once the context->top is smaller or equal than context->maxEntryCount: * - The elements are removed from the list. * * By doing that we will solve the following scenario: * - Initial context state before calling a function: top = 2, maxEntryCount = 4 * - Start a function: * - Enter 3 entries into the context. * - As the number of entries to enter plus top is greater than * maxEntryCount. The entries will not be added into the context. * However top will be incremented as many entries are expected to be * entered. * - Context state: top = 5, maxEntryCount = 4 * - Leave 3 entries from the context. * - Decrement top, 3 elements. * - Context state: top = 2, maxEntryCount = 4 * - End the function. Now the state of the context will be the same as when * the function began. * * By keeping context->top with the expected value will ensure our developer * finishes a function with the same value of the context as it began. */ /* * Infinite loops - circular dependencies * As we have mentioned already the context will be used in heap monitoring and * logging. * So, it is necessary to set up a contract/rule between the context and those * features. * - Logging: * - The context feature can log messages. It is safe because nothing below * logging is going to enter/leave entries in the context. So, there are * not possible infinite loops. * - Advanced logging (advlog.1.0) is using the context to generate the * string using RTIOsapiActivityContext_getString(). * - It is safe to log in that function because logging is already * protected from infinite loops by ADVLOGLogger_blockThreadLogging(). * - Heap monitoring: * - In the context we cannot use our allocation function * (RTIOsapiHeap_allocate…()) because we cannot add allocation statistics * data to the heap monitoring. * - Then, in the context feature we use malloc instead. */ /** @internal \ingroup RTIOsapiContext * @brief Enter an entry to the context specified by the Id. * If the context is already full, the entry will not be added. * The entry memory is owned by the caller function. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. * @param entry \b In. The entry to add the context. */ extern RTIOsapiDllExport void RTIOsapiContext_enter( struct RTIOsapiContext *self, RTI_UINT32 contextIndex, void *entry); /** @internal \ingroup RTIOsapiContext * @brief Enter two entries to the context specified by the Id. * If the context is already full, the entries will not be added. * The entries memory is owned by the caller function. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. * @param entry1 \b In. The entry1 to add to the context. * @param entry2 \b In. The entry1 to add to the context. */ extern RTIOsapiDllExport void RTIOsapiContext_enterPair( struct RTIOsapiContext *self, RTI_UINT32 contextIndex, void *entry1, void *entry2); /** @internal \ingroup RTIOsapiContext * @brief Leave the last entry of the context specified by the Id. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. */ extern RTIOsapiDllExport void RTIOsapiContext_leave( struct RTIOsapiContext *self, RTI_UINT32 contextIndex); /** @internal \ingroup RTIOsapiContext * @brief Leave the last N entries of the context specified by the Id. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. * @param groupSize \b In. Number of entries to remove from the list. */ extern RTIOsapiDllExport void RTIOsapiContext_leaveGroup( struct RTIOsapiContext *self, RTI_UINT32 contextIndex, RTI_UINT32 groupSize); /** @internal \ingroup RTIOsapiContext * @brief Return the number of entries of the context specified by the Id. * * @param contextIndex \b In. The index of the context. * * @return number of entries of the context specified by the Id. */ extern RTIOsapiDllExport RTI_UINT32 RTIOsapiContext_getDepth(RTI_UINT32 contextIndex); /* ========================================= */ /* Category of the Context */ /* ========================================= */ /* * The category is used to store the current category of the codepath * that we are in. This is used to enable filtering of log messages by * category and labelling log messages with the current category. * * When entering a category codepath, you first need to set the * category. There are three methods that allow you to do this: * RTIOsapiActivityContext_setCategory: * Overwrites the existing category * RTIOsapiActivityContext_addCategory: * Adds the new category to the existing category (but does not * overwrite any bits that have been set to 1). * RTIOsapiActivityContext_addAndCacheCategory: * Adds the new category to the existing category and stores the * existing category. * * It is recommended to usually use RTIOsapiActivityContext_addAndCacheCategory * when setting the category when entering a codepath so that you are able to * restore the category to its previous value when you are leaving the * codepath. You can then call RTIOsapiActivityContext_setCategory to set the * category to its previous value. You should always restore the category * before returning from the function in which you add the category, otherwise * the category will still be set even though we will no longer be in the * correct codepath for that category. * * There is also a method RTIOsapiActivityContext_resetCategory which will * clear the bits in the context category that are set to 1 in the input * category. This can also be used to reset the category after setting it. * * RTIOspaiActivityContext_getCategory can be used to get the current value * of the context's category mask. * * The following shows an example of setting the category to DISCOVERY, * logging messages, and then resetting the category: * * RTICategoryMask cachedCategory = RTI_LOG_CATEGORY_NONE; * RTIOsapiActivityContext_addAndCacheCategory( * &cachedCategory, * RTI_LOG_CATEGORY_DISCOVERY_BIT); * DDSLogger_log( * RTI_LOG_BIT_LOCAL, * &RTI_LOG_EMPTY_TEMPLATE, * "This is a log message in the discovery codepath!\n"); * RTIOsapiActivityContext_setCategory(cachedCategory); * */ /** @internal \ingroup RTIOsapiContext * @brief Get the current category mask of the context. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param category \b Out. Pointer to location where we will store category. * @param contextIndex \b In. The index of the context. */ extern RTIOsapiDllExport void RTIOsapiContext_getCategory( struct RTIOsapiContext *self, RTILogCategoryMask *category, RTI_UINT32 contextIndex); /** @internal \ingroup RTIOsapiContext * @brief Set the category mask of the context to the category mask passed in. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. * * @param category \b In. The category mask to set to. * * @return True if the category mask is set successfully. False otherwise. * * This function should always be called before a "return" statement when * using RTIOsapiActivityContext_addAndCacheCategory; */ extern RTIOsapiDllExport void RTIOsapiContext_setCategory( struct RTIOsapiContext *self, RTI_UINT32 contextIndex, RTILogCategoryMask category); /** @internal \ingroup RTIOsapiContext * @brief Add the passed category to the existing category mask of the * context. This will not clear other category masks if they are already * set, it will just add the category mask passed in. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. * * @param category \b In. The category mask to add. * * @return True if the category mask is set successfully. False otherwise. */ extern RTIOsapiDllExport void RTIOsapiContext_addCategory( struct RTIOsapiContext *self, RTI_UINT32 contextIndex, RTILogCategoryMask category); /** @internal \ingroup RTIOsapiContext * @brief * * @param contextIndex \b In. The index of the context. * * @param category \b In. The category mask to add. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param cachedCategory \b Out. A pointer to a variable where we will cache * a mask of the bits to be restored upon a call to resetCategory. This * mask will be set to the current value of * self->_activityContext->category; * @param contextIndex \b In. The index of the context. * @param category \b In. The category mask to add. * * @return True if the category mask is set successfully. False otherwise. * * When using this function, there should not be a "return" statement * prior to calling RTIOsapiContext_setCategory to reset the category. */ extern RTIOsapiDllExport void RTIOsapiContext_addAndCacheCategory( struct RTIOsapiContext *self, RTILogCategoryMask *cachedCategory, RTI_UINT32 contextIndex, RTILogCategoryMask category); /** @internal \ingroup RTIOsapiContext * @brief Reset the category mask of a given category to all 0s. * * @param self \b In. The context itself. If it is NULL, it will be get * from the TSS. * @param contextIndex \b In. The index of the context. * * @param category \b In. The category mask to clear. * * @return True if the category mask is reset successfully. False otherwise. */ extern RTIOsapiDllExport void RTIOsapiContext_resetCategory( struct RTIOsapiContext *self, RTI_UINT32 contextIndex, RTILogCategoryMask category); /* ========================================= */ /* ContextScope */ /* ========================================= */ /* * We define "scope" as a block of code which: * - Starts with a RTIOsapiContextScope_begin call. * This macro opens the block. * After calling begin(), you must initialize all the stack variables needed to * support entering/leaving contexts within the block. * - Ends with an RTIOsapiContextScope_end call. * Before ending a scope, you must leave any remaining entered entries. * This macro closes the block. * * The general scope does not provide significant functionality, * it just opens and closes the brackets. It is just conceptual. * The upper layer contexts are going to rely on the concept of scope. * * The context scope has two rules: * - It has to be robust to not segmentation fault nor array overflow. * - The context should have the same status before calling "begin" and after * calling "end". */ /** @internal \ingroup RTIOsapiContext * @brief This macro opens the block. After calling begin(), you must initialize * all the stack variables needed to support entering/leaving contexts within * the block. * * Note: make sure to not add ';' after a call to this macro. */ extern RTIOsapiDllExport void RTIOsapiContextScope_begin(void); /** @internal \ingroup RTIOsapiContext * @brief Upon ending a scope, you must leave any remaining entered entries. * Close the block. */ extern RTIOsapiDllExport void RTIOsapiContextScope_end(void); #ifdef __cplusplus } /* extern "C" */ #endif #include "osapi/osapi_context_impl.h" #endif /* osapi_context_h */