Named memory is memory allocated from the database server shared memory, just as user memory. You can, however, assign a name to a block of named memory and then access this memory block by name and memory duration. The database server stores the name and its corresponding address internally. By contrast, user memory is always accessed by its address.
The disadvantage of user memory is that the database server deallocates PER_COMMAND, PER_STMT_EXEC, PER_STMT_PREP, and PER_STATEMENT memory after the command or statement completes. Because a UDR might execute many times for a particular SQL statement (once for each row processed), you might want to retain the memory pointer across all calls to the same UDR.
To save memory across invocations of a UDR, you can perform one of the following tasks:
For more information, see Saving a User State.
The advantage of named memory is that it is global within the memory duration it was allocated. Therefore, it can be accessed by many UDRs that execute in the context of many queries, or even by more than one session. Named memory is useful as global memory for caching data across UDRs or for sharing memory between UDRs executing in the context of many SQL statements.
Possible uses for named memory follow:
The DataBlade API provides the memory-management functions to dynamically allocate named memory in a C UDR. These functions return a name of the named-memory block and subsequent operations are performed on that name and memory duration. Table 101 shows the memory-management functions that the DataBlade API provides for memory operations on named memory.
The minmprot.h header file defines the functions and data type structures of the named-memory-management functions. The minmmem.h header file automatically includes the minmprot.h header file. However, the mi.h header file does not automatically includes minmmem.h. To access the named-memory-management functions, you must include minmmem.h in any DataBlade API routine that calls these functions.
The following table summarizes the memory operations for named memory.
Memory Duration | Memory Operation | Function Name |
---|---|---|
Specified memory duration | Constructor | mi_named_alloc( ),
mi_named_zalloc( ) |
Destructor | mi_named_free( ) |
To handle dynamic memory allocation of named memory, use one of the following DataBlade API memory-management functions.
These named-memory-allocation functions allocate a block of named memory of a specified size and a specified memory duration. You can use both regular and advanced memory durations for named memory. Usually, named-memory allocations are at memory durations longer than PER_COMMAND. With PER_ROUTINE and PER_COMMAND memory durations, you can use the MI_FPARAM structure to store information. You must ensure that the memory duration is sufficiently long that all UDRs that need to access it can access it.
If the allocation of the named-memory block is successful, these functions store a pointer to the allocated block in their mem_ptr argument. The UDR that allocated the named-memory block can access this named memory through this address. However, this address is deallocated when the routine invocation completes. Other UDRs must use the block name to access the named-memory block. For more information, see Obtaining a Block of Allocated Named Memory.
These DataBlade API memory-management functions work correctly with the transaction management and memory reclamation of the database server. They provide the same advantages as the user-memory-management functions (see Allocating User Memory (Server)). In addition, they provide the advantage that the named-memory block can be accessed by a name, which facilitates access to the memory across UDRs.
The benefit of named memory is that several UDRs can access it. Therefore, UDRs can cache data for sharing between UDRs executing in different contexts. To use named memory, UDRs that need to access it must take the following steps:
This named-memory block must have a name that will be known to all UDRs that need to access the block. It must also have a memory duration that is sufficient for the required lifetime of the cached data. The UDR that allocates the named memory can access the data from the address that mi_named_alloc( ) or mi_named_zalloc( ) returns. However, once this UDR completes, the local copy of this address is deallocated.
The mi_named_get( ) function returns the address of the named-memory block. The UDR can use this address to access the desired data within the named memory.
For example, suppose a UDR named initialize( ) allocates a named-memory block named cache_blk with the following mi_named_alloc( ) call:
mi_integer *blk_ptr; mi_integer status; ... status = mi_named_alloc(sizeof(mi_integer), "cache_blk", PER_STMT_EXEC, &blk_ptr); switch( status ) { case MI_ERROR: mi_db_error_raise(NULL, MI_EXCEPTION, "mi_named_alloc for cache_blk failed."); break; case MI_NAME_ALREADY_EXISTS: break; case MI_OK: *blk_ptr = 0; break; default: mi_db_error_raise(NULL, MI_EXCEPTION, "Invalid mi_named_alloc status for cache_blk"); }
If another UDR, for example, some_task( ), needs to access the integer in cache_blk, it can use the mi_named_get( ) function, as the following code fragment shows:
mi_integer *blk_ptr; mi_integer status; ... status = mi_named_get("cache_blk", PER_STMT_EXEC, &blk_ptr); switch( status ) { case MI_ERROR: mi_db_error_raise(NULL, MI_EXCEPTION, "mi_named_get for cache_blk failed."); break; case MI_NO_SUCH_NAME: /* maybe need to call mi_named_alloc( ) here */ ... break; case MI_OK: if ( *blk_ptr > 0 ) *blk_ptr++; break; default: mi_db_error_raise(NULL, MI_EXCEPTION, "Invalid mi_named_alloc status for cache_blk"); }
If some_task( ) successfully obtains the address of the cache_blk named-memory block (status is MI_OK), it increments the cached integer.
By default, the database server runs UDRs concurrently. A UDR that uses data it shares with other UDRs or with multiple instances of the same routine must implement concurrency control on its data. When the named-memory functions mi_named_alloc( ), mi_named_zalloc( ), and mi_named_get( ) return the address of a named-memory block, they do not request a lock on this named memory. It is the responsibility of your UDRs or DataBlade to manage concurrency issues on the named-memory block.
The greater the memory duration that is associated with the named memory, the more likely that you must manage concurrency of that memory. If the named memory is never updated (it is read-only), there are no concurrency problems. However, if any UDR updates the named memory that has a duration of PER_COMMAND or greater, there are concurrency issues just like there are for any global variable that gets updated.
For example, suppose the function myfunc( ) allocates a named-memory block named named_mem1. The memory duration of named_mem1 determines possible concurrency issues when myfunc( ) is called in the following query:
SELECT * FROM my_table WHERE myfunc(column1) = 1 OR myfunc(column2) = 2
The following table shows possible concurrency issues of named_mem1.
Named-Memory Allocation | Concurrency Issues? |
---|---|
mi_named_alloc(2048,
"named_mem1",
PER_COMMAND, nmem1_ptr) |
Yes
Each invocation of myfunc( ) in the query gets it own private instance of named_mem1, which expires when the UDR completes, but there might be multiple threads running in a subquery that share the same PER_COMMAND pool. If PER_COMMAND memory is cached in the MI_FPARAM user data, however, there are no concurrency issues because each thread has its own MI_FPARAM structure. Unless you need memory to be shared between threads, this is the preferable alternative for PER_COMMAND. |
mi_named_alloc(2048,
"named_mem1",
PER_SESSION, nmem1_ptr) |
Yes
Each invocation of myfunc( ) in the same query accesses the same named_mem1. This memory does not get deallocated until the session closes. |
mi_named_alloc(2048,
"named_mem1",
PER_SYSTEM, nmem1_ptr) |
Yes
Every invocation of myfunc( ) in every SQL statement accesses the same named_mem1. This memory does not get deallocated until the database server shuts down. |
To handle concurrency problems, use the following DataBlade API memory-locking functions in UDRs that update named memory.
The following guidelines are recommended for handling concurrency problems:
After the named-memory block is locked, you can guarantee that all accesses will obtain a consistent read.
The DataBlade API locking interface is intended to be used in a tightly coupled, fast section of code that protects a critical region during modification. Code should follow these steps:
Suppose you have a user-defined structure named MyInfo with the following declaration:
typedef struct
{
mi_integer is_initialized;
... other members here....
} MyInfo;
The following sample code allocates a named-memory block named MyInfo_memory for the MyInfo structure. It then locks a critical section of code before updating the is_initialized integer in this named-memory block.
MyInfo *GetMyInfo( ) { mi_string *memname="MyInfo_memory", msgbuf[80]; mi_integer status; MyInfo *my_info = NULL; /* Allocate the named-memory block. If it has already been * allocated, obtain a pointer to this block. */ status = mi_named_zalloc(sizeof(MyInfo), memname, PER_SESSION, (void **)&myinfo); if( status == MI_NAME_ALREADY_EXISTS ) status = mi_named_get(memname, PER_SESSION, (void **)&my_info); switch(status) { case MI_ERROR: mi_db_error_raise(NULL, MI_EXCEPTION, "GetMyInfo: mi_named_get or mi_named_zalloc failed."); return (MyInfo *)NULL; break; /* Have a pointer to the named_memory block. */ case MI_OK: break; case MI_NO_SUCH_NAME: mi_db_error_raise(NULL, MI_EXCEPTION, "GetMyInfo: no name after good get"); return (MyInfo *)NULL; break; default: sprintf(msgbuf, "GetMyInfo: mi_named memory case %d.", status); mi_db_error_raise(NULL, MI_EXCEPTION, msgbuf); return (MyInfo *)NULL; break; } /* * BEGIN CRITICAL SECTION. * * All access to the my_info structure is done * inside this lock-protected section of code. * * If two threads try to initialize information * at the same time, the second one blocks on * the mi_lock_memory call. * * A reader also blocks so that it gets a * consistent read if another thread is updating * that memory. */ status = mi_lock_memory(memname, PER_SESSION); switch(status) { case MI_ERROR: mi_db_error_raise(NULL, MI_EXCEPTION, "GetMyInfo: mi_lock_memory call failed."); return (MyInfo *)NULL; break; case MI_OK: break; case MI_NO_SUCH_NAME: mi_db_error_raise(NULL, MI_EXCEPTION, "mi_lock_memory got MI_NO_SUCH_NAME."); return (MyInfo *)NULL; break; default: sprintf(msgbuf, "GetMyInfo: mi_lock_memory case %d.", status); mi_db_error_raise(NULL, MI_EXCEPTION, msgbuf); return (MyInfo *)NULL; break; } /* The lock on the named-memory block has been * obtained. */ /* The mi_named_zalloc( ) call above zeroed out * the structure, like calloc( ). So if the is_initialized * flag is set to zero, named memory has not been * initialized yet. */ if (my_info->is_initialized == 0) { /* In this block we populate the named-memory * structure. After initialization succeeds, set the * is_initialized flag. * * If any operation fails, MUST release the lock * before calling mi_db_error_raise( ): * * if (whatever != MI_OK) * { * mi_unlock_memory(memname, PER_SESSION); * mi_db_error_raise(NULL, MI_EXCEPTION, * "operation X failed!"); * return (MyInfo *)NULL; * } * */ my_info->is_initialized = 1; } /* endif: MyInfo structure not initialized */ else { /* Update or get a consistent read here. Again, * before any exception is raised with * mi_db_error_raise( ), the lock MUST be released. */ } /* * END CRITICAL SECTION. */ mi_unlock_memory (memname, PER_SESSION); return my_info; }
The preceding code fragment uses the mi_lock_memory( ) function to obtain the lock on the named memory. The following code fragment uses mi_try_lock_memory( ) to try to get a lock on a named-memory block 10 times before it gives up:
for ( lockstat=MI_LOCK_IS_BUSY, i=0; lockstat == MI_LOCK_IS_BUSY && i < 10; i++ ) { lockstat = mi_try_lock_memory(mem_name, PER_STMT_EXEC); switch( lockstat ) { case MI_OK: break; case MI_LOCK_IS_BUSY: mi_yield( ); /* Yield the processor. */ break; case MI_NO_SUCH_NAME: mi_db_error_raise(NULL, MI_EXCEPTION, "Invalid name of memory after good get"); return MI_ERROR; break; case MI_ERROR: mi_db_error_raise(NULL, MI_EXCEPTION, "Lock request failed."); return MI_ERROR; break; default: mi_db_error_raise(NULL, MI_EXCEPTION, "Invalid status from mi_try_lock_memory( )"); return MI_ERROR; break; } } /* Check the status after coming out of the loop. */ if( lockstat == MI_LOCK_IS_BUSY ) { mi_db_error_raise(NULL, MI_EXCEPTION, "Could not get lock on named memory."); return MI_ERROR; } /* Now have a locked named-memory block. Can perform a * read or update on the memory. */ ... mi_unlock_memory(mem_name, PER_STMT_EXEC);
Usually, the mi_try_lock_memory( ) function is a better choice than mi_lock_memory( ) for lock requests because mi_try_lock_memory( ) does not hang if the lock is busy.
The database server does not release any locks you acquire on named memory. You must ensure that your code uses the mi_unlock_memory( ) function to release locks in the following cases:
The database server automatically reclaims the named memory that mi_named_alloc( ) and mi_named_zalloc( ) allocate. The memory duration of the named memory determines when the database server marks the memory for deallocation. Named memory remains valid until either of the following events occurs:
To conserve resources, use the mi_named_free( ) function to explicitly deallocate the named memory once your DataBlade API module no longer needs it. The mi_named_free( ) function is the destructor function for named memory.
Keep the following restrictions in mind about memory deallocation of named memory:
The mi_named_free( ) function cannot free a named-memory block that is currently locked by another owner. If a UDR with another owner has a lock on the requested memory block, mi_named_free( ) marks the block as "deallocation pending" but does not actually free the memory. A subsequent call to mi_named_get( ) would return the MI_NO_SUCH_NAME return value for this named-memory block. Once the UDR with another owner has explicitly unlocked the memory block with mi_unlock_memory( ), a "deallocation pending" memory block is automatically freed. A subsequent call to mi_named_get( ) from this other UDR would return the MI_NO_SUCH_NAME return value for this named-memory block.
Home | [ Top of Page | Previous Page | Next Page | Contents | Index ]