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.
There are a wide variety of signals, including:
SIGSEGV
-- which is trapped by the hardware,
e.g., when a NULL pointer is dereferenced;
SIGALRM
-- which indicates the the expiration of a
software timer;
SIGINT
-- which occurs when a user types a break
character, such as control-C.
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.
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.
This code registers the
/* 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 */
}
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.
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.
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.
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.
Applications use the
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];
};
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:
As shown below, the
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;
}
register_handler
method is
responsible for registering the static method
Signal_Handler::dispatcher
for that signal with
sigaction
:
The
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;
}
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.
Event_Handler
. For example, the following is a concrete
event handler that handles the SIGINT
signal.
This concrete event handler maintains state for its
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_;
};
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
.
This concrete event handler works just like the
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_;
};
SIGINT_Handler
.
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.
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.
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.wm.edu/~schmidt/ACE.html.
Thanks to Achint Sandhu <sandhu@nortel.ca> for comments on this paper.
[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