Motif Programming Manual (Volume 6A)

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer

X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation





In this chapter:



Chapter: 26

Signal Handling



This chapter describes the techniques which should be adopted in order to handle UNIX signals within an X-based application. It is not a lesson in signal handling per se: if you are unsure what a signal is or how to handle one in an ordinary non-X-based application, you should consult your operating system or programming language manuals.

In X11R5, handling signals safely could be problematic. UNIX signals are delivered asynchronously in the context of the currently running process. On receipt of a signal, your application branches immediately to any handlers which have been set up to process the signal concerned, without any consideration to the application state or context. That is, regardless of whatever state the application is in, a new function context is pushed onto the process stack in order to immediately handle the signal. This scheme of operations can severely interfere with the transmission and processing of X protocol messages between the X server and the application client, because the signal could potentially be delivered right in the middle of an X call which is manipulating the event queue. Any attempt by the signal handler to call further X routines in these circumstances might garbage any messages which are in progress. Although the probability of this interference is small, because signals can arise from a variety of reasons which in many cases are from events external to the application, mission critical applications have to guard against the possibility by incorporating carefully constructed code to process potentially dangerous signals safely and cleanly. This is particularly important because the default action associated with some signal types causes the termination of the application, and thus we have no option but to install our own handlers to override the system default.

The solution to the problem is to ensure that the processing of received signals is delayed until such time as the application knows that it is safe to do so1. In practical terms, this means that we rewrite our X event loop to take into account the possible receipt of a signal, and we only process the signal when we know that the event queue is stable. This usually involves two signal handling routines. The first routine is installed as the normal signal handler, but it does nothing more than set a flag on receipt of the signal. The second routine performs the actual signal processing; we call this at a later date when it is safe to do so.

In X11R6, the task is much simplified because the toolkit has been rewritten in order to handle signals safely. There are new toolkit procedures which we can call that are specifically designed to ensure that the receipt and handling of a signal does not interfere with the X queue processing.

In the discussion which follows, we present both X11R5- and X11R6-style signal handling. In the case of X11R5, the issue is complicated by the fact that an application can decide to handle events in two different ways, either using the lower level Xlib mechanisms, or through the higher level event procedures provided in the X toolkit.

Handling Signals in X11R5


Handling Signals using Xlib

An application that uses Xlib gets events from the server using a function like XNextEvent(). This function reads the next event in the queue and fills an XEvent data structure that describes various things about the event, such as the window associated with it, the time it took place, the event type, and so on. When the function returns, the event has been delivered and it is up to the application to decide what to do next. The following code fragment demonstrates a simplified view of Xlib event handling:

void sigchld_handler(int);

main_event_loop ()
{
    ...
    signal (SIGCHLD, sigchld_handler);

    while (1) {
        XNextEvent (display, &event);
        switch (event.type) {
            case ConfigureNotify: /*...*/ break;
            case Expose:          /*...*/ break;
            case ButtonPress:     /*...*/ break;
            case EnterWindow:     /*...*/ break;
            case LeaveWindow:     /*...*/ break;
            case MapNotify:       /*...*/ break;
            ...
        }
    }
}
If the operating system decides to deliver a SIGCHLD signal, the signal can arrive at any time, possibly inside any of the case statements or even inside the call to XNextEvent(). The signal handler for the signal is called automatically by the operating system. If the signal handler makes any Xlib calls, you have no way of knowing if it is doing so at a time when another Xlib call is being sent to the X server. The solution is to have the signal handler do nothing but set a flag to indicate that the signal has been delivered. Then, just before the call to XNextEvent(), the event loop can check the flag to determine whether or not to call another function that actually processes the signal. This new design is shown in the following code fragment:

static int sigchld_delivered;
void sigchld_handler(...), real_sigchld_handler(int);

main_event_loop()
{
    ...
    signal (SIGCHLD, real_sigchld_handler);

    while (1) {
        /* it's safe to handle signals that may have been delivered */
        if (sigchld_delivered > 0) {
            /* add other parameters as necessary */
            sigchld_handler (SIGCHLD);
            sigchld_delivered--;
        }
        XNextEvent (display, &event);
        switch (event.type) {
            case ConfigureNotify: /*...*/ break;
            case Expose:          /*...*/ break;
            case ButtonPress:     /*...*/ break;
            case EnterWindow:     /*...*/ break;
            case LeaveWindow:     /*...*/ break;
            case MapNotify:       /*...*/ break;
            ...
        }
    }
}
All that real_sigchld_handler() does is increment the sigchld_delivered flag, as shown in the following fragment:

/* additional parameters differ between BSD and SYSV */
void real_sigchld_handler (int sig)
{
    sigchld_delivered++;
}
The actual sigchld_handler() routine can do whatever it needs to do, including call Xlib routines, since it is only called when it is safe to do so. You should note that XNextEvent() waits until it reads an event from the X server before it returns, so handling the signal may take a long time if the program is waiting for the user to do something.

These code fragments demonstrate the general design for handling signals in a rudimentary way. In a real application, the actual signal handler would probably need access to all of the parameters passed to the original signal handling function. One example of this situation would be a signal handler that displays the values of all its parameters in a dialog box. You can't change anything on the display using the original signal handler because it would require making Xlib calls, so you have to save the parameters until the real signal handler is called. To save the parameters, you could define a data structure that contains fields for all of the parameters. The original signal handler could allocate a new structure and fill it in each time a signal is delivered.2 When the real signal handler is called, it can access the data structure and create a dialog using the appropriate Xlib calls.


Handling Signals in Xt

Since this is a book on Motif and Motif is based on Xt, the next step is to find a solution that is appropriate for Xt-based applications. In Xt, you typically don't read events directly from the X server using XNextEvent() and then branch on the event type to decide what to do next. Instead, Xt provides XtAppMainLoop(); the code for this function is below:

void XtAppMainLoop (XtAppContext app_context)
{
    XEvent event;

    for (;;) {
        XtAppNextEvent (app_context, &event);
        XtDispatchEvent (&event);
    }
}
Since the event processing loop is internal to the Xt toolkit, we don't have the opportunity to insert a check to see if any signals have been delivered, as we did with Xlib. There are various ways to handle this problem. We could write our own event processing loop and include code that tests for the delivery of a signal. One problem with this solution is that it bypasses a standard library routine. We want to ensure upwards compatibility with future versions of Xt, and if we write our own routine, we risk losing any functionality that might be introduced later.

Even though it is unlikely that XtAppMainLoop() will change in the future, we should find another way to solve the problem. Clearly, the desired effect is to get Xt to notify us just before it's going to call XNextEvent(), since this is the window of opportunity where it is safe for a signal handler to make Xlib or Xt calls. It just so happens that Xt provides two methods that do what we want: work procedures and timers.

A work procedure is a function that is called by Xt when it does not have any events to process. Although an application can register multiple work procedures, the procedures are processed one at a time, with the most recent one being invoked first. We can solve the signal handler problem using a work procedure because most applications spend a fair bit of time waiting for the user to generate events. In the signal handler, we register a work procedure using XtAppAddWorkProc(). When the application is idle, Xt invokes the work procedure, which does the real work of handling the signal. The following code fragment uses this approach:

XtAppContext app;
static void real_reset(int);
static Boolean reset(XtPointer);

main (int argc, char *argv[])
{
    ...
    signal (SIGCHLD, real_reset);
    ...
}

/* reset() -- a program died... */
static void real_reset (int unused)
{
    int pid, i;

    #ifdef    SYSV
        int status;
    #else  /* SYSV */
        union wait status;
    #endif /* SYSV */

    if ((pid = wait (&status)) == -1)
        /* an error of some kind (fork probably failed); ignore it */
        return;

    (void) XtAppAddWorkProc (app, reset, NULL);
}

static Boolean reset (XtPointer client_data)
{
    /* handle anything Xt/Xlib-related that needs to be done now */
    ...
    return True; /* remove the work procedure from the list */
}
This example assumes that the application forks off a new process at some point. When the child eventually exits, the parent is sent a SIGCHLD signal, at which point the application branches directly to the real_reset() signal handler. This routine reaps the child using wait() and then adds a work procedure using XtAppAddWorkProc(). (The function normally returns a work procedure ID, but we're not interested in it here.) When Xt does not have any events to process, it calls reset(). This routine can perform any other tasks necessary for handling the signal, such as calling Xlib routines, popping up dialogs, or anything it likes.

If the application is waiting for events when it receives the signal, the work procedure is invoked almost immediately after the actual signal handler. However, if the application is in a callback routine handling an event, the work procedure is not called until control is passed back to the event loop. While it's true that there may be some delay between the time that the signal is delivered and the time that it is actually processed, the delay is usually small enough that an application doesn't need to worry about it. If timing is critical, you can always set a global signal flag when the signal is received, and then test that variable in critical sections of your code to see if the signal has been delivered.


An Example

The signal handling problem can also be solved with a timer, using the same approach as with a work procedure. Example 26-1 demonstrates the use of a timer in a more realistic application.3The program displays an array of DrawnButtons that start application programs. While an application is running, the associated button is insensitive, so that the user can only run one instance of the application. When the application exits, the button is reactivated, so that the user can select it again.

Example  26-1 The app_box.c program

/* app_box.c -- make an array of DrawnButtons that, when activated,
** executes a program. When the program is running, the drawn button
** associated with the program is insensitive. When the program dies,
** reactivate the button so the user can select it again.
*/

#include <Xm/DrawnB.h>
#include <Xm/RowColumn.h>
#include <signal.h>

#ifndef   SYSV
#include <sys/wait.h>
#else  /* SYSV */
#define SIGCHLD SIGCLD
#endif /* SYSV */

#define MAIL_PROG "/bin/mail"

typedef struct {
    Widget    drawn_w;
    char      *pixmap_file;
    char      *exec_argv[6]; /* 6 is arbitrary, but big enough */
    int       pid;
} ExecItem;

ExecItem prog_list[] = {
    { NULL, "terminal", { "xterm", NULL }, 0 },
    { NULL, "flagup", { "xterm", "-e", MAIL_PROG, NULL }, 0 },
    { NULL, "calculator", { "xcalc", NULL }, 0 },
    { NULL, "xlogo64", { "foo", NULL }, 0 }
};

XtApp Context app;/* application context for the whole program */
GC    gc;         /* used to render pixmaps in the widgets */

void reset (int);
void reset_btn (XtPointer, XtIntervalId *);
void redraw_button (Widget, XtPointer, XtPointer);
void exec_prog (Widget, XtPointer, XtPointer);

main (int argc, char *argv[])
{
    Widget  toplevel, rowcol;
    Pixmap  pixmap;
    Pixel   fg, bg;
    Arg     args[8];
    int     i, n;

    /* we want to be notified when child programs die */
    (void) signal (SIGCHLD, reset);

    XtSetLanguageProc (NULL, NULL, NULL);

    /* Since this is an X11R5 example... 
    ** For X11R6, use XtVaOpenApplication()
    */
    toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    NULL);

    n = 0;
    XtSetArg (args[n], XmNorientation, XmHORIZONTAL); n++;
    rowcol = XmCreateRowColumn (toplevel, "rowcol", args, n);

    /* get the foreground and background colors of the rowcol
    ** so the gc (DrawnButtons) will use them to render pixmaps.
    */
    XtVaGetValues (rowcol,
    XmNforeground, &fg,
    XmNbackground, &bg, NULL);
    gc = XCreateGC (XtDisplay (rowcol),
                    RootWindowOfScreen (XtScreen (rowcol)),
                    NULL, 0);
    XSetForeground (XtDisplay (rowcol), gc, fg);
    XSetBackground (XtDisplay (rowcol), gc, bg);

    for (i = 0; i < XtNumber (prog_list); i++) {
        /* the pixmap is taken from the name given in the structure */
        pixmap = XmGetPixmap (XtScreen (rowcol),
                                prog_list[i].pixmap_file, fg, bg);
        /* Create a drawn button 64x64 (arbitrary, but sufficient)
        ** shadowType has no effect till pushButtonEnabled is false.
        */
        n = 0;
        XtSetArg (args[n], XmNwidth, 64);                       n++;
        XtSetArg (args[n], XmNheight, 64);                      n++;
        XtSetArg (args[n], XmNpushButtonEnabled, True);         n++;
        XtSetArg (args[n], XmNshadowType, XmSHADOW_ETCHED_OUT); n++;
        prog_list[i].drawn_w = XmCreateDrawnButton (rowcol,
                                                    "dbutton",
                                                    args, n);
        XtManageChild (prog_list[i].drawn_w);

        /* if this button is selected, execute the program */
        XtAddCallback (prog_list[i].drawn_w, XmNactivateCallback,
                            exec_prog, (XtPointer) &prog_list[i]);
        /* when the resize and expose events come, redraw pixmap */
        XtAddCallback (prog_list[i].drawn_w, XmNexposeCallback,
                            redraw_button, (XtPointer) pixmap);
        XtAddCallback (prog_list[i].drawn_w, XmNresizeCallback,
                            redraw_button, (XtPointer) pixmap);
    }

    XtManageChild (rowcol);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}

/* redraw_button() -- draws the pixmap into its DrawnButton
** using the global GC. Get the width and height of the pixmap
** being used so we can either center it in the button or clip it.
*/
void redraw_button (Widget button, XtPointer client_data,
XtPointer call_data)
{
    Pixmap       pixmap = (Pixmap) client_data;
    int          unused, destx, desty;
    unsigned int srcx, srcy, pix_w, pix_h;
    int          drawsize, border;
    Dimension    bdr_w, w_width, w_height;
    short        hlthick, shthick;
    Window       root;
    XmDrawnButtonCallbackStruct *cbs =
                                (XmDrawnButtonCallbackStruct *) call_data;

    /* get width and height of the pixmap. don't use srcx and root */
    XGetGeometry (XtDisplay (button), pixmap, &root, &unused,
                    &unused, &pix_w, &pix_h, &srcx, &srcx);
    /* get the values of all the resources that affect the entire
    ** geometry of the button.
    */
    XtVaGetValues (button,
                    XmNwidth, &w_width,
                    XmNheight, &w_height,
                    XmNborderWidth, &bdr_w,
                    XmNhighlightThickness, &hlthick,
                    XmNshadowThickness, &shthick,
                    NULL);
    /* calculate available drawing area, width first */
    border = bdr_w + hlthick + shthick;
    /* if window is bigger than pixmap, center it; else clip pixmap */
    drawsize = w_width - 2 * border;
    if (drawsize > pix_w) {
        srcx = 0;
        destx = (drawsize - pix_w) / 2 + border;
    }
    else {
        srcx = (pix_w - drawsize) / 2;
        pix_w = drawsize;
        destx = border;
    }
    drawsize = w_height - 2 * border;
    if (drawsize > pix_h) {
        srcy = 0;
        desty = (drawsize - pix_h) / 2 + border;
    }
    else {
        srcy = (pix_h - drawsize) / 2;
        pix_h = drawsize;
        desty = border;
    }

    XCopyArea (XtDisplay (button), pixmap, cbs->window, gc, srcx, srcy, pix_w, 
                                            pix_h, destx, desty);
}

/* exec_proc() -- the button has been pressed; fork() and call
** execvp() to start up the program. If the fork or the execvp
** fails (program not found?), the sigchld catcher will get it
** and clean up. If the program is successful, set the button's
** sensitivity to False (to prevent the user from exec'ing again)
** and set pushButtonEnabled to False to allow shadowType to work.
*/
void exec_prog (Widget drawn_w, XtPointer client_data,
                                        XtPointer call_data)
{
    ExecItem *program = (ExecItem *) client_data;
    XmDrawnButtonCallbackStruct *cbs =
                                (XmDrawnButtonCallbackStruct *) call_data;

    switch (program->pid = fork ()) {
        case 0:  /* child */
            execvp (program->exec_argv[0], program->exec_argv);
            perror (program->exec_argv[0]); /* command not found? */
            _exit (255);
        case -1:
            printf ("fork() failed.\n");
    }

    /* The child is off executing program... parent continues */
    if (program->pid > 0) {
        XtVaSetValues (drawn_w, XmNpushButtonEnabled, False, NULL);
        XtSetSensitive (drawn_w, False);
    }
}

/* reset() -- a program died, so find out which one it was and
** reset its corresponding DrawnButton widget so it can be reselected
*/
void reset (int unused)
{
    int         pid, i;
    #ifdef    SYSV
        int         status;
    #else  /* SYSV */
        union wait  status;
    #endif /* SYSV */

    if ((pid = wait (&status)) != -1) {
        for (i = 0; i < XtNumber (prog_list); i++)
            if (prog_list[i].pid == pid) {
                /* program died -- now reset item. But not here! */
                XtAppAddTimeOut (app, (unsigned long) 0, reset_btn,
                                    (XtPointer) prog_list[i].drawn_w);
                break;
            }
    }

    (void) signal (SIGCHLD, reset);
}

/* reset_btn() -- reset the sensitivity and pushButtonEnabled resources
** on the drawn button. This cannot be done within the signal
** handler or we might step on an X protocol packet since signals are
** asynchronous. This function is safe because it's called from a timer
*/
void reset_btn (XtPointer closure, XtIntervalId *unused)
{
    Widget drawn_w = (Widget) closure;

    XtVaSetValues (drawn_w, XmNpushButtonEnabled, True, NULL);
    XtSetSensitive (drawn_w, True);
    XmUpdateDisplay (drawn_w);
} 
The output of the program is shown in Figure 26-1.

Figure  26-1 Output of the app_box program

The program in Example 26-1 is almost identical in design to the code fragment that used a work procedure, but it is more like something you might actually write. The program uses DrawnButtons to represent different application programs. The idea is that when a button is pressed, the program corresponding to the image drawn on the button is run. The button turns insensitive for as long as the application is alive. When the user exits the program, the button's state is restored so the user can select it again.

Each button has a data structure associated with it that specifies the file that contains the icon bitmap, an argv that represents the program to be run, the process ID associated with the program's execution, and a handle to the button itself. The callback routine for each button spawns a new process, sets the button to insensitive, and immediately returns control to the main event loop. The process ID is saved in the button's data structure. When the external process terminates, a SIGCHLD signal is sent to the main program and the button is reset.

As a general note, it is crucial that you understand that the new process does not attempt to interact with the widgets in its parent application or read events associated with the same display connection as its parent process. Even though the child has access to the same data structures as the parent, it cannot use its parent's connection to the X server because multiple processes cannot share an X server connection. If a child process intends to interact with the X server, it must close its existing connection and open a new one.

In our application, we play it safe by running a completely new application using execvp(). This system call executes a program provided it can be found in the user's PATH, so we don't need to specify full pathnames to the applications. If the program cannot be found for whatever reason, the child process dies immediately and the reset() signal handler is called by the operating system.

The reset() signal handler is called whenever a child process dies. At this point, the child needs to be reaped and the state of the button needs to be reset. The wait() system call is used to reap the child; this routine can be called from within reset() because it doesn't make any Xlib calls. However, we cannot reset the button's state by calling XtVaSetValues() and XtSetSensitive() because these routines would ultimately result in Xlib calls. Therefore, rather than actually resetting the button in reset(), we call XtAppAddTimeOut() to install a timer routine. This Xt call is safe in a signal handler because it does not make any calls to Xlib; the timer is handled entirely on the client side.

XtAppAddTimeOut() registers a timer procedure that is called after a specified amount of time. Xt's main event processing loop takes care of calling the timer routine after the appropriate time interval. Since we have specified an interval of 0 for the reset_btn() timer, the routine is called immediately after the signal is received and control is passed back to the main event loop. The reset_btn() routine handles restoring the state of the DrawnButton, so that the user can run the associated application again.

In terms of signal handling, there is really one main difference between using a work procedure and using an interval timer. The work procedure is called as soon as the application is idle and waiting for input, while the timer is called after a specified interval.


Additional Issues

There are several loose ends that we need to address. One issue involves the way timers are implemented. You may be thinking, "Isn't a timer another signal in UNIX?" While the answer is yes, what is important is that Xt-timers are not implemented using UNIX signals, but instead using a feature of the select() system call. In this context, select() is used to determine if the X server is sending events to the application (although this function does not actually read any events). The last parameter to select() is a time interval that specifies how long the routine waits before returning whether there is anything to read. Setting this time interval allows Xt to implement what appears to be a timer. As long as there are events to read from the server, however, the timer is inactive, which is why a timer in Xt can only be set in terms of an interval, rather than as a real-time value. It is also why you should never rely on the accuracy of these timers.

Timers are not implemented using UNIX signals for the same reasons that we did not call XtVaSetValues() from within the SIGCHLD signal handler. It is also for this reason that you should not use UNIX-based functions such as sleep() or setitimer() to modify widgets or make Xlib calls. We don't mean to imply that you should not use these functions at all; it's just that the same restrictions apply to UNIX timers as they do to other UNIX signals. If you need to do any X or Xt-related function calls, don't do it from a signal handler. You should install a zero-length interval timeout function using XtAppAddTimeOut() and, when the toolkit invokes your function, call whatever X routines are necessary.Timers of this type are used frequently with clock programs and text widgets. In the case of a clock, the timer advances the second hand, while for a text widget, it causes the insertion cursor to flash.

Another loose end that needs to be tied up involves System V's handling of signals. In most modern versions of UNIX (derived from BSD UNIX), when a signal is delivered to an application, any system call that might be going on is interrupted, the signal handler is called, and when it returns, the system call is allowed to continue. For example, if you are reading in the text of a file using read() and a signal is sent to the application, the read() is suspended while the signal handler is called. After your signal handler returns, the read() is restarted and it returns the actual number of bytes read as if no signal had ever occurred. Under System V, all system calls are interrupted and return an error (with errno set to EINTR). In this case, all of the data read by the read() call is lost.

This situation is a problem in X because read() is used to read events from the X server. If read() fails because a signal is delivered, then the protocol that was being sent by the server is lost, as would be anything we were sending to the server, since the same is true for calls to write(). There really isn't anything you can do about this problem, except, perhaps, for upgrading to a more modern version of UNIX. This problem does not exist with SVR4 or Solaris.

Even system calls in BSD-derived UNIX systems may have problems. If, for example, you call read() from a signal handler that interrupted another read(), you still might not get what you expected because read() is not re-entrant. A function that is re-entrant is one that can be called at any time, even while the function is already being executed.

We're pretty safe with the advice we've given so far, with one exception: calling XtAppAddTimeOut() or XtAppAddWorkProc() eventually requires the allocation of memory to add the new timer or work procedure to the respective list. If your application happens to be allocating memory when a signal is delivered and you try to add a timer or a work procedure, you could make another call to alloc(), which is the lowest-level routine that allocates memory from the system. Unless your version of UNIX has a re-entrant memory allocation system call, your memory stack may be corrupted.4There really isn't anything that you can do about these problems, and there are no official specifications anywhere in the X documents that even address these issues, so the best tactic is to minimize the exposure using timers or work procedures as described here.

Handling Signals in X11R6

In X11R6, three new functions are added in order to handle signals safely in the context of the X Toolkit Intrinsics event queue mechanisms.


XtAppAddSignal

Firstly, XtAppAddSignal() registers a handler with the toolkit which is to be invoked at the appropriate safe point in the Xt event mechanisms; the handler is permitted to call further X routines, in the knowledge that it is safe to do so at that juncture. XtAppAddSignal() returns an opaque handle, an XtSignalId, used to keep track of the registered handler. The full signature of the routine is as follows:

XtSignalId XtAppAddSignal ( XtAppContext          app,
                            XtSignalCallbackProc  handler,
                            XtPointer             client_data)
The handler parameter is the application-specific routine invoked in response to a signal. As usual, client_data is any application-specific data which you want your handler to be passed when invoked. The returned XtSignalId should be stored for subsequent later use. An XtSignalCallbackProc routine is defined as follows:

typedef void (*XtSignalCallbackProc) (XtPointer closure, XtSignalId *id)
The application handler should be registered after the usual operating system signal() call. Note that the purpose of handler is not to catch the signal, but to perform any processing that is required in the knowledge that it is Xt-event-safe. A standard signal handling routine is still required to catch the signal itself. The following code fragment outlines the steps:

void my_safe_handler (XtPointer, XtSignalId *);
void my_signal_catcher ();

XtSignalId my_signal_id;
XtAppContext app;
...
/* standard operating system signal() call */
signal (SIGCHLD, my_signal_catcher);

/* register a handler to process the signal safely */
my_signal_id = XtAppAddSignal (app, my_safe_handler, NULL);
...

XtNoticeSignal

If we are still using the standard operating system signal() routines to catch a signal, and a separate application handler to perform safe processing, what then, you may ask, is the connecting piece of logic which makes the system work? The answer is the routine XtNoticeSignal(). We call this inside our normal operating system signal handling routine to inform Xt that the signal has arrived, secure in the knowledge that this Xt routine is the only intrinsics function we are guaranteed as to its signal safety. XtNoticeSignal() takes as a parameter the XtSignalId of the handler we wish to invoke in response to the current signal delivery. XtNoticeSignal() therefore has the following form:

void XtNoticeSignal (XtSignalId)
Following on from the previous example, we might sketch our signal catching routine as follows:

void my_signal_catcher (int signo)
{
    /* Perform non-X processing */
    ...

    /* Inform the intrinsics of signal arrival */
    XtNoticeSignal (my_signal_id);
}
XtNoticeSignal() therefore replaces the various calls to XtAppAddWorkProc() or XtAppAddTimeOut() within the signal catching routine, as recommended for the X11R5 examples. XtNoticeSignal() simply arranges to call the handler associated with the parameter XtSignalId at a safe point in the Xt event processing loop.


XtRemoveSignal

We can disassociate a handler from the X Intrinsics at any time by calling XtRemoveSignal(). We would do this if we are no longer interested in handling the given signal, or if we wanted to change the handler. The routine is passed as a parameter the XtSignalId returned from a previous call to XtAppAddSignal(). XtRemoveSignal() has the following prototype:

void XtRemoveSignal (XtSignalId)
The source of the signal must be disabled before calling XtRemoveSignal() in order to prevent possible race conditions. The following code fragment outlines the correct scheme of operations:

XtSignalId my_signal_id;
...
/* Disable the signal source */
signal (SIGCHLD, SIG_IGN);

/* Unregister the Intrinsics signal handler */
XtRemoveSignal (my_signal_id);

An Example

The following fragments of code represent the changes required to modify Example 26-1 to use the X11R6 signal notification mechanisms.5We add a new global variable for the XtSignalId returned by XtAppAddSignal(), we modify reset() to call XtNoticeSignal(), and we change reset_btn() to an XtSignalCallbackProc.

.....
XtAppContext  app;          /* application context for the whole program */
GC            gc;           /* used to render pixmaps in the widgets */
XtSignalId    sign_id;      /* X11R6 Signal Registration handle */
ExecItem      *reap = NULL; /* the item which has just terminated */
....
/* reset_btn is now an XtSignalCallbackProc */
void reset_btn (XtPointer, XtSignalId *);
...

main (int argc, char *argv[])
{
    ...

    /* we want to be notified when child programs die */
    (void) signal (SIGCHLD, reset);

    XtSetLanguageProc (NULL, NULL, NULL);
    /* Now that we are using X11R6...*/
    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    /* Register our safe signal handler with the Intrinsics */
    /* Pass the address of the reap pointer as client data */
    sign_id = XtAppAddSignal (app, reset_btn, (XtPointer) &reap);

    /* The rest of main() is exactly as before */
    n = 0;
    XtSetArg (args[n], XmNorientation, XmHORIZONTAL); n++;
    rowcol = XmCreateRowColumn (toplevel, "rowcol", args, n);

    ...
    XtAppMainLoop (app);
}

/* reset() -- a program died, so find out which one it was and
** reset its corresponding DrawnButton widget so it can be reselected
**
** The difference between this and the X11R5 example is the replacing
** of XtAppAddTimeOut() with XtNoticeSignal().
*/
void reset (int unused)
{
    int pid, i;
    #ifdef    SYSV
        int      status;
    #else  /* SYSV */
        union    wait status;
    #endif /* SYSV */

    reap = (ExecItem *) 0;

    /* Basically the same loop as X11R5, except for XtNoticeSignal()
    ** instead of the XtAppAddTimeOut() call
    */
    if ((pid = wait (&status)) != -1) {
        for (i = 0; i < XtNumber (prog_list); i++)
            if (prog_list[i].pid == pid) {
                /* program died -- now reset item. But not here! */
                /* Set up the client data for our signal procedure */
                reap = &prog_list[i];
                /* Inform Xt of signal arrival */
                XtNoticeSignal (sign_id);
                break;
            }
    }

    (void) signal (SIGCHLD, reset);
}

/* reset_btn() -- reset the sensitivity and pushButtonEnabled resources
** on the drawn button.
**
** For X11R6, we have simply changed the signature to that
** of an XtSignalCallbackProc, and pass a pointer to the
** reap ExecItem as client data, instead of a Widget.
*/
void reset_btn (XtPointer closure, XtSignalId *id) 
{
    ExecItem **reap_ptr = (ExecItem **) closure;
    Widget     drawn_w  = (*reap_ptr)->drawn_w;

    XtVaSetValues (drawn_w, XmNpushButtonEnabled, True, NULL);
    XtSetSensitive (drawn_w, True);
    XmUpdateDisplay (drawn_w);
}

Summary

Up until the release of X11R6, the official advice of the X Consortium was that you should not mix signals with X applications. However, there are cases where you must choose the lesser of two evils. The need for signal handling exists and cannot simply be ignored. In X11R6, Xt has support for registering signal handlers, so this problem is no longer a critical issue as far as the support offered by the X Toolkit is concerned. In an X11R5 environment, the approaches given in this chapter should serve you well most of the time.

The most important lesson to learn from this chapter may well be that UNIX signals are potentially dangerous to X applications, or indeed any sort of program that relies on a client-server protocol. They can also be a problem for system calls in an extremely sensitive or real-time environment. The issue is not X specific; X just happens to be an environment where the issue arises. Whenever the operating system can interrupt the client side (or the server side, for that matter), you should be prepared to consider those cases where the client-server protocol may be breached. Using the X11R6 signal notification scheme, the toolkit takes upon itself the task of ensuring X protocol integrity.



1 It should be noted that BSD-style UNIX systems do provide a system call that effectively suspends signal delivery, but it would be too costly to invoke this routine for each Xlib call. Furthermore, it is considered inappropriate for X, a windowing system that is designed to be independent of the operating system, to adopt this system-specific solution.

2 As we will discuss later, there can also be problems with memory allocation in a signal handler.

3 In X11R6, the routine XtVaAppInitialize() is deprecated, and should be replaced with XtVaOpenApplication ().

4 The GNU version of malloc() is re-entrant, so it is safe from this problem.

5 XtVaOpenApplication(), the SessionShell widget class, XtAppAddSignal(), and XtNoticeSignal() are only available in X11R6. XtVaAppInitialize() is considered deprecated in X11R6.






X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer




Copyright © 1991, 1994, 2000, 2001, 2002 O'Reilly & Associates, Inc., Antony Fountain and Jeremy Huxtable. All Rights Reserved.