Sample code that allocates a named-memory

The 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:
  • Immediately after you are done accessing the named memory
  • Before you raise an exception with mi_db_error_raise()
  • Before you call another DataBlade® API function that raises an exception internally (For more information, see Handling errors from DataBlade API functions.)
  • Before the session ends
  • Before the memory duration of the named memory expires
  • Before you attempt to free the named memory
Important: 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.