Next | Prev | Up | Top | Contents | Index

Exchanging the First Datum

The processes using a shared arena frequently need to locate some fundamental data structure that has been allocated within the arena. For example, the parent process might create a data structure in the arena, and fill it with pointers to other objects within the arena. Any process starting to use the arena needs the address of the base structure in order to find all the other objects.

The shared arena has a special one-pointer field for storing such a basic address. This area is accessed using the following calls:

void usputinfo (usptr_t *handle,

void *info);

Set the shared pointer field of arena *handle to info.
void *usgetinfo (usptr_t *handle); Return the contents of the shared pointer field of arena *handle.
int uscasinfo (usptr_t *handle,
void *old, void *new);
Change the contents of the shared pointer field in arena *handle. Returns 1 when the contents matched old and new was stored.

The purpose of uscasinfo() is to change the contents of the field in an atomic fashion, avoiding any race condition between concurrent processes in a multiprocessor. All three functions are discussed in detail in the usputinfo(3P) reference page.

Tip: The data type of the shared pointer field is void*, a 64-bit value when the program is compiled to the 64-bit model. If you need to cast the value to an integer, use the type __psint_t, a pointer-sized integer in any model. Often, the parent process creates and initializes the arena before it creates any of the child processes that will share the arena. In this case, you expect no race conditions. The parent can set the shared pointer using usputinfo(). Each child process can fetch the value with usgetinfo().

In the less-common case when an arena is shared by unrelated processes, each process that calls usinit() might be the first one to create the arena, or might not. If the calling process is the first, it should initialize the basic contents and set the shared pointer. If it is not the first, it should use the initialized contents that another process has already prepared. This problem is resolved with uscasinfo(), as sketched by the code in Example 2-2.

Example 2-2 : Setting Up an Arena with uscasinfo()

typedef struct arenaStuff {
   ulock_t   updateLock; /* exclusive use of this structure */
   short     joinedProcs; /* number of processes joined */
   ...pointers to other things allocated by setUpArena()... 
} arenaStuff_t;
/*
|| The following function preforms the one-time setup of the
|| arenaStuff structure. It assumes that updateLock is held.
*/
extern void
setUpArena(usptr_t *arena, arenaStuff_t *stuff);
/*
|| The following function joins a specified arena, creating it
|| and initializing it if necessary.  It could be extended with
|| values to pass to usconfig(3) before the arena is created.
*/ 
usptr_t*
joinArena(char *arenaPath)
{
   usptr_t *arena;
   arenaStuff_t *stuff;
   int ret;
   /*
   || Join the arena, creating it if necessary. Exit on error.
   */
   if (!arena = usinit(arenaPath))
   {
      perror("usinit");
      return arena;
   }
   /*
   || Do the following as many times as necessary until the arena
   || has been initialized.
   */
   for(ret=0; !ret; )
   {
      if (stuff = (arenaStuff_t *)usgetinfo(arena))
      {
         /*
         || Another process has created the arena, and either has
         || initialized it or is initializing it right now. Acquire
         || the lock, which will block us until initializing is done.
         */
         ussetlock(stuff->updateLock);
         /* here do anything needing exclusive use of arena */
         ++stuff->joinedProcs; /* another process has joined */
         usunsetlock(stuff->updateLock); /* release arena */
         ret = 1; /* end the loop */
      }
      else
      {
         /*
         || This process appears to be first to call usinit().
         || Allocate an arenaStuff structure with its updateLock
         || already held and 1 process joined, and try to swap it
         || into place as the active one. We expect no errors
         || in setting up arenaStuff. If one occurs, the arena is
         || simply unusable, and we return a NULL to the caller.
         */
         if (! (stuff = usmalloc(sizeof(arenaStuff_t),arena) ) )
            return stuff; /* should never occur */
         if (! (stuff->updateLock = usnewlock(arena) ) );
            return (usptr_t*)0; /* should never occur */
         if (! uscsetlock(stuff->updateLock, 1) )
            return (usptr_t*)0; /* should never occur */
         stuff->joinedProcs = 1;
         if (ret = uscasinfo(arena,0,stuff))
         {
            /*
            || Our arenaStuff is now installed. Initialize it.
            || The loop ends because ret is now nonzero.
            */
            setUpArena(arena,stuff);
            usunsetlock(stuff->updateLock);
         }
         else
         {
            /*
            || uscasinfo() either did not find a current value of 0
            || (indicates a race with another process executing this
            || code) or it failed for some other reason. In any case,
            || release allocated stuff and repeat the loop (ret==0).
            */
            usfreelock(stuff->updatelock,arena);
            usfree(stuff,arena);
         }
      } /* usgetinfo returned 0 */
   } /* while uscasinfo swap fails */
   /* arena->initialized arena, updateLock not held */
   return arena;
}
Example 2-2 assumes that everything allocated in the arena will be accessed through a collection of pointers, arenaStuff. The two problems to be solved are:

The solution in Example 2-2 is based on the discussion in the uscasinfo(3P) reference page. Each process calls function joinArena(). If a call to usgetinfo() returns nonzero, it is the address of an arenaStuff_t that has been allocated by some other process. Possibly that process is concurrently executing, initializing the arena. The current process waits until the lock in the arenaStuff_t is released. On return from the ussetlock() call, the process has exclusive use of arenaStuff until it releases the lock. It uses this exclusive control to increment the count of processes using the arena.

When usgetinfo() returns 0, the calling process is probably the first to create the arena, so it allocates an arenaStuff structure, and also allocates the essential lock and puts it in a locked state. Then it calls uscasinfo() to swap the arenaStuff address for the expected value of 0. When the swap succeeds, the process completes initializing the arena and releases the lock.

The call to uscasinfo() could fail if, between the time the process receives a 0 from usgetinfo() and the time it calls uscasinfo(), another process executes this same code and installs its own arenaStuff. The process handles this unusual event by releasing the items it allocated and repeating the whole process.

When unrelated processes join an arena with code like that shown in Example 2-2, they should terminate their use of the arena with code similar to Example 2-3.

Example 2-3 : Resigning From an Arena

/*
|| The following function reverses the operation of joinArena.
|| Even if the calling process is the last one to hold the arena, 
|| nothing drastic is done. This is because it is impossible to
|| perform {usinit(); usgetinfo(); ussetlock();} as an atomic
|| sequence.  Once an arena comes into being it must remain
|| usable until the entire application shuts down. Unlinking the
|| arena file can be the last thing that main() does.
*/
void
resignArena(usptr_t *arena)
{
   arenaStuff_t *stuff = (arenaStuff_t *)usgetinfo(arena);
   ussetlock(stuff->updateLock);
   -- stuff->joinedProcs;
   usunsetlock(stuff->updateLock);
}
It might seem that, when the function resignArena() in Example 2-3 finds that it has reduced the joinedProcs count to 0, it could deinitialize the arena, for example unlinking the file on which the arena is based. This is not a good idea because of the remote chance of the following sequence of events:

  1. Process A executes joinArena(), initializing the arena.

  2. Unrelated process B executes joinArena() through the usinit() call, but is suspended for a higher-priority process before executing usgetinfo().

  3. Process A detects some error unrelated to arena use, and as part of termination, calls resignArena().

  4. Process B resumes execution with the call to usgetinfo().
If the resignArena() function did something irrevocable, such as unlinking or truncating the arena file, it would leave process B in an unexpected state.


Next | Prev | Up | Top | Contents | Index