Applying Design Patterns to Simplify Signal Handling

Douglas C. Schmidt
Editor, Patterns++
C++ Report
April 1998

It's remarkable how many seemingly vexing programming problems can be resolved by knowledge of basic design patterns. The purpose of this article is to demonstrate how patterns like Singleton [GOF], Adapter [GOF], and Hook Method [Pree] can simplify the development of components that avoid common traps and pitfalls associated with signals and signal handling.


1. Technical Background on Signals

Signals are ``software interrupts'' that allow applications to handle various types of events asynchronously. They are particularly useful for supporting multiple ``flow of control'' in single-threaded processes. Many operating systems support signals, including all versions of UNIX and Win32. In addition, rudimentary support for signals is defined in the ANSI C standard library and the ANSI/ISO C++ standard library.

There are a wide variety of signals, including:

Applications can register to handle these and other signals by passing callback functions to the signal or sigaction OS APIs [Stevens]. Likewise, applications can selectively ignore most signals by registering the SIG_IGN disposition with these OS APIs. As with other OS APIs, such as thread creation routines, the standard signal handling APIs expect C-style functions, rather than C++ objects or C++ member functions.

When the OS raises a signal that an application has registered to handle, the currently executing function is ``pinned'' by the signal handler function. To accomplish this, the OS first pushes a new activation record on top of the process' run-time stack. Next, it automatically invokes the application's pre-registered signal handler function. When this function completes, the OS pops off the signal handler context and returns control back to the point where the program was executing before the signal occurred.


2. Signal Handler Example

The following C code illustrates how to use the SIGINT and SIGQUIT signals to shutdown a UNIX application process when a user types control-C or control-\, respectively. Much of the error handling logic has been omitted to save space and reduce unnecessary details.

/* Global variables that control 
   process shutdown. */
sig_atomic_t graceful_quit = 0;
sig_atomic_t abortive_quit = 0;

/* Signal handler for SIGINT. */
void SIGINT_handler (int signum)
{
  assert (signum == SIGINT);
  graceful_quit = 1;
}

/* Signal handler for SIGQUIT. */
void SIGQUIT_handler (int signum)
{
  assert (signum == SIGQUIT);
  abortive_quit = 1;
}

/* ... */

int main (void)
{
  struct sigaction sa;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = 0;

  /* Register the handler for SIGINT. */
  sa.sa_handler = SIGINT_handler;
  sigaction (SIGINT, &sa, 0);

  /* Register the handler for SIGQUIT. */
  sa.sa_handler =  SIGQUIT_handler;
  sigaction (SIGQUIT, &sa, 0);

  /* Run the main event loop. */
  while (graceful_quit == 0 
         && abortive_quit == 0) 
    do_work ();

  if (abortive_quit == 1) {
    _exit (1);
  }
  else if graceful_quit {
    clean_up ();
    exit (0);
  }

  /* NOTREACHED */
}
This code registers the SIGINT_handler and SIGQUIT_handler functions using the standard POSIX sigaction library function. Once the signal handlers are registered, the UNIX OS run-time system collaborates to callback SIGINT_handler or SIGQUIT_handler asynchronously when SIGINT or SIGQUIT occur, respectively.

In this particular example, the delivery of the SIGINT signal triggers a graceful shut down of the application's event loop. Likewise, the delivery of the SIGQUIT signal triggers an abortive shut down by calling _exit. Unlike exit, _exit doesn't cleanup global resources, such as C standard I/O buffers, etc.

2.1. Common Traps and Pitfalls

Although the signal handling approach shown above works, its use of global variables is antithetical to good object-oriented design. For instance, note how each signal handler function must access the global variables graceful_quit and abortive_quit. In a large software system, the use of global variables can compromises encasulation and information hiding significantly. The use of global variables in C++ is also highly non-portable due to variation in the order of initialization of global statics.

In general, the use of global variables in signal handler functions in C++ programs causes an ``impedance mismatch.'' In particular, it presents a conflict between (1) the global-variables required to interact with signal handlers and (2) the well-structured OO design used for other application processing.

2.2. Solution

As usual, the way out of this programming morass is to apply several common design patterns, such as Singleton [GOF], Adapter [GOF], and Hook Method [Pree]. This section outlines the steps required to accomplish this task.


A. Define a Hook Method Interface

-- We'll start out by defining an Event_Handler interface that contains, among other things, a virtual method called handle_signal:
class Event_Handler
{
public:
  // Hook method for the signal hook method.
  virtual int handle_signal (int signum) = 0;

  // ... other hook methods for other types of
  // events such as timers, I/O, and 
  // synchronization objects.
};
Programmers subclass from Event_Handler and override the handle_signal hook method to perform application-specific behavior in response to designated signals. Instances of these subclasses, i.e., concrete event handlers, are the targets of the hook method callback. These callbacks are invoked through a Signal_Handler singleton, which is described next.


B. Define a Signal Handler Component as a Singleton

-- The Signal_Handler component is a singleton that encapsulates a collection of concrete event handlers, which applications register to process various types of signals. The Signal_Handler implementation shown below is based on the Singleton [GOF], Adapter [GOF], and Hook Method [Pree] patterns. The Singleton pattern provides a factory for a singular (sole) instance. This pattern is used for the Signal_Handler component since standard OS processes that support signals have just one table that maps the occurrence of a signal to its C-style signal handler function.

The Adapter pattern transforms one interface into another interface that is more appropriate for clients. This pattern is used in the Signal_Handler component to adapt the C-style signal handling functions required by sigaction into the C++ methods defined by concrete event handlers.

The Hook Method pattern enables general-purpose algorithm code to perform some steps supplied by an application-specific callback method. This pattern decouples the generic signal registration and dispatching logic in the Signal_Handler component from the application-specific behavior performed when the handle_signal hook method is invoked on a concrete event handler.

The following C++ code illustrates the application of these patterns to develop the Signal_Handler singleton component.

class Signal_Handler
{
public:
 // Entry point.
 static Signal_Handler *instance (void);

 // Register an event handler <eh> for <signum>
 // and return a pointer to any existing <Event_Handler>
 // that was previously registered to handle <signum>.
 Event_Handler *register_handler (int signum,
		                  Event_Handler *eh);

 // Remove the <Event_Handler> for <signum>
 // by setting the slot in the <signal_handlers_>
 // table to NULL.
 int remove_handler (int signum);

private:
 // Ensure we're a Singleton.
 Signal_Handler (void); 

 // Singleton pointer.
 static Signal_Handler *instance_;

 // Entry point adapter installed into <sigaction> 
 // (must be a static method or a stand-alone 
 // extern "C" function).
 static void dispatcher (int signum);

 // Table of pointers to concrete <Event_Handler>s
 // registered by applications.  NSIG is the number of 
 // signals defined in </usr/include/sys/signal.h>.
 static Event_Handler *signal_handlers_[NSIG];
};
Applications use the Signal_Handler's register_handler method to install the appropriate Event_Handler * for a particular signal. If an existing Event_Handler * is registered for this signal it is returned from the register_handler method. This design enables ``signal chaining,'' which makes it possible to temporarily replace the existing Event_Handler for a signal with another handler within the scope of a function or block. This feature is useful since signal handlers don't otherwise ``nest.''

The following example illustrates the use of signal chaining to ensure that the EPIPE is handled appropriately during a write system call to a network socket connection:

class EPIPE_Handler : public Event_Handler
{
public:
  virtual int handle_signal (int signum);
  // Handle the SIGPIPE signal appropriately.

  // ...
};

EPIPE_Handler epipe_handler;

ssize_t
sigsafe_write (int socket, 
               const char *buf,
               size_t len)
{
  Event_Handler *oeh;

  // Temporarily replace any existing 
  // signal handler with a special handler
  // for the EPIPE signal for the duration 
  // of this function.
  oeh = Signal_Handler::instance ()->
          register_handler (SIGPIPE, 
                            &epipe_handler);

  ssize_t n = write (socket, buf, len);

  // Restore the original signal handler.
  Signal_Handler::instance ()->
    register_handler (SIGPIPE, oeh);

  return n;
}
As shown below, the register_handler method is responsible for registering the static method Signal_Handler::dispatcher for that signal with sigaction:

Event_Handler *
Signal_Handler::register_handler (int signum,
				  Event_Handler *eh)
{
  // Copy the <old_eh> from the <signum> slot in 
  // the <signal_handlers_> table.
  Event_Handler *old_eh =
    Signal_Handler::signal_handlers_[signum];

  // Store <eh> into the <signum> slot in the
  // <signal_handlers_> table.
  Signal_Handler::signal_handlers_[signum] = eh;
 
  // Register the <dispatcher> to handle this
  // <signum>.
  struct sigaction sa;
  sa.sa_handler = Signal_Handler::dispatcher;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = 0;
  sigaction (signum, &sa, 0);

  return old_eh;
}
The Signal_Handler's dispatcher method is called back by the signal dispatching mechanism in the OS when the designated signum is raised. This static method plays the role of an adapter, which transforms the C-style signal handling function required by sigaction into the C++ handle_signal method that is overridden by concrete event handlers. The dispatcher simply indexes into the <signum> slot of the <signal_handlers_> and invokes the concrete event handler's handle_signal method, as follows:
   
void Signal_Handler::dispatcher (int signum)
{
  // Perform a sanity check...
  if (Signal_handler::signal_handlers_[signum] != 0)
    // Dispatch the handler's hook method.
    Signal_handler::signal_handlers_
      [signum]->handle_signal (signum);
}

Note how the signum received by the dispatcher is passed through to the handle_signal hook method. This enables handle_signal to determine which signal it is handling. Therefore, the same concrete event handler can be programmed to handle different signals in different ways.


C. Define the Concrete Event Handlers

-- The next step is to define concrete event handlers that derive from Event_Handler. For example, the following is a concrete event handler that handles the SIGINT signal.

class SIGINT_Handler : public Event_Handler
{
public:
  SIGINT_Handler (void)
    : graceful_quit_ (0) {}

  // Hook method.
  virtual int handle_signal (int signum)
  {
    assert (signum == SIGINT);
    this->graceful_quit_ = 1;
  }

  // Accessor.
  sig_atomic_t graceful_quit (void)
  { return this->graceful_quit_; }

private:
  sig_atomic_t graceful_quit_;
};
This concrete event handler maintains state for its graceful_quit_ data member. Its handle_signal hook method sets the value of graceful_quit_ to 1. As shown below, this value instructs the application to terminate gracefully.

The following is a concrete event handler that handles SIGQUIT.

class SIGQUIT_Handler : public Event_Handler
{
public:
  SIGQUIT_Handler (void)
    : abortive_quit_ (0) {}

  // Hook method.
  virtual int handle_signal (int signum)
  {
    assert (signum == SIGQUIT);
    this->abortive_quit_ = 1;
  }

  // Accessor.
  sig_atomic_t abortive_quit (void)
  { return this->abortive_quit_; }

private:
  sig_atomic_t abortive_quit_;
};
This concrete event handler works just like the SIGINT_Handler.


D. Register Event Handler Subclasses with Signal Handler Singleton

-- An application can use the Signal_Handler component and the Event_Handler base class to define concrete event handlers and register them using Signal_Handler::instance, as follows:
int main (void)
{
  SIGINT_Handler sigint_handler;
  SIGQUIT_Handler sigquit_handler;

  // Register the handler for SIGINT.  
  Signal_Handler::instance ()->register_handler 
    (SIGINT, &sigint_handler);

  // Register the handler for SIGQUIT.
  Signal_Handler::instance ()->register_handler 
    (SIGQUIT, &sigquit_handler);

  // Run the main event loop. 
  while (sigint_handler.graceful_quit () == 0
         && sigquit_handler.abortive_quit () == 0) 
    do_work ();

  if (sigquit_handler.abortive_quit () == 1) 
    _exit (1);
  else /* if sigint_handler.graceful_quit () */ {
    clean_up ();
    return 1;
  }
}
By using the Signal_Handler component shown above, we can leverage the Singleton, Adapter, and Hook Method patterns to invoke the handle_signal hook method of the concrete event handlers in response to SIGINT and SIGQUIT signals. This design is an improvement over the original C-style approach since it does not require any global or static variables. In particular, the state of each concrete event handler can be maintained locally within an object.

There are many variations on this basic design. For instance, we could support other sigaction semantics using various flags and sigmasks. Likewise, we could allowing multple Event_Handlers to be registered per-signal. In addition, we could support the POSIX extended signal API. However, the fundamental essence of all these solution involves recognizing the power of several basic patterns: Singleton, Adapter, and Hook Method.


3. Caveats

Although signals are powerful and the Signal_Handler design presented in this article is flexible, the use of signals can still be quite problematic. In particular, signals are hard to program and use correctly. For instance, the operations that can occur in signal handler context are very limited. Therefore, highly portable programs should only set variables of type sig_atomic_t and then return. Likewise, POSIX defines a very limited set of system calls that can safely be invoked in a signal handler. Unfortunately, these constraints can be very limiting since signal handlers may want to perform arbitrary handling logic when a signal occurs.

Programming with signals also can be overly complex due to timing problems and race conditions when the current thread of control is preempted by a signal handler. Likewise, debugging programs that use asynchronous signals is hard since events occur at different points of time during program execution.

However, if you decide that your applications require signals and signal handling, knowledge of the patterns and OO design components described in this article can be invaluable to simplify your programming tasks.


4. Concluding Remarks

The Signal_Handler and Event_Handler classes described in this article are loosely based on components in the ACE framework [Schmidt]. ACE is an OO framework that implements many core design patterns for concurrent communication software. It provides a rich set of reusable C++ wrappers and framework components that perform common communication software tasks across a range of OS platforms.

The communication software tasks provided by ACE include event demultiplexing and event handler dispatching, signal handling, service initialization, interprocess communication, shared memory management, message routing, dynamic (re)configuration of distributed services, concurrent execution and synchronization. ACE is freely available via the WWW at URL www.cs.wustl.edu/~schmidt/ACE.html.

Thanks to Achint Sandhu <sandhu@nortel.ca> for comments on this paper.


References

[GoF] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Reading, MA: Addison-Wesley, 1995.

[Pree] Wolfgang Pree, Design Patterns for Object-Oriented Software Development, Addison-Wesley, Reading, MA, 1994.

[Schmidt] Douglas C. Schmidt, ACE: an Object-Oriented Framework for Developing Distributed Applications, Proceedings of the 6th USENIX C++ Technical Conference, Cambridge, Massachusetts, April, 1994.

[Stevens] W. Richard Stevens, ``UNIX Network Programming,'' Second Edition, Prentice Hall, Englewood Cliffs, NJ, 1997.


Back to C++ Report Editorials home page.

Last modified 11:34:51 CDT 28 September 2006