Home | Previous Page | Next Page   Creating User-Defined Routines > Managing Memory > Managing Shared Memory >

Managing Named Memory

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.

Tip:
The DataBlade API named-memory-management functions execute only in C UDRs. Do not use these memory-management functions in client LIBMI applications. For DataBlade API modules that you design to run in both client LIBMI applications and UDRs, use the user-memory-management functions. For information on memory management in a client LIBMI application, see Appendix A. Writing a Client LIBMI Application.

To save memory across invocations of a UDR, you can perform one of the following tasks:

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.

Table 101. DataBlade API Named-Memory-Management Functions
Named-Memory Task DataBlade API Functions
Allocating named memory mi_named_alloc( ), mi_named_zalloc( )
Obtaining an allocated named-memory block mi_named_get( )
Controlling concurrency mi_lock_memory( ), mi_try_lock_memory( ), mi_unlock_memory( )
Deallocating named memory mi_named_free( )

Warning:
These advanced memory-management functions can adversely affect your UDR if you use them incorrectly. Use them only when the regular DataBlade API user-memory-management functions cannot perform the task you need done.

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.

Tip:
Each of the named-memory functions in Table 101 have tracepoints in them that generate output when the trace level is greater than zero (0). The output consists of the function name and the arguments passed to it. For more information on tracepoints, see Using Tracing.

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( )

Allocating Named Memory

To handle dynamic memory allocation of named memory, use one of the following DataBlade API memory-management functions.

Memory-Allocation Task DataBlade API Function
To allocate named memory with a specified memory duration mi_named_alloc( )
To allocate named memory with a specified memory duration that is initialized with zeros mi_named_zalloc( )

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.

Tip:
These named-memory-management functions do not use the current memory duration.

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.

Obtaining a Block of Allocated Named Memory

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:

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.

Important:
The preceding code fragment does not handle concurrency issues that result from multiple UDRs trying to access the cache_blk named memory at the same time. For more information, see Handling Concurrency Issues.

Handling Concurrency Issues

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.

Memory-Locking Task DataBlade API
Memory-Locking Function
Request a lock on the specified named-memory block and wait for the lock to be obtained. mi_lock_memory( )
Request a lock on the specified named-memory block and do not wait for the lock to be obtained. mi_try_lock_memory( )
Unlock the specified named-memory block. mi_unlock_memory( )

The following guidelines are recommended for handling concurrency problems:

Tip:
Locking of named memory (with mi_lock_memory( ) and mi_try_lock_memory( )) uses its own locking mechanism to keep track of named-memory locks. It does not consume database locks.

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:

Warning:
After you obtain a lock on a named-memory block, you must explicitly release it with the mi_unlock_memory( ) function. Failure to release a lock before one of the previous conditions occurs can severely impact the operation of the database server.

Deallocating Named Memory

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 ]