/* * (c) Copyright, Real-Time Innovations, 2002-2026. * 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 reda_table_h #define reda_table_h #include "osapi/osapi_type.h" #include "reda/reda_dll.h" #include "reda/reda_epoch.h" #include "reda/reda_fastBuffer.h" #ifdef __cplusplus extern "C" { #endif /* Doxygen file & module documentation ==================================*/ /*e \file \brief Defines the REDATable */ /* ----------------------------------------------------------------- */ /*e \defgroup REDATableClass REDATable \ingroup REDAModule @brief a data structure that allows safe and efficient storage, iteration and search among fixed-size structures that are entered by the application. Each such structure is referred as a "record". The REDA model of a table is composed of three main elements: The table (REDATable) that holds records, cursors (REDACursor) that allow iteration and modification of records in the table, and weakReferences (REDAWeakReference) that allow caching a logical index to a record in the table. \image html Table_Overview_Public.jpg "Overview of REDATable" Each record consists of three areas: a key, a readOnlyArea and a readWriteArea. The format and size of these three areas is the same for all \e records in the table and is defined by providing REDAOrderedDataTypes when the table is created. REDATable requires the key to be unique accross all records in the table. The key is used to provide efficient searching, using an underlying REDAHashedSkiplist. The readOnlyArea is written when a new record is added to the table and can never be modified. The readWriteArea of a record can be modified. REDATable is optimized for use in a multi-threaded environment where multiple threads may need to iterate as well as enter or remove records. These operations are done through creating a REDACursor and binding it to a table. Once a cursor has been succesfully bound to the table, all cursor-opertations are multi-thread safe. Through a bound cursor, threads can modify the records and the table. The weakReference is a reference to a record of a table. Such a reference is "weak" because the record can be removed even if there are weak-references to it. In that case, dereferencing the weak-reference (with gotoWeakReference) will return an error. As most operations are done safely through a cursor, only three methods can be invoked on a table: REDATable_bindCursor, REDATable_cleanup and REDATable_delete. When calling these methods on the REDATable itself, the caller has the responsability to make sure the table has not been deleted. After REDATable_delete has been called, no methods can be called on that table any more; however, cursors that might still be bound to that table will be fine: they will refuse to start.. \image html Table_States.jpg "REDATable lifecycle" The life-line of a table can be illustrated in the pseudo-code below: \anchor DOXANCHOR_tableLifeline \code struct REDACursor *cursor; struct REDAWorker *worker; struct REDATable *table; struct REDAWorkerFactory *workerFactory; workerFactory = REDAWorkerFactory_new(...) worker = REDAWorkerFactory_createWorker(workerFactory, ...); // or use a pre-existing one table = REDATable_new(...); // create table REDATable_bindCursor(table, cursor, worker); // initialize cursor and bind it REDACursor_start(cursor, ...); // find a record and read it REDACursor_gotoKeyEqual(cursor, ); REDACursor_getReadOnlyArea(cursor, ...) // Find a record and modify it REDACursor_gotoKeyEqual(cursor, ); REDACursor_modifyReadWriteArea(cursor, ...) REDACursor_finishReadWriteArea(cursor, ...) // Lock the table REDACursor_lockTable(cursor, ...); // Now we can insert & remove records, etc. REDACursor_assertAndModifyReadWriteArea(cursor, ...) // Remove a record REDACursor_gotoKeyEqual(cursor, ); REDACursor_removeRecord(cursor, ...); // Indicate we are no longer using the cursor REDACursor_finish(cursor, ); // The application can keep the cursor around and call _start()/_finish() // again to access more records REDACursor_start(cursor, ...); // Access more records... REDACursor_finish(cursor, ...); // The cursor can remove the entire table as follows REDACursor_start(cursor, ...); REDACursor_lockTable(cursor, ...); REDACursor_removeTable(cursor, ...); REDACursor_finish(cursor, ...); // For cleanup, the following method must be called regularly REDATable_cleanup(table, &tableCanBeDeleted,...) // Finally, the table can be deleted if(tableCanBeDeleted) { REDATable_delete(table,...) } // At this point: methods on the table CANNOT be invoked any more // BUT the cursors bound to the table will still be safe REDACursor_start(cursor, ...) // returns FALSE as the table has been deleted \endcode @see REDACursor REDAWeakReference */ /* Need to declare these so that we don't have to expose the internal * structures */ struct REDASkiplistDescription; struct REDAExclusiveArea; struct REDAOrderedDataType; struct REDAHashDefinition; struct REDACursor; struct REDATable; struct REDAWorker; struct REDAWeakReferenceManager; /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Used to indicate that a default value should be used. Typically used as argument to functions that expect configuration parameters and hints such as number of records, hash-buckets, etc. */ #define REDA_TABLE_USE_DEFAULT_VALUE (-1) /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Used to indicate the maximum length of table name used for creating table. */ #define REDA_TABLE_NAME_MAX_LENGTH (80) /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Prototypes the function that can be registered to get notification that a table is physically removed from memory. This is the place where any resources that where loaned to the table may be freed. This function is specified in the \c tableFinalizeFnc argument to the REDATable_new function. This function will be called exactly once from the context of a thread calling REDATable_cleanup. When the function has been called, no cursor can access the table any more and the table can be deleted with REDATable_delete. @param param \b In. The \c tableFinalizeParam specified when the table was created. See REDATable_new. @param recordFinalizeParam\b In. The \c recordFinalizeParam specified when the table was created. See REDATable_new. @param tableUserData\b In. The userData that was stored in the table on creation. See REDATable_new. @param worker\b In. Reaper worker. @see REDATable_new */ typedef void (*REDATableFinalizeFunction)( void *param, void *recordFinalizeParam, void *tableUserData, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Prototypes the function that can be registered to get notification that a record is physically removed from memory. This is the place where any resources that where inside the record may be freed. Prior to this function being called it may be possible for cursors to still access a removed record if they were positioned at the record or if there was a race condition. After this function is called, it is guaranteed that nobody can access the record and thus it is only now that we may finalize and release resources associated with the record. @param param\b In. The \c recordFinalizeParam specified when the table was created. See REDATable_new. @param key\b In. The record's key @param readOnlyArea\b In. The record's readOnlyArea @param readWriteArea\b In. The record's readWriteArea @param worker\b In. Reaper worker. @mtsafety Safe. It is guaranteed that no other worker is using the record now and in future. You may destroy at will. @see REDATable_new */ typedef void (*REDATableRecordFinalizeFunction)( void *param, void *key, void *readOnlyArea, void *readWriteArea, struct REDAWorker *worker); /* ===================================================================== */ /*e \defgroup REDATablePropertyClass REDATableProperty \ingroup REDATableClass @brief A convenient way to group properties of a table Note that this is not actually used in */ struct REDATableProperty { struct REDAFastBufferPoolGrowthProperty growth; int hashBucketCount; }; /* --------------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Creates a REDATable. REDATable is implemented using a REDAHashedSkiplist for management of the records. See REDAHahsedSkiplist for more information on the performance and memory usage implications of the hashing. @param tableName \b In. Name of table for debugging purpose. Can be NULL. @param keyType \b In. Defines the type of keys used to identify the records in the the table. Must have been initialized with REDAOrderedDataType_define. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. @param readOnlyAreaType \b In. Defines the type of data in the read-only area of the records. Must have been initialized with REDAOrderedDataType_define or can be NULL in which case the Table does not have records with a readOnlyArea. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. @param readWriteAreaType \b In. Defines the type of data in the read-write area of the records. Must have been initialized with REDAOrderedDataType_define OR can be NULL in which case the Table does not have records with a readWriteArea. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. @param hashDefinition \b In. Defines the hashing that will be used on the key. See the documentation for REDAHashedSkiplist and REDAHash for more information. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. In case this parameter is NULL, no hashing will be used. @param skiplistDesc \b In. Describes the properties of the underlying hashedSkiplist. This description is NOT copied into the table; it must remain in existence with the Table. @param weakRefManager \b In. a manager for the weak references. Must have been allocated with REDAWeakReferenceManager_new. The WeakReferenceManager must remain in existence as long as the Table exists or weak-references to records of the Table might still be lying around in the application. The WeakReferenceManager typically will be the last object to be destroyed in an application using Tables. @param tableEA \b In. The table-level exclusive area. It is necessary that a worker can enter the adminEA of the table, the adminEA of the weak-reference manager and the record EA's from within the tableEA. @param adminEA \b In. The administrative EA: the table will use this EA for short internal protection. This EA must be such that it can be entered from within any other EA: from within the table-EA and/or record-EA's. @param tableFinalizeFnc \b In. A function to be notified that the table is physically removed from memory and nobody is able to access it anymore. See REDATableFinalizeFunction for details. This parameter may be NULL if no finalization function is desired. @param tableFinalizeParam \b In. Parameter to pass to tableFinalizeFnc. @param recordFinalizeFnc \b In. Used to notify application when a record is about the be physically removed and nobody can access its contents. See REDARecordFinalizeFunction for details. May be NULL to indicate there is no notification is needed. @param recordFinalizeParam \b In. Additional parameter to pass to recordFinalizeFnc. @param growthPropertyIn. This is like the table's property. Since there are no other meaningful table property, we just use this "parent" class directly. @param tableUserData \b In. Table provides storage space for one pointer, which can be retrieved safely through REDACursor_getTableUserData. The table only provides safe read-only access; this pointer or the structure it points cannot be modified without additional protection on top of the Table. @mtsafety Safe. @see REDACursor REDAWeakReferenceManager_new REDATable_cleanup REDATable_delete */ extern REDADllExport struct REDATable *REDATable_new( const char *tableName, const struct REDAOrderedDataType *keyType, const struct REDAOrderedDataType *readOnlyAreaType, const struct REDAOrderedDataType *readWriteAreaType, const struct REDAHashDefinition *hashDefinition, const struct REDASkiplistDescription *skiplistDesc, const struct REDAFastBufferPoolGrowthProperty *growthPropertyIn, struct REDAWeakReferenceManager *weakRefManager, struct REDAExclusiveArea *tableEA, struct REDAExclusiveArea *adminEA, REDATableFinalizeFunction tableFinalizeFnc, void *tableFinalizeParam, REDATableRecordFinalizeFunction recordFinalizeFnc, void *recordFinalizeParam, void *tableUserData); /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Get the read-only growth property of the table @mtsafety Safe. */ extern REDADllExport const struct REDAFastBufferPoolGrowthProperty * REDATable_getGrowthProperty(struct REDATable *me); /*e \ingroup REDATableClass @brief Get the read-only table name. @mtsafety Safe. */ extern REDADllExport const char *REDATable_getName(const struct REDATable *me); /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Deletes a REDATable. It is the responsability of the application that uses tables to ensure that after the call to REDATable_delete is made, no other methods (bindCursor, cleanup or delete) are invoked on this table. When the table is deleted when the precondition (see below) is met; the implementation guarantees that Cursors still bound to the Table will not be allowed to use the deleted Table and WeakReferences refering to records of the deleted Table are invalidated. @pre A valid (undeleted) Table; the table has been removed and REDATable_cleanup() has been called as many times as required until it sets the parameter tableCanBeDeleted to TRUE. @post The table is deleted. @param table \b In. The table to delete. @mtsafety Unsafe. Only one thread can call this method. @see REDACursor REDATable_new REDATable_cleanup */ extern REDADllExport void REDATable_delete(struct REDATable *table); /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Bind a new cursor to a specific table and worker. This function initializes the cursor and binds it to a table and worker. Cursors must be bound prior to be in used on tables. This function must be called before calling any other functions on the cursor, specifically REDACursor_start and any other functions that attempt to access a table. Refer to the \ref DOXANCHOR_CursorStates "cursor state diagram" for the role of this function within the cursor lifeline. This operation will fail if the table has been removed or if pre-condition checking is enabled it is detected that they are not met @pre cursor must point to enough memory to hold a (struct REDACursor). Cursor must be in state "unbound"; Table must be a valid table. It can be removed but it cannot have benn deteted (i.e. REDATable_delete should have not been called on that table. @post on success (RTI_TRUE is returned): the cursor is bound to the table and worker and is in state "inactive". on failure (RTI_FALSE is returned) the cursor is left in the state UNBOUND. @param table \b In. The table to bind the cursor to @param cursor \b In. The cursor to bind. Cannot be NULL @param worker \b In. The worker under which context the cursor will operate @mtsafety unsafe. This call can only be made by one thread (worker) on any given cursor. Once bound then only that thread/worker can use the cursor. \code struct REDACursor cursor; struct *REDAWorker worker; struct *REDATable table; worker = REDAWorker_new(...); // or use a pre-existing one table = REDATable_new(...); // create table if(!REDATable_bindCursor(table, &cursor, worker)) { // error-handling } // Now we can start and use the cursor: REDACursor_start(&cursor,...); // declare intent of using the cursor \endcode @mtsafety Safe. As long as the table is undeleted, workers can concurrently bind to the table without affecting each other. @see REDACursor_start REDACursor_unbind */ extern REDADllExport RTIBool REDATable_bindCursor( struct REDATable *table, struct REDACursor *cursor, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDATableClass @brief Cleaning up will (if possible) delete the structures (records or the entire table) that have been removed through Cursors. The registered record- and table-finalization methods will also be called from this method right before the affected structures are removed from memory. The typical usage is to call this function at some period to "delete" the records that have been removed until the output parameter tableCanBeDeleted is TRUE and then, once we are sure the table will no longer be accessed, call Table_delete. This operation can be called in any of the table states: If the table is in the state Alive, then it will just delete the removed records that are no longer accessible by the bound cursors. If the table is in the state Removed, in addition to deleting the removed records that are innaccessible it will check whether the table can be removed and transition to the "CanBeDeleted" state. If the table is in the "CanBeDeleted" state, the operation will be a NOOP. It will however return RTI_TRUE and tableCanBeDeleted=TRUE. @pre A table in the Alive, Removed, or CanBeDeleted state. The table cannot have been deleted (by calling Table_delete) @invariant The logical contents of the table is not modified @param table \b in. A valid table to clean up. @param tableCanBeDeleted \b out. An optional parameter; if RTI_TRUE is returned, the table is in the "CanBeDeleted" state and the operation REDATable_delete can be called to effectively delete the table without endangering the safety of any Cursors bound to the table. @param worker \b in. The worker that calls this function. @return RTI_TRUE on success, RTI_FALSE on failure @mtsafety Safe. As long as the table is undeleted, any worker can cleanup the table. @see REDACursor REDATable_delete */ extern REDADllExport RTIBool REDATable_cleanup( struct REDATable *table, RTIBool *tableCanBeDeleted, struct REDAWorker *worker); /*e \defgroupREDATablePropertyClass * @brief A convenient way to form a table property from individual parameters * * @note This functions is implemented as a macro */ void REDATableProperty_init( struct REDATableProperty *me, int initial, int maximal, int growthIncrement, int hashBucketCount); #ifdef __cplusplus } /* extern "C" */ #endif /* pick up hidden performance boosting macros and optimizations */ #include "reda/reda_table_impl.h" #endif /* reda_table_h */