[ Previous | Next | Contents | Glossary | Home | Search ]
Motif 2.1 Widget Writer's Guide



MultiThread-Safe Motif Widgets

Writing a multithreaded application ideally requires that the libraries the application uses be MT-safe. Otherwise the application must ensure that only one thread is inside the library at any given time. This could lead to unnatural design and coding strategies and to sacrificing parallelism in the application. Making a library MT-safe basically means that the library should take the responsibility for ensuring consistent internal state even when the application starts multiple threads that may simultaneously invoke its API.

This section describes how to develop a MT-safe Motif widget library.

MT-Safety in the Xt Intrinsics

The Xt library in Release-6 of the X-Window system (X11R6) is MT-safe. A brief description of the MT-safety provided by Xt follows.

Concurrency

Concurrency refers to the number of threads that can safely execute a piece of code at the same time. Xt permits one thread per XtAppContext to be active. So multiple threads can be active within Xt simultaneously, if each one of them is spawned in distinct XtAppContexts. Thus the concurrency provided by Xt is at the XtAppContext level.

This model is elegant for application programmers. However it can be difficult for widget programmers since it allows multiple active threads within the widget methods concurrently, and hence all global data access within the widget library needs to be suitably protected.

Fortunately, most data in a widget is stored in the widget instance. And since a widget instance hierarchy is rooted in an XtAppContext, the one thread per XtAppContext model protects instance data from corruption due to multiple threads.

AppLock

Xt implements the one thread per XtAppContext policy by protecting all public entry points into the library with AppLocks. Every public Xt API locks the application context at entry and releases it at exit. Xt provides the following functions to lock and unlock the XtAppContext:

  1. void XtAppLock(XtAppContext app)

  2. void XtAppUnlock(XtAppContext app)

    Note that these locks are recursive; that is, the same thread invoking XtAppLock multiple times with the same application context will not deadlock.

  3. ProcessLock

    Xt provides ProcessLocks to ensure that only one thread at a time can execute critical code paths. All access to global data is considered critical code and is done while the process is locked. Xt provides the following functions to lock and unlock the ProcessLock:

    1. void XtProcessLock(void)

    2. void XtProcessUnlock(void)

      Note that these locks are recursive; that is, the same thread invoking XtProcessLock multiple times will not deadlock.

    3. Deadlock

      Xt avoids deadlock by mandating a simple locking hierarchy: always acquire the AppLock first; then acquire the ProcessLock.

      MT-Safe Motif Widgets

      Motif uses the same locking strategies implemented in Xt:

      1. Envelop all public APIs using AppLocks

      2. Protect all global data using ProcessLocks.

      3. API Protection

        All public entry points into the widget should be enveloped with the XtAppLock, XtAppUnlock pair. Private entry points intended for internal use only need not be protected, as the invoking public API will have acquired AppContext locks. Public functions that are just wrappers around Xt functions (such as XmCreate* functions) do not require AppLocks since the Xt function already does this.

        Typically, widget APIs have either a Widget, Display or Screen parameter. The AppContext can be obtained from these using the following Xt functions:

        1. XtAppContext XtWidgetToApplicationContext(Widget)

        2. XtAppContext XtDisplayToApplicationContext(Display *)

        3. XtAppContext XtDisplayToApplicationContext(DisplayOfScreen(Screen *))

          Widget APIs that do not have either a Widget, Display or Screen parameter cannot be protected using AppLocks. A potential deadlock exists if such a function wants to acquire the ProcessLock (because the function has not acquired the AppLock yet), resulting in a break in the locking hierarchy. In this situation, one must ensure that once the ProcessLock is obtained, an AppLock should not be attempted until the ProcessLock is released. Basically this means that Xt function calls, user callbacks, or other non-safe routines that can AppLock should not be invoked from within ProcessLock'ed regions in this function. For example, consider a widget public API XmFooBar:

          Dimension XmFooBar(Widget w)
          {
                  .....
           return w->core.width;
          }

          The MT-safe version of the above function would be:

          Dimension XmFooBar(Widget w)
          {
                int return_value;
                XtAppContext app = XtWidgetToApplicationContext(w);
                XtAppLock(app);
                ......
                return_value = w->core.width;
                XtAppUnlock(app);
                      return return_value;
          }

        4. Global Data Protection

          Global data can be classified as either read-only or read-write:

          1. Read-only globals are those that are written into only during initialization and read thereafter. The initialization can happen either at compile time or once during runtime (typically in the Widget's ClassInitialize method). These globals are MT-safe and do not require any access protection, except during initialization.

          2. Read-Write globals can be written into at any time during program execution. All accesses must be protected by XtProcessLocks.

            Other random pieces of global data (mostly function static variables) can be usually dealt with by either making them instance fields or stack variables. If that does not work, ProcessLocks can be used to protect global access regions. Exceptions are those variables that are used to communicate state information across function. These can be handled using thread specific storage to retain their semantics.

            A discussion on the common global categories in Motif widgets follows.

          3. Class Methods and Class Fields

            A widget basically has two parts: a class record and an instance record. The instance record is per-widget and is dynamically allocated when the widget is created, and thus is not global data. Class records however are statically allocated global data. The class record typically contains methods and few data fields. The class methods are either set up at compile time and never changed thereafter or are set up at runtime during ClassInitialize (to resolve inheritance). Class initialization happens once when the first widget of that class is created. This process happens within Xt and is already under ProcessLocks. Thus we can be assured that these class method pointers get written into only once by only one thread.

            So, we can consider the above two cases to be read-only situations and hence in general, class methods can be accessed without locks.

            However, the Xt Base classes (RectObj, Core, Constraint, Shell) are exceptions since Motif does change their class method pointers at runtime to implement Pre and Post hooks. Hence we consider all the Xt Classes' class methods to be read-write and we need to access those method pointers within XtProcessLocks.

            Consider a code fragment that invokes the resize method:

            {
                  WidgetClass wc; Widget w;
                  (*wc->core_class.resize)(w);
             
            }

            The MT-safe version would be

            {
                  WidgetClass wc; Widget w;
                  XtWidgetProc resize;
                  XtProcessLock();
                  resize = wc->core_class.resize;
                  XtProcessUnlock();
                  (*resize)(w);
            }

            Most class fields are read-write and hence require XtProcessLock protection.

            XContext

            XContexts are an Xlib abstraction to implement per display storage. Since Display connections cannot be shared among XtAppContexts, we are ensured that the data hanging off XContexts also cannot be shared among XtAppContexts. Hence we don't need to protect these data using Locks.

            However, ProcessLock protection may be needed to protect the initialization of the XContext (created using XUniqueContext if the initialization does not happen in a Widget's ClassInitialize procedure.

            Resource Lists

            Widgets define their resource lists as static global XtResource structures. These lists are set up at compile time and never changed thereafter. Hence these can be considered as read-only data and do not require access protection.

            Motif widgets also define synthetic resource tables as static global XmSyntheticResource structures. These lists are also read-only and do not require protection.

            Action Tables and Translation Tables

            Widgets define their Actions and Translations using static global XtActionRec and XtTranslation structures respectively. These are set up at compile time and only modified by the (MT-safe) Xt intrinsics. Hence these can be considered read-only data and do not require access protection.

            Traits

            Motif defines the Trait mechanism as a means of sharing behavior among widget classes. A widget establishes a trait by setting up a static global XmTrait structure. This table is set up at compile time and its contents should not be changed thereafter. Thus Trait tables also do not require any protection.

            Resource Converters

            Xt Resource converters are used for inter-type resource conversions. The converter can return the converted data either in local static storage or in the heap. The choice is made based on the address passed by the caller to store the converted value in. If the address is NULL, static storage is used, else the memory (on the heap) pointed to by address is used. Since static storage should be avoided for MT-safety, always pass in a valid heap address when invoking any converter. The guidelines when using/writing resource converters are:

            1. Resource converters should be able to handle both the cases of internal static storage and passed in heap storage.

            2. Invoke resource converters with a valid heap address for the converter to store the converted value. (Xt always invokes resource converters by passing in a valid heap address to store the converted value.)

            3. Default Resource Value Procedures

              XtCallProc procedures are used in widget resource lists to provide default values for a resource. Default procedures typically use static storage to store the default value. Fortunately, this usage is MT safe, since the code in Xt that invokes this procedure and copies over the default value into the resource location is within XtProcessLocks. Thus, the statics in default procedures present no problems.

              Event Loops

              Widgets may use their own event loops to implement Drag and Drop and other such esoteric features.

              The event processing functions in R6 Xt that block while waiting for events drop the XtAppLock during the wait (XtAppNextEvent, XtAppPeekEvent, and XAppProcessEvent ). This is done so that other threads get a chance to issue Xt calls while the event processing thread waits for events. However in the case of Drag and Drop, we really do not want other threads to get into Xt and change state while the Drag operation is in progress. There is also the danger that another event processing thread might come in and "steal" events meant for this thread.

              To avoid this situation, event loops should be replaced with "busy" loops that check the event queue without giving up the XtAppLock.

              The following code implements a "busy" event loop that does not give up the XtAppLock by replacing XtAppNextEvent:

              while (XtAppGetExitFlag(app) == False)  {
                      XEvent event;
              #ifndef MT_SAFE
                      XtAppNextEvent(app, &event);
                      XtDispatchEvent(&event);  /* Process it */
              #else  /* MT_SAFE version ...  */
                      XtInputMask mask;
                      while (!(mask = XtAppPending(app)))
                    /* EMPTY */;  /* Busy waiting - so that we don't lose our Lock! */
                      if (mask & XtIMXEvent) {  /* We have a XEvent */
                     /* Get the XEvent - we know its there! Note that XtAppNextEvent
              would also process timers/alternate inputs */
                              XtAppNextEvent(app, &event); /* No blocking, since
              an event is ready */
                           XtDispatchEvent(&event);  /* Process it */
                      }
                      else /* Not a XEvent, its an alternate input/timer event -
              Process it.
                          XtAppProcessEvent(app, mask); /* No blocking, since an event
              is ready */
                }

              MT-Safe C Library Functions

              Some of the C library functions are inherently MT unsafe. A MT-safe libc implementation will provide alternatives to those functions. This section lists the common libc functions that are MT-unsafe along with their safe POSIX equivalents.

              MT-Unsafe

              MT-Safe

              getlogin

              getlogin_r

              ttyname

              ttyname_r

              readdir

              readdir_r

              strtok

              strtok_r

              ctime, localtime...

              ctime_r, localtime_r...

              getgrnam, getgrgid

              getgrnam_r, getgrgid_r

              getpwnam, getpwuid

              getpwnam_r, getpwuid_r

              MT-Safe Motif Library Functions

              The following Motif library functions are MT-safe:

              1. XmFontListEntryCreate_r

              2. XmFontListCreate_r

              3. XmStringCreateFontList_r

              4. Thread Specific Storage

                Occasionally, one might end up using globals to communicate state information across functions. The only way to retain the proper semantics in this case might be to use thread specific storage. The Xthreads.h header file in X11R6 provides a set of wrapper functions to support thread_specific storage.

                The sequence of steps to creating and using Thread Specific Storage are:

                1. Create a unique key that identifies this storage. This step should be done only once for all the threads. The most convenient way to enforce this is to do the key creation in the concerned widget's ClassInitialize procedure. This also has the nice side effect of automatically being within XtProcessLocks. The Xthreads API for key creation is
                  xthread_key_create(xthread_key_t *key_return);

                2. Set and Get thread specific data: once a key is created, each thread may bind a value to that key. The values are specific to the binding thread and are maintained for each thread independently. Typically, this value is a pointer to heap storage where the actual data is stored. The heap is set up once for each thread during the first call to get the thread specific data. Subsequently, that heap location is used as the storage for the global variable. The Xthreads API is:
                  void xthread_getspecific(xthread_key_t key, void **value_return);
                  int xthread_setspecific(xthread_key_t key, const void *value);

                  Note that xthread_getspecific returns NULL the first time its invoked for a specific thread, so that gives us the chance to set up the heap storage for each thread.

                  Thread specific storage can be expensive, hence it should be utilized as a last resort.

                3. Live Resources

                  For MT-safety, widget resources should be designed so that XtGetValues returns a copy of the resource value (in the heap). The GetValuesHook method or the XmSyntheticResource export_proc can be used to achieve this.

                  If a live pointer to the actual instance field has to be returned, flag this situation as a MT-unsafe usage so that the application programmer is aware of it.

                  CheckList

                  Following is a summary of the discussions in this section:

                  1. First and foremost, avoid global and static variables. Use

                    1. Instance fields to store widget specific information.

                    2. XContexts to store per Display information to be shared among multiple widgets.

                    3. If forced to use globals or statics, protect them by either

                      1. Using XtProcessLocks (XtProcessLock and XtProcessUnlock) around all access points or

                      2. Using thread specific storage for globals that communicate state information across functions

                      3. The exceptions to the above are Xt resource converters and XtCallProc, where it is safe to use statics. Note that resource converters should be invoked with a valid n heap address to store the converted value.

                      4. The Xt base classes' class methods need to be protected since their function pointers can be dynamically written into by Motif. Copy the pointer into a stack variable and invoke the method through the stack variable. The copying needs to be done within XtProcessLocks. Use a similar strategy when accessing other fields in any class record (accesses of this nature are rare).

                      5. Protect all public APIs with XtAppLocks. Call XtAppLock on entry into the function and XtAppUnlock when exiting.

                      6. Maintain the locking hierarchy: Always acquire the XtAppLock first, then the XtProcessLock

                      7. Investigate usage of private Event loops and make them MT-safe as described earlier.

                      8. Copy out resource values if possible when an application does XtGetValues on the resource. Use the Xm synthetic resource mechanism or the GetValuesHook method to do this. If this cannot be done because it is inefficient, document these resources with appropriate warnings.

                      9. Use MT safe versions of unsafe libC functions.

                      10. Use conditional #defines to isolate all MT related changes, so that the code can be built non-MT-safe on platforms that do not support threading.

                      11. [ Previous | Next | Contents | Glossary | Home | Search ]