Contents Previous Next

21 Signal Handling


This chapter describes the problems that can occur when UNIX signals are mixed with X applications. It explains how signals work, and why they can wreak havoc with X. The chapter also suggests some workarounds that can help an application to minimize the damage. Since the technology is not well thought-through in this area and signals are a fact of life for UNIX applications, something must be done. The techniques described here can be thought of as a practical starting point until the problems are addressed by UNIX and X system developers at a more fundamental level.

When writing an X-based application, programmers sometimes run into one of the most frustrating stumbling blocks of development: how to handle UNIX signals appropriately for an X client application. The problem is difficult to identify because there are rarely any adverse effects to the improper handling of signals. When problems do arise, the unwitting developer may spend weeks trying to unravel the mystery. Symptoms include spurious X protocol errors, lost X events, incomplete window redraws, or even core dumps. These problems arise because many people fail to understand the relationship between UNIX signals and the X protocol design.

To make an analogy for the type of behavior associated with UNIX signals, let's examine a telephone feature most of us are familiar with: call waiting. We start with a situation where you're on the phone talking to someone while someone else is trying to call you. When you have call waiting and another caller is trying to get through, an audio tone is sent to your receiver. (In most modern telephone systems, the other party doesn't hear the tone, but in older systems, they may hear a click.) Quite literally, you've been interrupted by another call, and you have to handle it. What you do next is what UNIX programmers would call interrupt handling.

In the UNIX operating system, signals are delivered to an application (a process) when an abnormal condition occurs. The difference between call waiting and UNIX signals is that there is more than one signal that can be delivered by UNIX, but there is only one tone that call waiting would deliver. These signals may be generated by the user at the keyboard, by another process using the kill system call (which sends a signal to a process ID), by the operating system itself, and so on. For example, job control typically involves one signal that indicates when the process has stopped (SIGTSTP ), and another that indicates when the process has continued ( SIGCONT). Another signal is generated when an application has spawned a new process with fork() and this child process dies. The operating system notifies the parent about the child's death, so that the parent can reap the child. In this case, the operating system delivers a SIGCHLD (SIGCLD for System V) to the parent. Still another signal is SIGFPE (floating point exception), which indicates a division by 0 error. A final example is a segmentation fault (SIGSEGV), where the application has exceeded some internal boundary, like an array index that is out of range.

In all these cases, the signal that the operating system sends to the application is like the tone that you hear when call waiting is activated. The programmer has the ability to specify how these signals should be serviced by trapping them using signal handlers. A signal handler is a function installed for a signal type using the signal() system call, which takes the following form:

   signal(sig_number, function)
The sig_number is the signal identifier, which is a defined symbol like those described above, while function is a routine that you write. If the signal is delivered, the routine is called automatically. What the signal handler function actually does is up to you. For example, if you trap the SIGCHLD signal, when a previously forked process terminates, your signal handler should probably call the wait() system call to reap the child. With SIGFPE, you may want to notify the user that he has entered an invalid value. For SIGSEGV, your program should assume that the application is no longer in a runnable state and clean up after itself by removing temporary files, making backup copies of unfinished files, flushing buffers, or whatever.

Now, what does all this have to do with X applications? Let's say that you want to display an ErrorDialog upon receipt of a signal. Here is where the problem with X and UNIX signals arises. To elaborate, we return to the call-waiting analogy. The original telephone conversation that you were having before call waiting interrupted represents the X protocol communication between an X client and the X server. Now, let's assume that instead of hearing a tone that indicates there is an incoming call, you are immediately transferred to the new call without any notification. In mid-conversation, the original caller can no longer hear you. Furthermore, when you are transferred back to the original caller, the discussion may have progressed without your knowledge. Whatever you were saying would now be completely confusing to anyone listening.

This situation is analogous to what happens when a UNIX signal interrupts a program that might be communicating with the X server via the X protocol. When a UNIX signal is delivered, the operating system immediately branches to your signal handler without notice. If you are in the middle of an X protocol message (an Xlib call) at the time of the signal delivery, and your signal handler also calls an Xlib routine that generates another protocol message, the X server is sent a garbled message. Basically, you started to say something and got interrupted, so now you are saying something completely different. The result is an X protocol error.

What are the chances of this happening? Most of the time, it's pretty slim, especially when you're dealing with signals that are delivered infrequently. An application doesn't spawn a new process or find a floating point exception very often, and it certainly should never find a segmentation fault. It is unlikely that an Xlib call will be interrupted by a UNIX signal, and many people get away with using the signal() system call without programming around potential problems. However, this kind of sloppy programming can lead to problems that are extremely difficult to decipher. The fact is, these problems do occur, so a robust program needs to be ready in case the improbability machine is turned on.

Now that we've explained the problem, let's address the solution. We can learn something from the design of the telephone call waiting system. Fortunately, call waiting doesn't disconnect you from the original caller and transfer you to the new one; it just beeps and lets you switch over when you've had a chance to announce what you're going to do. We'd like to handle UNIX signals this way, but unfortunately, that's not how they work. Signals do interrupt the application without notice. 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 inappropriate for X, a windowing system that is completely independent of the operating system, to use this technique. Instead, we can emulate the behavior of the beep by writing a signal handler that notes the signal's delivery, but does not actually do anything that involves X. Later, when we know it's safe, we can do what we originally intended to do. Now all we have to do is determine when it is safe to take action.

21.1 Handling Signals in 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();

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

   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) {
               sigchld_handler (SIGCHLD); /* add other params as necessary */
               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:
   void
   real_sigchld_handler(sig)
   int sig;
   /* additional parameters differ between BSD and SYSV */
   {
       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. As we will discuss later, there can also be problems with memory allocation in a signal handler. When the real signal handler is called, it can access the data structure and create a dialog using the appropriate Xlib calls.

21.2 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(app_context)
   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(), reset();

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

   /* reset() -- a program died... */
   static void
   real_reset()
   {
       int pid, i;
   #ifdef SYSV
       int status;
   #else
       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(client_data)
   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.

21.3 An Example

The signal handling problem can also be solved with a timer, using the same approach as with a work procedure. the source code demonstrates the use of a timer in a more realistic application. The 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. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* 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
   #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 },
   };

   XtAppContext app;    /* application context for the whole program */
   GC gc;               /* used to render pixmaps in the widgets */
   void reset(), reset_btn(), redraw_button(), exec_prog();

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

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

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       rowcol = XtVaCreateWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel,
           XmNorientation, XmHORIZONTAL,
           NULL);

       /* 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.
            */
           prog_list[i].drawn_w = XtVaCreateManagedWidget ("dbutton",
               xmDrawnButtonWidgetClass, rowcol,
               XmNwidth,             64,
               XmNheight,            64,
               XmNpushButtonEnabled, True,
               XmNshadowType,        XmSHADOW_ETCHED_OUT,
               NULL);
           /* if this button is selected, execute the program */
           XtAddCallback (prog_list[i].drawn_w,
               XmNactivateCallback, exec_prog, &prog_list[i]);

           /* when the resize and expose events come, redraw pixmap */
           XtAddCallback (prog_list[i].drawn_w,
               XmNexposeCallback, redraw_button, pixmap);
           XtAddCallback (prog_list[i].drawn_w,
               XmNresizeCallback, redraw_button, 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(button, client_data, call_data)
   Widget button;
   XtPointer client_data;
   XtPointer call_data;
   {
       Pixmap pixmap = (Pixmap) client_data;
       XmDrawnButtonCallbackStruct *cbs =
           (XmDrawnButtonCallbackStruct *) call_data;
       int srcx, srcy, destx, desty, pix_w, pix_h;
       int drawsize, border;
       Dimension bdr_w, w_width, w_height;
       short hlthick, shthick;
       Window root;

       /* get width and height of the pixmap. don't use srcx and root */
       XGetGeometry (XtDisplay (button), pixmap, &root, &srcx, &srcx,
           &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 1st */
       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 execing again)
    * and set pushButtonEnabled to False to allow shadowType to work.
    */
   void
   exec_prog(drawn_w, client_data, call_data)
   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.0);
       }

       /* 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 pid, i;
   #ifdef SYSV
       int status;
   #else
       union wait status;
   #endif /* SYSV */

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

       for (i = 0; i < XtNumber (prog_list); i++)
           if (prog_list[i].pid == pid) {
               /* program died -- now reset item.  But not here! */
               XtAppAddTimeOut (app, 0, reset_btn, prog_list[i].drawn_w);
               return;
           }

       printf ("Pid #%d ???0, pid); /* error, but not fatal */
   }

   /* 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(drawn_w)
   Widget drawn_w;   /* client_data from XtAppAddTimeOut() */
   {
       XtVaSetValues(drawn_w,
           XmNpushButtonEnabled, True,
           NULL);
       XtSetSensitive (drawn_w, True);
   }
The output of the program is shown in the figure.

figs.eps/V6a.20.01.eps.png
Output of app_box.c

The program in the source code 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.

21.4 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. The GNU version of malloc() is re-entrant, so it is safe from this problem. There 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.

21.5 Summary

The official advice of the X Consortium staff is 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 will have support for signal handlers, so this problem should no longer exist. Until then, however, 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 dangerous to X applications, or 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. 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 protocol may be breached.


Contents Previous Next