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.
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.
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.
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.
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.
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.
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.