Browse Source

Merge branch 'trac3407'

Merge in D2 signal handling.
Thomas Markwalder 11 years ago
parent
commit
0ca77191a3

+ 1 - 0
src/bin/d2/Makefile.am

@@ -63,6 +63,7 @@ b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += io_service_signal.cc io_service_signal.h
 b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
 b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
 b10_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.h

+ 85 - 9
src/bin/d2/d2.dox

@@ -52,7 +52,7 @@ base are shown in the following class diagram:
 - isc::d2::DControllerBase - provides all of the services necessary to manage
 an application process class derived from isc::d2::DProcess. These services include:
     - Command line argument handling
-    - Process instantiation and initialization0
+    - Process instantiation and initialization
     - Support for stand-alone execution
     - Support for integrated operation as a BUNDY module (session management
       and event handling)
@@ -84,10 +84,6 @@ an application process class derived from isc::d2::DProcess. These services incl
 
     The file may contain an arbitrary number of other modules.
 
-    @todo DControllerBase will soon support dynamic reloading of the
-    configuration file upon receipt of the SIGHUP signal, and graceful
-    shutdown upon receipt of either SIGTERM or SIGINT.
-
     @todo Eventually, some sort of secure socket interface which supports remote
     control operations such as configuration changes or status reporting will
     likely be implemented.
@@ -118,6 +114,90 @@ through the CPL layer:
 
 The CPL classes will likely move into a common library.
 
+@subsection cplSignals CPL Signal Handling
+
+CPL supports interaction with the outside world via OS signals. The default
+implementation supports the following signal driven behavior:
+- SIGHUP receipt of this signal will cause a reloading of the configuration
+file.
+- SIGINT/SIGTERM receipt of either of these signals will initiate an
+orderly shutdown.
+
+CPL applications wait for for process asynchronous IO events through
+isc::asiolink::IOService::run() or its variants.  These calls are not
+interrupted upon signal receipt as is the select() function and while
+boost::asio provides a signal mechanism it requires linking in additional
+libraries.  Therefore, CPL provides its own signal handling mechanism to
+propagate an OS signal such as SIGHUP to an IOSerivce as a ready event with a
+callback.
+
+isc::d2::DControllerBase uses two mechanisms to carry out signal handling.  It
+uses isc::util::SignalSet to catch OS signals, and isc::d2:IOSignalQueue to
+propagate them to its isc::asiolink::IOService as instances of
+isc::d2::IOSignal.
+
+This CPL signaling class hierarchy is illustrated in the following diagram:
+
+@image html cpl_signal_classes.svg "CPL Signal Classes"
+
+The mechanics of isc::d2::IOSignal are straight forward. Upon construction it
+is given the target isc::asiolink::IOService, the value of the OS signal to
+send (e.g. SIGINT, SIGHUP...), and an isc::d2::IOSignalHandler.  This handler
+should contain the logic the caller would normally execute in its OS signal
+handler. Each isc::d2::IOSignal instance has a unique identifier called its
+sequence_id.
+
+Internally, IOSignal creates a 1 ms, one-shot timer, on the given
+IOService.  When the timer expires its event handler invokes the caller's
+IOSignalHandler passing it the sequence_id of the IOSignal.
+
+Sending IOSignals is done through an isc::d2::IOSignalQueue.  This class is
+used to create the signals, house them until they are delivered, and dequeue
+them so they can be been handled.  To generate an IOSignal when an OS signal
+arrives, the process's OS signal handler need only call
+isc::d2::IOSignalQueue::pushSignal() with the appropriate values.
+
+To dequeue the IOSignal inside the caller's IOSignalHandler, one simply
+invokes isc::d2::IOSignalQueue::popSignal() passing it the sequence_id
+parameter passed to the handler.  This method returns a pointer to
+instigating IOSignal from which the value of OS signal (i.e. SIGINT,
+SIGUSR1...) can be obtained.  Note that calling popSignal() removes the
+IOSignalPtr from the queue, which should reduce its reference count to
+zero upon exiting the handler (unless a deliberate copy of it is made).
+
+A typical isc::d2::IOSignalHandler might be structured as follows:
+@code
+
+    void processSignal(IOSignalId sequence_id) {
+    // Pop the signal instance off the queue.
+    IOSignalPtr signal = io_signal_queue_->popSignal(sequence_id);
+
+    int os_signal_value = signal->getSignum();
+    :
+    // logic based on the signal value
+    :
+    }
+
+@endcode
+
+IOSignal's handler invocation code will catch, log ,and then swallow any
+exceptions thrown by an IOSignalHandler.  This is done to protect the integrity
+IOService context.
+
+CPL integrates the use of the two mechanisms by registering the method,
+isc::d2::DControllerBase::osSignalHandler(), as the
+isc::util::SignalSet::onreceipt_handler_.  This configures SignalSet's internal
+handler to invoke the method each time a signal arrives.  When invoked, this
+method will call isc::d2::IOSignalQueue::pushSignal() to create an
+isc::d2::IOSignal, passing in the OS signal received and
+isc::d2::DControllerBase::ioSignalHandler() to use as the IOSignal's
+ready event handler
+
+The following sequence diagram depicts the initialization of signal handling
+during startup and the subsequent receipt of a SIGHUP:
+
+@image html cpl_signal_sequence.svg "CPL Signal Handling Sequence"
+
 @section d2ProcesDerivation D2's CPL Derivations
 
 D2's core application classes are DDNS-specific derivations of the CPL as show
@@ -380,8 +460,4 @@ when D2UpdateMgr creates and starts executing a transaction:
 
 @image html nc_trans_sequence.svg "Transaction Execution Sequence"
 
-
-
-
-
 */

+ 29 - 4
src/bin/d2/d2_messages.mes

@@ -32,15 +32,15 @@ new configuration. It is output during server startup, and when an updated
 configuration is committed by the administrator.  Additional information
 may be provided.
 
+% DCTL_CONFIG_FILE_LOAD_FAIL %1 configuration could not be loaded from file: %2
+This fatal error message indicates that the application attempted to load its
+initial configuration from file and has failed. The service will exit.
+
 % DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
 This critical error message indicates that the initial application
 configuration has failed. The service will start, but will not
 process requests until the configuration has been corrected.
 
-% DCTL_CONFIG_FILE_LOAD_FAIL %1 configuration could not be loaded from file: %2
-This fatal error message indicates that the application attempted to load its
-initial configuration from file and has failed. The service will exit.
-
 % DCTL_CONFIG_START parsing new configuration: %1
 A debug message indicating that the application process has received an
 updated configuration and has passed it to its configuration manager
@@ -129,6 +129,16 @@ mapping additions which were received and accepted by an appropriate DNS server.
 This is a debug message that indicates that the application has DHCP_DDNS
 requests in the queue but is working as many concurrent requests as allowed.
 
+% DHCP_DDNS_CFG_FILE_RELOAD_ERROR configuration reload failed: %1, reverting to current configuration.
+This is an error message indicating that the application attempted to reload
+its configuration from file and encountered an error.  This is likely due to
+invalid content in the configuration file.  The application should continue
+to operate under its current configuration.
+
+% DHCP_DDNS_CFG_FILE_RELOAD_SIGNAL_RECVD OS signal %1 received, reloading configuration from file: %2
+This is an informational message indicating the application has received a signal
+instructing it to reload its configuration from file.
+
 % DHCP_DDNS_CLEARED_FOR_SHUTDOWN application has met shutdown criteria for shutdown type: %1
 This is an informational message issued when the application has been instructed
 to shutdown and has met the required criteria to exit.
@@ -452,6 +462,16 @@ in event loop.
 This is informational message issued when the application has been instructed
 to shut down by the controller.
 
+% DHCP_DDNS_SHUTDOWN_SIGNAL_RECVD OS signal %1 received, starting shutdown
+This is an informational message indicating the application has received a signal
+instructing it to shutdown.
+
+% DHCP_DDNS_SIGNAL_ERROR signal handler for signal %1, threw an unexpected exception: %2
+This is an error message indicating that the application encountered an unexpected
+error after receiving a signal.  This is a programmatic error and should be
+reported.  While The application will likely continue to operating, it may be
+unable to respond correctly to signals.
+
 % DHCP_DDNS_STARTING_TRANSACTION Transaction Key: %1
 This is a debug message issued when DHCP-DDNS has begun a transaction for
 a given request.
@@ -468,6 +488,11 @@ message but the attempt to send it suffered a unexpected error. This is most
 likely a programmatic error, rather than a communications issue. Some or all
 of the DNS updates requested as part of this request did not succeed.
 
+% DHCP_DDNS_UNSUPPORTED_SIGNAL ignoring reception of unsupported signal: %1
+This is a debug message indicating that the application received an unsupported
+signal.  This a programmatic error indicating the application has registered to
+receive the signal, but for which no processing logic has been added.
+
 % DHCP_DDNS_UPDATE_REQUEST_SENT %1 for transaction key: %2 to server: %3
 This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
 server.

+ 90 - 1
src/bin/d2/d_controller.cc

@@ -30,7 +30,8 @@ DControllerBasePtr DControllerBase::controller_;
 DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
     : app_name_(app_name), bin_name_(bin_name),
       verbose_(false), spec_file_name_(""),
-      io_service_(new isc::asiolink::IOService()){
+      io_service_(new isc::asiolink::IOService()),
+      signal_set_(), io_signal_queue_() {
 }
 
 void
@@ -75,6 +76,7 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
                    "Application Process initialization failed: " << ex.what());
     }
 
+
     LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);
 
     // Step 3 is to load configuration from file.
@@ -91,10 +93,24 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
     // Everything is clear for launch, so start the application's
     // event loop.
     try {
+        // Now that we have a proces, we can set up signal handling.
+        initSignalHandling();
+
         runProcess();
+
+        /// @todo Once Trac #3470 is addressed this will not be necessary.
+        /// SignalSet uses statics which do not free in predicatable order.
+        if (signal_set_) {
+            signal_set_->clear();
+        }
     } catch (const std::exception& ex) {
         LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
                   .arg(app_name_).arg(ex.what());
+        /// @todo Once Trac #3470 is addressed this will not be necessary.
+        /// SignalSet uses statics which do not free in predicatable order.
+        if (signal_set_) {
+            signal_set_->clear();
+        }
         isc_throw (ProcessRunError,
                    "Application process event loop failed: " << ex.what());
     }
@@ -288,6 +304,79 @@ DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
 }
 
 void
+DControllerBase::initSignalHandling() {
+    /// @todo block everything we don't handle
+
+    // Create our signal queue.
+    io_signal_queue_.reset(new IOSignalQueue(io_service_));
+
+    // Install the on-receipt handler
+    util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase::
+                                                     osSignalHandler,
+                                                     this, _1));
+    // Register for the signals we wish to handle.
+    signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM));
+}
+
+bool
+DControllerBase::osSignalHandler(int signum) {
+    // Create a IOSignal to propagate the signal to IOService.
+    io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase::
+                                                     ioSignalHandler,
+                                                     this, _1));
+    return (true);
+}
+
+void
+DControllerBase::ioSignalHandler(IOSignalId sequence_id) {
+    // Pop the signal instance off the queue.  This should make us
+    // the only one holding it, so when we leave it should be freed.
+    // (note that popSignal will throw if signal is not found, which
+    // in turn will caught, logged, and swallowed by IOSignal callback
+    // invocation code.)
+    IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id);
+
+    // Now call virtual signal processing method.
+    processSignal(io_signal->getSignum());
+}
+
+void
+DControllerBase::processSignal(int signum) {
+    switch (signum) {
+        case SIGHUP:
+        {
+            LOG_INFO(dctl_logger, DHCP_DDNS_CFG_FILE_RELOAD_SIGNAL_RECVD)
+                     .arg(signum).arg(getConfigFile());
+            int rcode;
+            isc::data::ConstElementPtr comment = isc::config::
+                                                 parseAnswer(rcode,
+                                                             configFromFile());
+            if (rcode != 0) {
+                LOG_ERROR(dctl_logger, DHCP_DDNS_CFG_FILE_RELOAD_ERROR)
+                          .arg(comment->stringValue());
+            }
+
+            break;
+        }
+
+        case SIGINT:
+        case SIGTERM:
+        {
+            LOG_INFO(dctl_logger, DHCP_DDNS_SHUTDOWN_SIGNAL_RECVD)
+                     .arg(signum);
+            isc::data::ElementPtr arg_set;
+            executeCommand(SHUT_DOWN_COMMAND, arg_set);
+            break;
+        }
+
+        default:
+            LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT,
+                      DHCP_DDNS_UNSUPPORTED_SIGNAL).arg(signum);
+            break;
+    }
+}
+
+void
 DControllerBase::usage(const std::string & text)
 {
     if (text != "") {

+ 74 - 7
src/bin/d2/d_controller.h

@@ -19,9 +19,11 @@
 #include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d_process.h>
+#include <d2/io_service_signal.h>
 #include <dhcpsrv/daemon.h>
 #include <exceptions/exceptions.h>
 #include <log/logger_support.h>
+#include <util/signal_set.h>
 
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
@@ -78,9 +80,12 @@ typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
 /// creation.
 /// It provides the callback handlers for command and configuration events
 /// which could be triggered by an external source.  Such sources are intended
-/// to be registed with and monitored by the controller's IOService such that
+/// to be registered with and monitored by the controller's IOService such that
 /// the appropriate handler can be invoked.
 ///
+/// DControllerBase provides dynamic configuration file reloading upon receipt
+/// of SIGHUP, and graceful shutdown upon receipt of either SIGINT or SIGTERM.
+///
 /// NOTE: Derivations must supply their own static singleton instance method(s)
 /// for creating and fetching the instance. The base class declares the instance
 /// member in order for it to be available for static callback functions.
@@ -102,8 +107,9 @@ public:
     /// 1. parse command line arguments
     /// 2. instantiate and initialize the application process
     /// 3. load the configuration file
-    /// 4. start and wait on the application process event loop
-    /// 5. exit to the caller
+    /// 4. initialize signal handling
+    /// 5. start and wait on the application process event loop
+    /// 6. exit to the caller
     ///
     /// It is intended to be called from main() and be given the command line
     /// arguments.
@@ -155,7 +161,7 @@ public:
     ///                   configuration data for this controller's application
     ///
     ///     module-config: a set of zero or more JSON elements which comprise
-    ///                    the application'ss configuration values
+    ///                    the application's configuration values
     /// @endcode
     ///
     /// The method extracts the set of configuration elements for the
@@ -279,6 +285,24 @@ protected:
         return ("");
     }
 
+    /// @brief Application-level signal processing method.
+    ///
+    /// This method is the last step in processing a OS signal occurrence.  It
+    /// is invoked when an IOSignal's internal timer callback is executed by
+    /// IOService.  It currently supports the following signals as follows:
+    /// -# SIGHUP - instigates reloading the configuration file
+    /// -# SIGINT - instigates a graceful shutdown
+    /// -# SIGTERM - instigates a graceful shutdown
+    /// If received any other signal, it will issue a debug statement and
+    /// discard it.
+    /// Derivations wishing to support additional signals could override this
+    /// method with one that: processes the signal if it is one of additional
+    /// signals, otherwise invoke this method (DControllerBase::processSignal())
+    /// with signal value.
+    /// @todo Provide a convenient way for derivations to register additional
+    /// signals.
+    virtual void processSignal(int signum);
+
     /// @brief Supplies whether or not verbose logging is enabled.
     ///
     /// @return returns true if verbose logging is enabled.
@@ -372,7 +396,7 @@ protected:
     /// to begin its shutdown process.
     ///
     /// Note, it is assumed that the process of shutting down is neither
-    /// instanteneous nor synchronous.  This method does not "block" waiting
+    /// instantaneous nor synchronous.  This method does not "block" waiting
     /// until the process has halted.  Rather it is used to convey the
     /// need to shutdown.  A successful return indicates that the shutdown
     /// has successfully commenced, but does not indicate that the process
@@ -381,11 +405,48 @@ protected:
     /// @return returns an Element that contains the results of shutdown
     /// command composed of an integer status value (0 means successful,
     /// non-zero means failure), and a string explanation of the outcome.
+    ///
+    /// @param args is a set of derivation-specific arguments (if any)
+    /// for the shutdown command.
     isc::data::ConstElementPtr shutdownProcess(isc::data::ConstElementPtr args);
 
+    /// @brief Initializes signal handling
+    ///
+    /// This method configures the controller to catch and handle signals.
+    /// It instantiates an IOSignalQueue, registers @c osSignalHandler() as
+    /// the SignalSet "on-receipt" handler, and lastly instantiates a SignalSet
+    /// which listens for SIGHUP, SIGINT, and SIGTERM.
+    void initSignalHandling();
+
+    /// @brief Handler for processing OS-level signals
+    ///
+    /// This method is installed as the SignalSet "on-receipt" handler. Upon
+    /// invocation, it uses the controller's IOSignalQueue to schedule an
+    /// IOSignal with for the given signal value.
+    ///
+    /// @param signum OS signal value (e.g. SIGINT, SIGUSR1 ...) to received
+    ///
+    /// @return SignalSet "on-receipt" handlers are required to return a
+    /// boolean indicating if the OS signal has been processed (true) or if it
+    /// should be saved for deferred processing (false).  Currently this
+    /// method processes all received signals, so it always returns true.
+    bool osSignalHandler(int signum);
+
+    /// @brief Handler for processing IOSignals
+    ///
+    /// This method is supplied as the callback when IOSignals are scheduled.
+    /// It fetches the IOSignal for the given sequence_id and then invokes
+    /// the virtual method, @c processSignal() passing it the signal value
+    /// obtained from the IOSignal.  This allows derivations to supply a
+    /// custom signal processing method, while ensuring IOSignalQueue
+    /// integrity.
+    ///
+    /// @param sequence_id id of the IOSignal instance "received"
+    void ioSignalHandler(IOSignalId sequence_id);
+
     /// @brief Fetches the current process
     ///
-    /// @return the a pointer to the current process instance.
+    /// @return a pointer to the current process instance.
     DProcessBasePtr getProcess() {
         return (process_);
     }
@@ -403,7 +464,7 @@ private:
     std::string app_name_;
 
     /// @brief Name of the service executable.
-    /// By convention this matches the executable nam. It is also used to
+    /// By convention this matches the executable name. It is also used to
     /// establish the logger name.
     std::string bin_name_;
 
@@ -422,6 +483,12 @@ private:
     /// @brief Shared pointer to an IOService object, used for ASIO operations.
     IOServicePtr io_service_;
 
+    /// @brief Set of registered signals to handle.
+    util::SignalSetPtr signal_set_;
+
+    /// @brief Queue for propagating caught signals to the IOService.
+    IOSignalQueuePtr io_signal_queue_;
+
     /// @brief Singleton instance value.
     static DControllerBasePtr controller_;
 

+ 393 - 0
src/bin/d2/images/cpl_signal_classes.svg

@@ -0,0 +1,393 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="804" height="836" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="344" y="10" width="3" height="495" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="186" y="502" width="161" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="182" y="6" width="162" height="496" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="263" y="20">DControllerBase</text>
+	<line stroke="black" stroke-opacity="1" x1="182" y1="22" x2="344" y2="22" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="36">app_name_</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="50">bin_name_</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="64">verbose_</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="78">spec_file_name_</text>
+	<line stroke="black" stroke-opacity="1" x1="182" y1="80" x2="344" y2="80" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="94">DControllerBase()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="108">~DControllerBase()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="122">launch()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="136">updateConfig()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="150">configFromFile()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="164">executeCommand()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="178">getAppName()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="192">getBinName()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="206">customOption()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="186" y="220">createProcess()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="234">customControllerCommand()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="248">getUsageText()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="262">getCustomOpts()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="276">processSignal()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="290">isVerbose()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="304">setVerbose()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="318">getIOService()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="332">getSpecFileName()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="346">setSpecFileName()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="186" y="360">getController()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="186" y="374">setController()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="388">parseArgs()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="402">initProcess()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="416">runProcess()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="430">shutdownProcess()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="444">initSignalHandling()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="458">osSignalHandler()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="472">ioSignalHandler()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="486">getProcess()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="500">usage()</text>
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="737" y="207" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="667" y="251" width="73" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="663" y="203" width="74" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="700" y="217">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="700" y="233">IOSignalPtr</text>
+	<line stroke="black" stroke-opacity="1" x1="663" y1="235" x2="737" y2="235" />
+	<line stroke="black" stroke-opacity="1" x1="663" y1="243" x2="737" y2="243" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="467" y="353" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="371" y="397" width="99" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="367" y="349" width="100" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="417" y="363">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="417" y="379">IOSignalHandler</text>
+	<line stroke="black" stroke-opacity="1" x1="367" y1="381" x2="467" y2="381" />
+	<line stroke="black" stroke-opacity="1" x1="367" y1="389" x2="467" y2="389" />
+</g>
+	<rect fill="#c0ffff" stroke="none" stroke-opacity="1" x="323" y="800" width="268" height="25" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" x="323" y="812">Blue class integrate signal handling into D2</text>
+<g>
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="348" y1="278" x2="419" y2="278" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="419" y1="348" x2="425" y2="342" />
+	<line stroke="black" stroke-opacity="1" x1="419" y1="348" x2="413" y2="342" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="419" y1="278" x2="419" y2="348" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="751" y="309" width="3" height="103" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="653" y="409" width="101" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="649" y="305" width="102" height="104" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="700" y="319">IOSignal</text>
+	<line stroke="black" stroke-opacity="1" x1="649" y1="321" x2="751" y2="321" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="335">signum_</text>
+	<line stroke="black" stroke-opacity="1" x1="649" y1="337" x2="751" y2="337" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="351">IOSignal()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="365">~IOSignal()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="653" y="379">nextSequenceId()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="393">getSequenceId()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="407">getSignum()</text>
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="698" y1="304" x2="703" y2="297" />
+	<line stroke="black" stroke-opacity="1" x1="698" y1="304" x2="691" y2="298" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="697" y1="255" x2="698" y2="304" />
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="538" y="197">signals_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="100" y="444">signal_set_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="566" y="314">sequence_id_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="193" y="703">onreceipt_handler_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="454" y="27">io_signal_queue_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="599" y="729">io_service_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="433" y="729">io_service_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="549" y="424">sequence_id_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="429" y="419">handler_</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="724" y="539">timer_</text>
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="669" y="10" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="563" y="54" width="109" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="559" y="6" width="110" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="614" y="20">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="614" y="36">IOSignalQueuePtr</text>
+	<line stroke="black" stroke-opacity="1" x1="559" y1="38" x2="669" y2="38" />
+	<line stroke="black" stroke-opacity="1" x1="559" y1="46" x2="669" y2="46" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="558" y1="31" x2="552" y2="25" />
+	<line stroke="black" stroke-opacity="1" x1="558" y1="31" x2="552" y2="37" />
+	<line stroke="black" stroke-opacity="1" x1="348" y1="31" x2="558" y2="31" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="348,31 354,25 360,31 354,37" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="668" y="77" width="3" height="95" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="568" y="169" width="103" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="564" y="73" width="104" height="96" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="616" y="87">IOSignalQueue</text>
+	<line stroke="black" stroke-opacity="1" x1="564" y1="89" x2="668" y2="89" />
+	<line stroke="black" stroke-opacity="1" x1="564" y1="97" x2="668" y2="97" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="111">IOSignalQueue()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="125">~IOSignalQueue()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="139">pushSignal()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="153">popSignal()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="167">clear()</text>
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="618" y1="72" x2="624" y2="66" />
+	<line stroke="black" stroke-opacity="1" x1="618" y1="72" x2="612" y2="66" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="618" y1="58" x2="618" y2="72" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="168" y="532" width="3" height="285" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="24" y="814" width="147" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="20" y="528" width="148" height="286" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="94" y="542">SignalSet</text>
+	<line stroke="black" stroke-opacity="1" x1="20" y1="544" x2="168" y2="544" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="558">local_signals_</text>
+	<line stroke="black" stroke-opacity="1" x1="20" y1="560" x2="168" y2="560" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="574">SignalSet()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="588">SignalSet()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="602">SignalSet()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="616">~SignalSet()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="630">add()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="644">clear()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="658">getNext()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="672">handleNext()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="686">remove()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="24" y="700">setOnReceiptHandler()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="24" y="714">clearOnReceiptHandler()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="24" y="728">invokeOnReceiptHandler()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="742">block()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="756">erase()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="770">insert()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="784">maskSignals()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="798">popNext()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="812">unblock()</text>
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="566" y="206" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="490" y="250" width="79" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="486" y="202" width="80" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="526" y="216">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="526" y="232">IOSignalMap</text>
+	<line stroke="black" stroke-opacity="1" x1="486" y1="234" x2="566" y2="234" />
+	<line stroke="black" stroke-opacity="1" x1="486" y1="242" x2="566" y2="242" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="585" y="712" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="509" y="756" width="79" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="505" y="708" width="80" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="545" y="722">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="545" y="738">IOServicePtr</text>
+	<line stroke="black" stroke-opacity="1" x1="505" y1="740" x2="585" y2="740" />
+	<line stroke="black" stroke-opacity="1" x1="505" y1="748" x2="585" y2="748" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="563" y1="123" x2="528" y2="123" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="563,123 557,129 551,123 557,117" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="528" y1="201" x2="534" y2="195" />
+	<line stroke="black" stroke-opacity="1" x1="528" y1="201" x2="522" y2="195" />
+	<line stroke="black" stroke-opacity="1" x1="528" y1="123" x2="528" y2="201" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="662" y1="228" x2="656" y2="222" />
+	<line stroke="black" stroke-opacity="1" x1="662" y1="228" x2="656" y2="234" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="570" y1="228" x2="662" y2="228" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="672" y1="120" x2="783" y2="120" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="672,120 678,114 684,120 678,126" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="783" y1="120" x2="784" y2="733" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="589" y1="733" x2="595" y2="739" />
+	<line stroke="black" stroke-opacity="1" x1="589" y1="733" x2="595" y2="727" />
+	<line stroke="black" stroke-opacity="1" x1="784" y1="733" x2="589" y2="733" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="336" y1="506" x2="336" y2="734" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="336,506 342,512 336,518 330,512" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="504" y1="734" x2="498" y2="728" />
+	<line stroke="black" stroke-opacity="1" x1="504" y1="734" x2="498" y2="740" />
+	<line stroke="black" stroke-opacity="1" x1="336" y1="734" x2="504" y2="734" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="127" y="453" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="53" y="497" width="77" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="49" y="449" width="78" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="88" y="463">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="88" y="479">SignalSetPtr</text>
+	<line stroke="black" stroke-opacity="1" x1="49" y1="481" x2="127" y2="481" />
+	<line stroke="black" stroke-opacity="1" x1="49" y1="489" x2="127" y2="489" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="318" y="584" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="208" y="628" width="113" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="204" y="580" width="114" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="261" y="594">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="261" y="610">BoolSignalHandler</text>
+	<line stroke="black" stroke-opacity="1" x1="204" y1="612" x2="318" y2="612" />
+	<line stroke="black" stroke-opacity="1" x1="204" y1="620" x2="318" y2="620" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="574" y="358" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="504" y="402" width="73" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="500" y="354" width="74" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="537" y="368">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="537" y="384">IOSignalId</text>
+	<line stroke="black" stroke-opacity="1" x1="500" y1="386" x2="574" y2="386" />
+	<line stroke="black" stroke-opacity="1" x1="500" y1="394" x2="574" y2="394" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="90" y1="527" x2="96" y2="521" />
+	<line stroke="black" stroke-opacity="1" x1="90" y1="527" x2="84" y2="521" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="90" y1="501" x2="90" y2="527" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="263" y1="632" x2="257" y2="638" />
+	<line stroke="black" stroke-opacity="1" x1="263" y1="632" x2="269" y2="638" />
+	<line stroke="black" stroke-opacity="1" x1="263" y1="673" x2="263" y2="632" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="181" y1="401" x2="90" y2="401" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="181,401 175,407 169,401 175,395" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="172" y1="673" x2="263" y2="673" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="172,673 178,667 184,673 178,679" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="90" y1="448" x2="96" y2="442" />
+	<line stroke="black" stroke-opacity="1" x1="90" y1="448" x2="84" y2="442" />
+	<line stroke="black" stroke-opacity="1" x1="90" y1="401" x2="90" y2="448" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="648" y1="327" x2="560" y2="327" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="648,327 642,333 636,327 642,321" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="560" y1="353" x2="566" y2="347" />
+	<line stroke="black" stroke-opacity="1" x1="560" y1="353" x2="554" y2="347" />
+	<line stroke="black" stroke-opacity="1" x1="560" y1="327" x2="560" y2="353" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="499" y1="377" x2="493" y2="370" />
+	<line stroke="black" stroke-opacity="1" x1="499" y1="377" x2="492" y2="382" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="471" y1="376" x2="499" y2="377" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="267" y1="579" x2="273" y2="573" />
+	<line stroke="black" stroke-opacity="1" x1="267" y1="579" x2="261" y2="572" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="268" y1="506" x2="267" y2="579" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="531" y1="353" x2="536" y2="346" />
+	<line stroke="black" stroke-opacity="1" x1="531" y1="353" x2="524" y2="347" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="529" y1="254" x2="531" y2="353" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="588" y="551" width="3" height="123" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="500" y="671" width="91" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="496" y="547" width="92" height="124" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="542" y="561">IntervalTimer</text>
+	<line stroke="black" stroke-opacity="1" x1="496" y1="563" x2="588" y2="563" />
+	<line stroke="black" stroke-opacity="1" x1="496" y1="571" x2="588" y2="571" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="585">IntervalTimer()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="599">operator =()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="613">IntervalTimer()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="627">~IntervalTimer()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="641">setup()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="655">cancel()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="669">getInterval()</text>
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="546" y1="707" x2="551" y2="700" />
+	<line stroke="black" stroke-opacity="1" x1="546" y1="707" x2="539" y2="701" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="545" y1="675" x2="546" y2="707" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="585" y="463" width="3" height="53" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="497" y="513" width="91" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="493" y="459" width="92" height="54" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="539" y="473">TimerCallback</text>
+	<line stroke="black" stroke-opacity="1" x1="493" y1="475" x2="585" y2="475" />
+	<line stroke="black" stroke-opacity="1" x1="493" y1="483" x2="585" y2="483" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="497" y="497">TimerCallback()</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="497" y="511">operator ()()</text>
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="763" y="548" width="3" height="47" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="667" y="592" width="99" height="3" />
+	<rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="663" y="544" width="100" height="48" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="713" y="558">&lt;&lt;typedef&gt;&gt;</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="713" y="574">IntervalTimerPtr</text>
+	<line stroke="black" stroke-opacity="1" x1="663" y1="576" x2="763" y2="576" />
+	<line stroke="black" stroke-opacity="1" x1="663" y1="584" x2="763" y2="584" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="671" y1="488" x2="671" y2="425" />
+<ellipse fill="none" stroke="black" stroke-width="1" stroke-opacity="1" cx="671" cy="419" rx="5" ry="5" />
+	<line stroke="black" stroke-opacity="1" x1="666" y1="419" x2="676" y2="419" />
+	<line stroke="black" stroke-opacity="1" x1="671" y1="414" x2="671" y2="424" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="589" y1="488" x2="671" y2="488" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="539" y1="406" x2="533" y2="412" />
+	<line stroke="black" stroke-opacity="1" x1="539" y1="406" x2="545" y2="411" />
+	<line stroke="black" stroke-opacity="1" x1="540" y1="458" x2="539" y2="406" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="540,458 533,452 539,446 545,451" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="492" y1="488" x2="419" y2="488" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="492,488 486,494 480,488 486,482" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="419" y1="401" x2="413" y2="407" />
+	<line stroke="black" stroke-opacity="1" x1="419" y1="401" x2="425" y2="407" />
+	<line stroke="black" stroke-opacity="1" x1="419" y1="488" x2="419" y2="401" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="541" y1="517" x2="535" y2="523" />
+	<line stroke="black" stroke-opacity="1" x1="541" y1="517" x2="547" y2="523" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="541" y1="546" x2="541" y2="517" />
+</g>
+<g>
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="714" y1="596" x2="714" y2="611" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="592" y1="611" x2="598" y2="617" />
+	<line stroke="black" stroke-opacity="1" x1="592" y1="611" x2="598" y2="605" />
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="714" y1="611" x2="592" y2="611" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="714" y1="543" x2="719" y2="536" />
+	<line stroke="black" stroke-opacity="1" x1="714" y1="543" x2="707" y2="537" />
+	<line stroke="black" stroke-opacity="1" x1="713" y1="413" x2="714" y2="543" />
+	<polygon fill="#000000" stroke="black" stroke-opacity="1" points="713,413 719,418 713,424 707,419" />
+</g>
+</svg>

+ 318 - 0
src/bin/d2/images/cpl_signal_sequence.svg

@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="848" height="803" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="92" y1="45" x2="92" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="139" y="8" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="45" y="23" width="97" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="41" y="4" width="98" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="90" y="20">:DControllerBase</text>
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="384" y="74" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="296" y="89" width="91" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="292" y="70" width="92" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="338" y="86">:IOSignalQueue</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="340" y1="111" x2="340" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="469" y="101" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="411" y="116" width="61" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="407" y="97" width="62" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="438" y="113">:SignalSet</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="440" y1="138" x2="440" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="559" y="350" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="507" y="365" width="55" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="503" y="346" width="56" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="531" y="362">:IOSignal</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="533" y1="387" x2="533" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="662" y="373" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="586" y="388" width="79" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="582" y="369" width="80" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="622" y="385">:IntervalTimer</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="624" y1="410" x2="624" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="769" y="421" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="686" y="436" width="86" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="682" y="417" width="87" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="726" y="433">:TimerCallback</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="727" y1="458" x2="727" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="832" y="8" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="773" y="23" width="62" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="769" y="4" width="63" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="801" y="20">:IOService</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="802" y1="45" x2="802" y2="803" />
+</g>
+<g>
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="262" y="8" width="3" height="18" />
+	<rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="176" y="23" width="89" height="3" />
+	<rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="172" y="4" width="90" height="19" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="217" y="20">:DProcessBase</text>
+</g>
+<g>
+	<line stroke="black" stroke-dasharray="18,6"  stroke-opacity="1" x1="219" y1="45" x2="219" y2="803" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="87" y="60" width="10" height="726" />
+</g>
+<g>
+	<polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="262,661 416,661 416,671 426,671 426,709 262,709 262,661" />
+	<line stroke="black" stroke-opacity="1" x1="416" y1="661" x2="426" y2="671" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="272" y="683">Details of configFromFile</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="272" y="695">omitted for clarity</text>
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="528" y="399" width="10" height="106" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="93" y="242" width="10" height="521" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="335" y="120" width="10" height="24" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="435" y="166" width="10" height="24" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="435" y="203" width="10" height="24" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="435" y="285" width="10" height="152" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="335" y="341" width="10" height="82" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="619" y="419" width="10" height="24" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="619" y="466" width="10" height="41" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="722" y="485" width="10" height="24" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="797" y="253" width="10" height="527" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="722" y="549" width="10" height="195" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="214" y="249" width="10" height="542" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="335" y="587" width="10" height="24" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="528" y="619" width="10" height="24" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="539" y1="471" x2="619" y2="471" />
+	<polygon fill="#000000" stroke="none" points="619,471 615,467 615,475" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="33" y1="66" x2="87" y2="66" />
+	<polygon fill="#000000" stroke="none" points="87,66 83,62 83,70" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="310" y1="290" x2="435" y2="290" />
+	<polygon fill="#000000" stroke="none" points="435,290 431,286 431,294" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="630" y1="492" x2="722" y2="492" />
+	<polygon fill="#000000" stroke="none" points="722,492 718,488 718,496" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="733" y1="554" x2="797" y2="554" />
+	<polygon fill="#000000" stroke="none" points="733,554 737,550 737,558" />
+</g>
+<g>
+	<polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="29,288 173,288 173,298 183,298 183,348 29,348 29,288" />
+	<line stroke="black" stroke-opacity="1" x1="173" y1="288" x2="183" y2="298" />
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="39" y="310">Sometime after</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="39" y="322">runProcess is called</text>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="39" y="334">SIGHUP is sent</text>
+</g>
+<ellipse fill="black" stroke="none" cx="28.5" cy="67.5" rx="4.5" ry="4.5" />
+<ellipse fill="black" stroke="none" cx="305.5" cy="291.5" rx="4.5" ry="4.5" />
+<g>
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="183" y1="306" x2="300" y2="291" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="93" y="98" width="10" height="122" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="99" y="562" width="10" height="195" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="441" y="313" width="10" height="116" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="220" y="722" width="10" height="24" />
+</g>
+<g>
+	<path fill="none" stroke="black" stroke-opacity="1" d="M 105 99 L 122 99 L 122 106 L 105 106" />
+	<polygon fill="#000000" stroke="none" points="105,106 109,110 109,102" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="104" y1="134" x2="335" y2="134" />
+	<polygon fill="#000000" stroke="none" points="335,134 331,130 331,138" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="104" y1="178" x2="435" y2="178" />
+	<polygon fill="#000000" stroke="none" points="435,178 431,174 431,182" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="104" y1="208" x2="435" y2="208" />
+	<polygon fill="#000000" stroke="none" points="435,208 431,204 431,212" />
+</g>
+<g>
+	<path fill="none" stroke="black" stroke-opacity="1" d="M 453 314 L 470 314 L 470 321 L 453 321" />
+	<polygon fill="#000000" stroke="none" points="453,321 457,325 457,317" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="346" y1="351" x2="441" y2="351" />
+	<polygon fill="#000000" stroke="none" points="346,351 350,347 350,355" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="110" y1="567" x2="722" y2="567" />
+	<polygon fill="#000000" stroke="none" points="110,567 114,563 114,571" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="110" y1="592" x2="335" y2="592" />
+	<polygon fill="#000000" stroke="none" points="335,592 331,588 331,596" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="110" y1="624" x2="528" y2="624" />
+	<polygon fill="#000000" stroke="none" points="528,624 524,620 524,628" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="105" y="652" width="10" height="100" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="346" y1="404" x2="528" y2="404" />
+	<polygon fill="#000000" stroke="none" points="528,404 524,400 524,408" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="539" y1="431" x2="619" y2="431" />
+	<polygon fill="#000000" stroke="none" points="619,431 615,427 615,435" />
+</g>
+<g>
+	<path fill="none" stroke="black" stroke-opacity="1" d="M 117 653 L 134 653 L 134 660 L 117 660" />
+	<polygon fill="#000000" stroke="none" points="117,660 121,664 121,656" />
+</g>
+<g>
+	<rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="111" y="686" width="10" height="61" />
+</g>
+<g>
+	<path fill="none" stroke="black" stroke-opacity="1" d="M 123 687 L 140 687 L 140 694 L 123 694" />
+	<polygon fill="#000000" stroke="none" points="123,694 127,698 127,690" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="122" y1="727" x2="220" y2="727" />
+	<polygon fill="#000000" stroke="none" points="220,727 216,723 216,731" />
+</g>
+<g>
+	<line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="261" y1="687" x2="141" y2="691" />
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="38" y="61">launch()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="106" y="91">initSignalHandling()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="113" y="121">IOSignalQueue(io_service)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="116" y="161">setOnReceiptHandler(DControllerBase::osSignalHandler)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="140" y="202">SignalSet(SIGHUP,SIGINT,SIGTERM)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="288" y="279">internalHandler(SIGHUP)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="359" y="309">invokeOnReceiptHandler(SIGHUP)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="314" y="340">pushSignal(SIGHUP, DControllerBase::ioSignalHandler)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="353" y="399">IOSignal(io_service, SIGHUP, handler)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="548" y="418">IntervalTimer(io_service)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="431" y="459">setup(TimerCallBack(sequence_id, handler), 1, ONE_SHOT))</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="637" y="478">TimerCallback(sequence_id, handler)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="737" y="547">operator ()()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="722" y="256">run()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="118" y="240">runProcess()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="146" y="254">run()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="128" y="562">ioSignalHandler(sequence_id)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="191" y="587">popSignal()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="448" y="615">getSignum()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="117" y="647">processSignal(SIGHUP)</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="126" y="682">configFromFile()</text>
+</g>
+<g>
+	<text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="142" y="722">configure()</text>
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="225" y1="260" x2="797" y2="260" />
+	<polygon fill="#000000" stroke="none" points="797,260 793,256 793,264" />
+</g>
+<g>
+	<path fill="none" stroke="black" stroke-opacity="1" d="M 105 243 L 122 243 L 122 250 L 105 250" />
+	<polygon fill="#000000" stroke="none" points="105,250 109,254 109,246" />
+</g>
+<g>
+	<line stroke="black" stroke-opacity="1" x1="104" y1="259" x2="214" y2="259" />
+	<polygon fill="#000000" stroke="none" points="214,259 210,255 210,263" />
+</g>
+</svg>

+ 124 - 0
src/bin/d2/io_service_signal.cc

@@ -0,0 +1,124 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/interval_timer.h>
+#include <d2/d2_log.h>
+#include <d2/io_service_signal.h>
+
+namespace isc {
+namespace d2 {
+
+IOSignal::IOSignal (asiolink::IOService& io_service, int signum,
+                    IOSignalHandler handler)
+    : sequence_id_(nextSequenceId()), signum_(signum),
+      timer_(new asiolink::IntervalTimer(io_service)) {
+    // Valid handler is essential.
+    if (!handler) {
+        isc_throw(IOSignalError,
+                  "IOSignal - handler cannot be null");
+    }
+
+    // Set up the timer as a one-shot which expires in 1 ms (intervals of 0
+    // are invalid). This means that once control returns to IOService::run
+    // the timer will have expired and its handler will be invoked.
+    timer_->setup(TimerCallback(sequence_id_, handler), 1,
+                  asiolink::IntervalTimer::ONE_SHOT);
+}
+
+IOSignal::~IOSignal() {
+    if (timer_) {
+        // In the unlikely event that the timer hasn't expired cancel it.
+        timer_->cancel();
+    }
+}
+
+IOSignal::
+TimerCallback::TimerCallback(IOSignalId sequence_id, IOSignalHandler handler)
+    : sequence_id_(sequence_id), handler_(handler) {
+    if (!handler) {
+        isc_throw(IOSignalError,
+                  "IOSignal::TimerCallback - handler cannot be null");
+    }
+}
+
+void
+IOSignal::TimerCallback::operator()() {
+    try {
+        handler_(sequence_id_);
+    } catch (const std::exception& ex) {
+        // We log it and swallow it so we don't undermine IOService::run.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_SIGNAL_ERROR)
+                  .arg(sequence_id_).arg(ex.what());
+    }
+
+    return;
+}
+
+IOSignalQueue::IOSignalQueue(IOServicePtr& io_service)
+    : io_service_(io_service), signals_() {
+    if (!io_service_) {
+        isc_throw(IOSignalError, "IOSignalQueue - io_serivce cannot be NULL");
+    }
+}
+
+IOSignalQueue::~IOSignalQueue() {
+    clear();
+}
+
+IOSignalId
+IOSignalQueue::pushSignal(int signum, IOSignalHandler handler) {
+    // Create the new signal.
+    IOSignalPtr signal(new IOSignal(*io_service_, signum, handler));
+
+    // Make sure the sequence_id isn't already in the queue.
+    IOSignalId sequence_id = signal->getSequenceId();
+    IOSignalMap::iterator it = signals_.find(sequence_id);
+    if (it != signals_.end()) {
+        // This really shouldn't happen unless we are in the weeds.
+        isc_throw (IOSignalError, "pushSignal - "
+                   "signal already exists for sequence_id: " << sequence_id);
+    }
+
+    //  Add the signal to the queue.
+    signals_[sequence_id] = signal;
+    return (sequence_id);
+}
+
+IOSignalPtr
+IOSignalQueue::popSignal(IOSignalId sequence_id) {
+    // Look for the signal in the queue.
+    IOSignalMap::iterator it = signals_.find(sequence_id);
+    if (it == signals_.end()) {
+        // This really shouldn't happen unless we are in the weeds.
+        isc_throw (IOSignalError, "popSignal - "
+                   "signal not found for sequence_id: " << sequence_id);
+    }
+
+    // Save the signal so we can return it.
+    IOSignalPtr signal = ((*it).second);
+
+    // Delete it from the queue.
+    signals_.erase(it);
+
+    // Return the signal.
+    return (signal);
+}
+
+void
+IOSignalQueue::clear() {
+    signals_.clear();
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace

+ 264 - 0
src/bin/d2/io_service_signal.h

@@ -0,0 +1,264 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef IO_SERVICE_SIGNAL_H
+#define IO_SERVICE_SIGNAL_H
+
+#include <d2/d2_asio.h>
+#include <exceptions/exceptions.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if IOSignal encounters an error.
+class IOSignalError : public isc::Exception {
+public:
+    IOSignalError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a unique identifier type for IOSignal.
+typedef uint64_t IOSignalId;
+
+/// @brief Defines a handler function for an IOSignal.
+/// IOSignalHandlers should contain the application level logic that would
+/// ordinarily be an OS signal handler.
+typedef boost::function<void(IOSignalId sequence_id)> IOSignalHandler;
+
+/// @brief Implements an asynchronous "signal" for IOService driven processing
+///
+/// This class allows a OS signal such as SIGHUP to propagated to an IOService
+/// as a ready event with a callback. While boost::asio provides a signal class,
+/// it requires linking in additional boost libraries that as of yet we do not
+/// need. Therefore, this class was implemented to allow IOService-based
+/// processes to handle signals as IOService events.
+///
+/// The mechanics of IOSignal are straight forward. Upon construction it is
+/// given the target IOService, the value of the signal to send (i.e. SIGINT,
+/// SIGHUP...), and an IOSignalHandler.  The IOSignalHandler should contain
+/// the logic the caller would normally execute in its OS signal handler. Each
+/// IOSignal instance has a unique identifier called its sequence_id.
+///
+/// Internally, IOSignal creates a 1 ms, one-shot timer, on the given
+/// IOService.  When the timer expires its event handler invokes the caller's
+/// IOSignalHandler passing it the sequence_id of the IOSignal.
+///
+/// Sending IOSignals is done through an IOSignalQueue.  This class is used to
+/// create the signals, house them until they are delivered, and dequeue them
+/// so they can be been handled.  To generate an IOSignal when an OS signal
+/// arrives, the process's OS signal handler simply calls @ref
+/// isc::d2::IOSignalQueue::pushSignal() with the appropriate values.
+///
+/// @note that an IOSignalQueue requires a non-null IOServicePtr to construct.
+/// This ensures that the IOService cannot be destroyed before any pending
+/// signals can be canceled.  It also means that a queue can only be used to
+/// send signals to that IOService.  If you need to send signals to more than
+/// one service, each service must have its own queue.
+///
+/// To dequeue the IOSignal inside the caller's IOSignalHandler, one simply
+/// invokes @ref isc::d2::IOSignalQueue::popSignal() passing it the sequence_id
+/// parameter passed to the handler.  This method returns a pointer to
+/// instigating IOSignal from which the value of OS signal (i.e. SIGINT,
+/// SIGUSR1...) can be obtained.  Note that calling popSignal() removes the
+/// IOSignalPtr from the queue, which should reduce its reference count to
+/// zero upon exiting the handler (unless a delibrate copy of it is made).
+///
+/// A typical IOSignalHandler might be structured as follows:
+/// @code
+///
+///    void processSignal(IOSignalId sequence_id) {
+///        // Pop the signal instance off the queue.
+///        IOSignalPtr signal = io_signal_queue_->popSignal(sequence_id);
+///
+///        int os_signal_value = signal->getSignum();
+///        :
+///        // logic based on the signal value
+///        :
+///     }
+///
+/// @endcode
+///
+/// IOSignal handler invocation code will catch, log ,and then swallow any
+/// exceptions thrown by a IOSignalHandler invocation.  This is done to protect
+/// the integrity IOService context.
+///
+class IOSignal {
+public:
+    /// @brief Constructor
+    ///
+    /// @param io_service IOService to which to send the signal
+    /// @param signum value of the signal to send
+    /// @param handler the handler to run when IOService "receives" the
+    /// signal
+    ///
+    /// @throw IOSignalError if handler is null
+    IOSignal(asiolink::IOService& io_service, int signum,
+              IOSignalHandler handler);
+
+    /// @brief Destructor
+    ~IOSignal();
+
+    /// @brief Static method for generating IOSignal sequence_ids.
+    ///
+    /// Generates and returns the next IOSignalId. This method is intentionally
+    /// static in the event a process is using generating signals to more than
+    /// IOService.  It assures that each IOSignal is unique with the process
+    /// space.
+    ///
+    /// @return The next sequential value as an IOSignalId.
+    static IOSignalId nextSequenceId() {
+        static IOSignalId next_id_ = 0;
+        return (++next_id_);
+    }
+
+    /// @brief Gets the IOSignal's sequence_id
+    ///
+    /// @return The sequence_id of the signal.
+    IOSignalId getSequenceId() const {
+        return (sequence_id_);
+    }
+
+    /// @brief Gets the OS signal value this IOSignal represents.
+    ///
+    /// @return The OS signal value (i.e. SIGINT, SIGUSR1...)
+    int getSignum() const {
+        return (signum_);
+    }
+
+    /// @brief Defines the callback used by IOSignal's internal timer.
+    ///
+    /// This class stores the sequence_id of the IOSignal being sent and the
+    /// IOSignalHandler to invoke when delivering the signal.  The () operator
+    /// is called by IOService when the timer expires.  This method invokes
+    /// the IOSignalHandler passing it the sequence_id.
+    class TimerCallback : public std::unary_function<void, void> {
+    public:
+        /// @brief Constructor
+        ///
+        /// @param sequence_id sequence_id of the IOSignal to handle
+        /// @param handler pointer to the function to handle the IOSignal
+        ///
+        /// @throw IOSignalError if handler is null.
+        TimerCallback(IOSignalId sequence_id, IOSignalHandler handler);
+
+        /// @brief () Operator which serves as the timer's callback
+        ///
+        /// It is invoked when the timer expires and calls the handler
+        /// passing in the signal.
+        void operator()();
+
+    private:
+        /// @brief Id of the IOSignal to which the callback pertains.
+        IOSignalId sequence_id_;
+
+        /// @brief Pointer to the function to handle the signal
+        IOSignalHandler handler_;
+    };
+
+private:
+    /// @brief Value which uniquely identifies each IOSignal instance.
+    IOSignalId sequence_id_;
+
+    /// @brief Numeric value of the signal to send (e.g. SIGINT, SIGUSR1...)
+    int signum_;
+
+    /// @brief Timer instance created to propagate the signal.
+    asiolink::IntervalTimerPtr timer_;
+};
+
+/// @brief Defines a pointer to an IOSignal
+typedef boost::shared_ptr<IOSignal> IOSignalPtr;
+
+/// @brief Defines a map of IOSignalPtr keyed by id
+typedef std::map<IOSignalId, IOSignalPtr> IOSignalMap;
+
+/// @brief Creates and manages IOSignals
+///
+/// This class is used to create IOSignals, house them until they are delivered,
+/// and dequeue them so they can be been handled.  IOSignals are designed to
+/// used once and then destroyed.  They need to be created from within OS
+/// signal handlers and persist until they have been delivered and processed.
+///
+/// This class is designed specifically to make managing them painless.
+/// It maintains an internal map of IOSignals keyed by sequence_id. When a
+/// signal is created via the pushSignal() method it is added to the map. When
+/// a signal is retrevied via the popSignal() method it is removed from the map.
+class IOSignalQueue {
+public:
+    /// @brief Constructor
+    ///
+    /// @param io_service the IOService to which to send signals.
+    /// @throw IOSignalError if io_service is NULL.
+    IOSignalQueue (IOServicePtr& io_service);
+
+    /// @brief Destructor.
+    ~IOSignalQueue();
+
+    /// @brief Creates an IOSignal
+    ///
+    /// Given a signal number and a handler, it will instantiate an IOSignal
+    /// and add it to the instance map.  (Remember that IOSignals are really
+    /// just timers programmed during construction, so once instantiated
+    /// there's nothing more required to "send" the signal other than return
+    /// control to IOService::run()).
+    ///
+    /// @param signum OS signal value of the signal to propagate
+    /// @param handler IOSignalHandler to invoke when the signal is delivererd.
+    ///
+    /// @return The sequence_id of the newly created signal.
+    ///
+    /// @throw IOSignalError if the sequence_id already exists in the map. This
+    /// is virtually impossible unless things have gone very wrong.
+    IOSignalId pushSignal(int signum, IOSignalHandler handler);
+
+    /// @brief Removes an IOSignal from the map and returns it.
+    ///
+    /// Given a sequence_id this method will extract the IOSignal from the
+    /// internal map and return.  At that point, the caller will hold the
+    /// only copy of the IOSignal.
+    ///
+    /// @param sequence_id  sequence_id of the IOSignal to retrieve.
+    ///
+    /// @return A smart pointer to the IOSignal.
+    ///
+    /// @throw IOSignalError if there is no matching IOSignal in the map for
+    /// the given sequence_id.  Other than by doubling popping, this should be
+    /// very unlikley.
+    IOSignalPtr popSignal(IOSignalId sequence_id);
+
+    /// @brief Erases the contents of the queue.
+    ///
+    /// Any instances still in the map will be destroyed. This will cause their
+    /// timers to be cancelled without any callbacks invoked. (Not sure when
+    /// this might be desirable).
+    void clear();
+
+private:
+    /// @brief Pointer to the IOService which will receive the signals.
+    IOServicePtr io_service_;
+
+    /// @brief A map of the IOSignals pushed through this queue.
+    IOSignalMap signals_;
+};
+
+/// @brief Defines a pointer to an IOSignalQueue.
+typedef boost::shared_ptr<IOSignalQueue> IOSignalQueuePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // IO_SERVICE_SIGNAL_H

+ 21 - 0
src/bin/d2/tests/Makefile.am

@@ -1,7 +1,20 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 
 PYTESTS = d2_test.py
+SHTESTS =
+# The test of dynamic reconfiguration based on signals will work only
+# if we are using file based configuration approach.
+if CONFIG_BACKEND_JSON
+SHTESTS += d2_reconfigure_test.sh
+SHTESTS += d2_sigterm_test.sh
+SHTESTS += d2_sigint_test.sh
+endif
+
 EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += d2_reconfigure_test.sh
+EXTRA_DIST += d2_sigterm_test.sh
+EXTRA_DIST += d2_sigint_test.sh
+EXTRA_DIST += d2_shutdown_test.sh
 
 # Explicitly specify paths to dynamic libraries required by loadable python
 # modules. That is required on Mac OS systems. Otherwise we will get exception
@@ -21,6 +34,12 @@ check-local:
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 
+	for shtest in $(SHTESTS) ; do \
+	echo Running test: $$shtest ; \
+	export B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir); \
+	$(abs_srcdir)/$$shtest || exit ; \
+	done
+
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
@@ -64,6 +83,7 @@ d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../io_service_signal.cc ../io_service_signal.h
 d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
 d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
 d2_unittests_SOURCES += ../nc_remove.cc ../nc_remove.h
@@ -79,6 +99,7 @@ d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
+d2_unittests_SOURCES += io_service_signal_unittests.cc
 d2_unittests_SOURCES += labeled_value_unittests.cc
 d2_unittests_SOURCES += nc_add_unittests.cc
 d2_unittests_SOURCES += nc_remove_unittests.cc

+ 141 - 27
src/bin/d2/tests/d2_controller_unittests.cc

@@ -15,6 +15,7 @@
 #include <config/ccsession.h>
 #include <d_test_stubs.h>
 #include <d2/d2_controller.h>
+#include <d2/d2_process.h>
 #include <d2/spec_config.h>
 
 #include <boost/pointer_cast.hpp>
@@ -51,6 +52,40 @@ public:
     /// @brief Destructor
     ~D2ControllerTest() {
     }
+
+    /// @brief Fetches the D2Controller's D2Process
+    ///
+    /// @return A pointer to the process which may be null if it has not yet
+    /// been instantiated.
+    D2ProcessPtr getD2Process() {
+        return (boost::dynamic_pointer_cast<D2Process>(getProcess()));
+    }
+
+    /// @brief Fetches the D2Process's D2Configuration manager
+    ///
+    /// @return A pointer to the manager which may be null if it has not yet
+    /// been instantiated.
+    D2CfgMgrPtr getD2CfgMgr() {
+        D2CfgMgrPtr p;
+        if (getD2Process()) {
+            p = getD2Process()->getD2CfgMgr();
+        }
+
+        return (p);
+    }
+
+    /// @brief Fetches the D2Configuration manager's D2CfgContext
+    ///
+    /// @return A pointer to the context which may be null if it has not yet
+    /// been instantiated.
+    D2CfgContextPtr getD2CfgContext() {
+        D2CfgContextPtr p;
+        if (getD2CfgMgr()) {
+            p = getD2CfgMgr()->getD2CfgContext();
+        }
+
+        return (p);
+    }
 };
 
 /// @brief Basic Controller instantiation testing.
@@ -120,34 +155,13 @@ TEST_F(D2ControllerTest, initProcessTesting) {
 /// This creates an interval timer to generate a normal shutdown and then
 /// launches with a valid, stand-alone command line and no simulated errors.
 TEST_F(D2ControllerTest, launchNormalShutdown) {
-    // command line to run standalone
-    char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-c"),
-                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
-                     const_cast<char*>("-v") };
-    int argc = 4;
-
-    // Create a valid D2 configuration file.
-    writeFile(valid_d2_config);
-
-    // Use an asiolink IntervalTimer and callback to generate the
-    // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
-    isc::asiolink::IntervalTimer timer(*getIOService());
-    timer.setup(genShutdownCallback, 2 * 1000);
-
-    // Record start time, and invoke launch().
-    ptime start = microsec_clock::universal_time();
-    EXPECT_NO_THROW(launch(argc, argv));
+    // Write valid_d2_config and then run launch() for 1000 ms.
+    time_duration elapsed_time;
+    runWithConfig(valid_d2_config, 1000, elapsed_time);
 
-    // Record stop time.
-    ptime stop = microsec_clock::universal_time();
-
-    // Verify that duration of the run invocation is the same as the
-    // timer duration.  This demonstrates that the shutdown was driven
-    // by an io_service event and callback.
-    time_duration elapsed = stop - start;
-    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
-                elapsed.total_milliseconds() <= 2200);
+    // Give a generous margin to accomodate slower test environs.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
+                elapsed_time.total_milliseconds() <= 1300);
 }
 
 /// @brief Configuration update event testing.
@@ -211,7 +225,107 @@ TEST_F(D2ControllerTest, executeCommandTests) {
     answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(COMMAND_SUCCESS, rcode);
+}
+
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(D2ControllerTest, invalidConfigReload) {
+    // Schedule to replace the configuration file after launch. This way the
+    // file is updated after we have done the initial configuration.
+    scheduleTimedWrite("{ \"string_test\": BOGUS JSON }", 100);
+
+    // Setup to raise SIGHUP in 200 ms.
+    TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+    // Write valid_d2_config and then run launch() for a maximum of 500 ms.
+    time_duration elapsed_time;
+    runWithConfig(valid_d2_config, 500, elapsed_time);
+
+    // Context is still available post launch.
+    // Check to see that our configuration matches the original per
+    // valid_d2_config (see d_test_stubs.cc)
+    D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
+    D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+    ASSERT_TRUE(d2_params);
+
+    EXPECT_EQ("127.0.0.1", d2_params->getIpAddress().toText());
+    EXPECT_EQ(5031, d2_params->getPort());
+    EXPECT_TRUE(d2_cfg_mgr->forwardUpdatesEnabled());
+    EXPECT_TRUE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+    /// @todo add a way to trap log file and search it
+}
+
+// Tests that the original configuration is replaced after a SIGHUP triggered
+// reconfiguration succeeds.
+TEST_F(D2ControllerTest, validConfigReload) {
+    // Define a replacement config.
+    const char* second_cfg =
+            "{"
+            " \"ip_address\": \"192.168.77.1\" , "
+            " \"port\": 777 , "
+            "\"tsig_keys\": [], "
+            "\"forward_ddns\" : {}, "
+            "\"reverse_ddns\" : {} "
+            "}";
+
+    // Schedule to replace the configuration file after launch. This way the
+    // file is updated after we have done the initial configuration.
+    scheduleTimedWrite(second_cfg, 100);
+
+    // Setup to raise SIGHUP in 200 ms.
+    TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+    // Write valid_d2_config and then run launch() for a maximum of 500ms.
+    time_duration elapsed_time;
+    runWithConfig(valid_d2_config, 500, elapsed_time);
+
+    // Context is still available post launch.
+    // Check to see that our configuration matches the replacement config.
+    D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
+    D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+    ASSERT_TRUE(d2_params);
+
+    EXPECT_EQ("192.168.77.1", d2_params->getIpAddress().toText());
+    EXPECT_EQ(777, d2_params->getPort());
+    EXPECT_FALSE(d2_cfg_mgr->forwardUpdatesEnabled());
+    EXPECT_FALSE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+    /// @todo add a way to trap log file and search it
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(D2ControllerTest, sigintShutdown) {
+    // Setup to raise SIGHUP in 1 ms.
+    TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+    // Write valid_d2_config and then run launch() for a maximum of 1000 ms.
+    time_duration elapsed_time;
+    runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+    // Signaled shutdown should make our elapsed time much smaller than
+    // the maximum run time.  Give generous margin to accomodate slow
+    // test environs.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+
+    /// @todo add a way to trap log file and search it
+}
+
+// Tests that the SIGTERM triggers a normal shutdown.
+TEST_F(D2ControllerTest, sigtermShutdown) {
+    // Setup to raise SIGHUP in 1 ms.
+    TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+    // Write valid_d2_config and then run launch() for a maximum of 1 s.
+    time_duration elapsed_time;
+    runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+    // Signaled shutdown should make our elapsed time much smaller than
+    // the maximum run time.  Give generous margin to accomodate slow
+    // test environs.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
 
+    /// @todo add a way to trap log file and search it
 }
 
 }; // end of isc::d2 namespace

+ 147 - 0
src/bin/d2/tests/d2_reconfigure_test.sh

@@ -0,0 +1,147 @@
+# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Test name
+TEST_NAME="D2.dynamicReconfiguration"
+# Path to the temporary configuration file.
+CFG_FILE="test_config.json"
+# Path to the Kea log file.
+LOG_FILE="test.log"
+# Kea configuration to be stored in the configuration file.
+CONFIG="{
+    \"DhcpDdns\":
+    {
+        \"ip_address\": \"127.0.0.1\",
+        \"port\": 53001,
+        \"tsig_keys\": [],
+        \"forward_ddns\" : {},
+        \"reverse_ddns\" : {}
+    }
+}"
+
+# Invalid configuration (negative valid-lifetime) to check that Kea
+# gracefully handles reconfiguration errors.
+CONFIG_INVALID="{
+    \"DhcpDdns\":
+    {
+        \"ip_address\": \"127.0.0.1\",
+        \"port\": BOGUS,
+        \"tsig_keys\": [],
+        \"forward_ddns\" : {},
+        \"reverse_ddns\" : {}
+    }
+}"
+
+
+# Set the location of the executable.
+BIN="b10-dhcp-ddns"
+BIN_PATH=".."
+
+# Import common test library.
+. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
+
+# Log the start of the test and print test name.
+test_start
+# Remove dangling Kea instances and remove log files.
+cleanup
+# Create new configuration file.
+create_config "${CONFIG}"
+# Instruct Kea to log to the specific file.
+set_logger
+# Start Kea.
+start_kea
+# Wait up to 20s for Kea to start.
+wait_for_kea 20
+if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
+    printf "ERROR: timeout waiting for D2 to start.\n"
+    clean_exit 1
+fi
+
+# Check if it is still running. It could have terminated (e.g. as a result
+# of configuration failure).
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: expected one D2 process to be started. Found %d processes\
+ started.\n" ${_GET_PIDS_NUM}
+    clean_exit 1
+fi
+
+# Check in the log file, how many times server has been configured. It should
+# be just once on startup.
+get_reconfigs
+if [ ${_GET_RECONFIGS} -ne 1 ]; then
+    printf "ERROR: D2 hasn't been configured.\n"
+    clean_exit 1
+else
+    printf "D2 successfully configured.\n"
+fi
+
+# Now use invalid configuration.
+create_config "${CONFIG_INVALID}"
+
+# Try to reconfigure by sending SIGHUP
+send_signal 1
+
+# Wait up to 10s for the D2Controller to log reload signal received.
+wait_for_message 10 "DHCP_DDNS_CFG_FILE_RELOAD_SIGNAL_RECVD"  1
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: D2Process did report the reload signal receipt.\n"
+    clean_exit 1
+fi
+
+# After receiving SIGHUP the server should try to reconfigure itself.
+# The configuration provided is invalid so it should result in
+# reconfiguration failure but the server should still be running.
+wait_for_message 10 "DHCP_DDNS_CFG_FILE_RELOAD_ERROR" 1
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: D2 did not report reload error.\n"
+    clean_exit 1
+fi
+
+# Make sure the server is still operational.
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: D2 process was killed when attempting reconfiguration.\n"
+    clean_exit 1
+fi
+
+# Restore the good configuration.
+create_config "${CONFIG}"
+
+# Reconfigure the server with SIGHUP.
+send_signal 1
+
+# There should be two occurrences of the DCTL_CONFIG_COMPLETE messages.
+# Wait for it up to 10s.
+wait_for_message 10 "DCTL_CONFIG_COMPLETE" 2
+
+# After receiving SIGHUP the server should get reconfigured and the
+# reconfiguration should be noted in the log file. We should now
+# have two configurations logged in the log file.
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: D2 hasn't been reconfigured.\n"
+    clean_exit 1
+else
+    printf "D2 successfully reconfigured.\n"
+fi
+
+# Make sure the server is still operational.
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: D2 process was killed when attempting reconfiguration.\n"
+    clean_exit 1
+fi
+
+# All ok. Shut down Kea and exit.
+clean_exit 0

+ 115 - 0
src/bin/d2/tests/d2_shutdown_test.sh

@@ -0,0 +1,115 @@
+# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+if [ $# -ne 2 ]; then
+    printf "USAGE: d2_shutdown_test.sh <test_name> <signal_num>\n"
+    exit 1
+fi
+
+# Test name
+TEST_NAME=$1
+# Signal number to be used for this test.
+SIG_NUM=$2
+# Path to the temporary configuration file.
+CFG_FILE="test_config.json"
+# Path to the Kea log file.
+LOG_FILE="test.log"
+# Kea configuration to be stored in the configuration file.
+CONFIG="{
+    \"DhcpDdns\":
+    {
+        \"ip_address\": \"127.0.0.1\",
+        \"port\": 53001,
+        \"tsig_keys\": [],
+        \"forward_ddns\" : {},
+        \"reverse_ddns\" : {}
+    }
+}"
+
+# Set the location of the executable.
+BIN="b10-dhcp-ddns"
+BIN_PATH=".."
+
+# Import common test library.
+. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
+
+# Log the start of the test and print test name.
+test_start
+# Remove dangling Kea instances and remove log files.
+cleanup
+# Create new configuration file.
+create_config "${CONFIG}"
+# Instruct Kea to log to the specific file.
+set_logger
+# Start Kea.
+start_kea
+# Wait up to 20s for Kea to start.
+wait_for_kea 20
+if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
+    printf "ERROR: timeout waiting for D2 to start.\n".
+    clean_exit 1
+fi
+
+# Check if it is still running. It could have terminated (e.g. as a result
+# of configuration failure).
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 1 ]; then
+    printf "ERROR: expected one Kea process to be started. Found %d processes\
+ started.\n" ${_GET_PIDS_NUM}
+    clean_exit 1
+fi
+
+# Check in the log file, how many times D2 been configured. It should
+# be just once on startup.
+get_reconfigs
+if [ ${_GET_RECONFIGS} -ne 1 ]; then
+    printf "ERROR: D2 hasn't been configured.\n"
+    clean_exit 1
+else
+    printf "D2 successfully configured.\n"
+fi
+
+# Send signal to D2 (SIGTERM, SIGINT etc.)
+send_signal ${SIG_NUM}
+
+# Wait up to 10s for the D2Controller to log shutdown received.
+wait_for_message 10 "DHCP_DDNS_SHUTDOWN_SIGNAL_RECVD" 1
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: D2Process did report the shutdown signal receipt.\n"
+    clean_exit 1
+fi
+
+# Wait up to 10s for the D2Process to log graceful shutdown.
+wait_for_message 10 "DHCP_DDNS_CLEARED_FOR_SHUTDOWN" 1
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: D2Process did not log orderly shutdown.\n"
+    clean_exit 1
+fi
+
+# Now wait for D2Controller to log that it is exiting.
+wait_for_message 10 "DCTL_STOPPING" 1
+if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
+    printf "ERROR: D2Controller did not log shutdown.\n"
+    clean_exit 1
+fi
+
+# Server should have shut down.
+get_pids
+if [ ${_GET_PIDS_NUM} -ne 0 ]; then
+    printf "ERROR: D2 did not shut down after receiving signal.\n"\
+ ${_GET_PIDS_NUM}
+    clean_exit 1
+fi
+
+clean_exit 0

+ 16 - 0
src/bin/d2/tests/d2_sigint_test.sh

@@ -0,0 +1,16 @@
+# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Run a test that sends SIGINT to Kea and checks if it shuts down gracefully.
+$(dirname $0)/d2_shutdown_test.sh "D2.sigint" 2

+ 16 - 0
src/bin/d2/tests/d2_sigterm_test.sh

@@ -0,0 +1,16 @@
+# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Run a test that sends SIGTERM to Kea and checks if it shuts down gracefully.
+$(dirname $0)/d2_shutdown_test.sh "D2.sigterm" 15

+ 133 - 47
src/bin/d2/tests/d_controller_unittests.cc

@@ -32,15 +32,19 @@ namespace d2 {
 /// has been constructed to exercise DControllerBase.
 class DStubControllerTest : public DControllerTest {
 public:
-
     /// @brief Constructor.
     /// Note the constructor passes in the static DStubController instance
     /// method.
     DStubControllerTest() : DControllerTest (DStubController::instance) {
+        controller_ = boost::dynamic_pointer_cast<DStubController>
+                                                 (DControllerTest::
+                                                  getController());
     }
 
     virtual ~DStubControllerTest() {
     }
+
+    DStubControllerPtr controller_;
 };
 
 /// @brief Basic Controller instantiation testing.
@@ -183,35 +187,15 @@ TEST_F(DStubControllerTest, launchProcessInitError) {
 /// launches with a valid, command line, with a valid configuration file
 ///  and no simulated errors.
 TEST_F(DStubControllerTest, launchNormalShutdown) {
-    // command line to run standalone
-    char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-c"),
-                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
-                     const_cast<char*>("-v") };
-    int argc = 4;
-
-    // Create a non-empty, config file.  writeFile will wrap the contents
-    // with the module name for us.
-    writeFile("{}");
-
-    // Use an asiolink IntervalTimer and callback to generate the
-    // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
-    isc::asiolink::IntervalTimer timer(*getIOService());
-    timer.setup(genShutdownCallback, 2 * 1000);
-
-    // Record start time, and invoke launch().
-    ptime start = microsec_clock::universal_time();
-    EXPECT_NO_THROW(launch(argc, argv));
-
-    // Record stop time.
-    ptime stop = microsec_clock::universal_time();
+    // Write the valid, empty, config and then run launch() for 1000 ms
+    time_duration elapsed_time;
+    ASSERT_NO_THROW(runWithConfig("{}", 2000, elapsed_time));
 
     // Verify that duration of the run invocation is the same as the
     // timer duration.  This demonstrates that the shutdown was driven
     // by an io_service event and callback.
-    time_duration elapsed = stop - start;
-    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
-                elapsed.total_milliseconds() <= 2200);
+    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
+                elapsed_time.total_milliseconds() <= 2300);
 }
 
 /// @brief Tests launch with an nonexistant configuration file.
@@ -255,35 +239,20 @@ TEST_F(DStubControllerTest, missingConfigFileArgument) {
 /// the process event loop. It launches wih a valid, stand-alone command line
 /// and no simulated errors.  Launch should throw ProcessRunError.
 TEST_F(DStubControllerTest, launchRuntimeError) {
-    // command line to run standalone
-    char* argv[] = { const_cast<char*>("progName"),
-                     const_cast<char*>("-c"),
-                     const_cast<char*>(DControllerTest::CFG_TEST_FILE),
-                     const_cast<char*>("-v") };
-    int argc = 4;
-
-    // Create a non-empty, config file.  writeFile will wrap the contents
-    // with the module name for us.
-    writeFile("{}");
-
     // Use an asiolink IntervalTimer and callback to generate the
     // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
     isc::asiolink::IntervalTimer timer(*getIOService());
-    timer.setup(genFatalErrorCallback, 2 * 1000);
+    timer.setup(genFatalErrorCallback, 2000);
 
-    // Record start time, and invoke launch().
-    ptime start = microsec_clock::universal_time();
-    EXPECT_THROW(launch(argc, argv), ProcessRunError);
-
-    // Record stop time.
-    ptime stop = microsec_clock::universal_time();
+    // Write the valid, empty, config and then run launch() for 1000 ms
+    time_duration elapsed_time;
+    EXPECT_THROW(runWithConfig("{}", 2000, elapsed_time), ProcessRunError);
 
     // Verify that duration of the run invocation is the same as the
     // timer duration.  This demonstrates that the shutdown was driven
     // by an io_service event and callback.
-    time_duration elapsed = stop - start;
-    EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
-                elapsed.total_milliseconds() <= 2200);
+    EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
+                elapsed_time.total_milliseconds() <= 2300);
 }
 
 /// @brief Configuration update event testing.
@@ -380,5 +349,122 @@ TEST_F(DStubControllerTest, executeCommandTests) {
     EXPECT_EQ(COMMAND_ERROR, rcode);
 }
 
+// Tests that registered signals are caught and handled.
+TEST_F(DStubControllerTest, ioSignals) {
+    // Tell test controller just to record the signals, don't call the
+    // base class signal handler.
+    controller_->recordSignalOnly(true);
+
+    // Setup to raise SIGHUP in 10 ms.
+    TimedSignal sighup(*getIOService(), SIGHUP, 10);
+    TimedSignal sigint(*getIOService(), SIGINT, 10);
+    TimedSignal sigterm(*getIOService(), SIGTERM, 10);
+
+    // Write the valid, empty, config and then run launch() for 500 ms
+    time_duration elapsed_time;
+    runWithConfig("{}", 500, elapsed_time);
+
+    // Verify that we caught the signals as expected.
+    std::vector<int>& signals = controller_->getProcessedSignals();
+    ASSERT_EQ(3, signals.size());
+    EXPECT_EQ(SIGHUP, signals[0]);
+    EXPECT_EQ(SIGINT, signals[1]);
+    EXPECT_EQ(SIGTERM, signals[2]);
+}
+
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(DStubControllerTest, invalidConfigReload) {
+    // Schedule to rewrite the configuration file after launch. This way the
+    // file is updated after we have done the initial configuration.  The
+    // new content is invalid JSON which will cause the config parse to fail.
+    scheduleTimedWrite("{ \"string_test\": BOGUS JSON }", 100);
+
+    // Setup to raise SIGHUP in 200 ms.
+    TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+    // Write the config and then run launch() for 500 ms
+    // After startup, which will load the initial configuration this enters
+    // the process's runIO() loop. We will first rewrite the config file.
+    // Next we process the SIGHUP signal which should cause us to reconfigure.
+    time_duration elapsed_time;
+    runWithConfig("{ \"string_test\": \"first value\" }", 500, elapsed_time);
+
+    // Context is still available post launch. Check to see that our
+    // configuration value is still the original value.
+    std::string  actual_value = "";
+    ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
+    EXPECT_EQ("first value", actual_value);
+
+    // Verify that we saw the signal.
+    std::vector<int>& signals = controller_->getProcessedSignals();
+    ASSERT_EQ(1, signals.size());
+    EXPECT_EQ(SIGHUP, signals[0]);
+}
+
+// Tests that the original configuration is replaced after a SIGHUP triggered
+// reconfiguration succeeds.
+TEST_F(DStubControllerTest, validConfigReload) {
+    // Schedule to rewrite the configuration file after launch. This way the
+    // file is updated after we have done the initial configuration.
+    scheduleTimedWrite("{ \"string_test\": \"second value\" }", 100);
+
+    // Setup to raise SIGHUP in 200 ms and another at 400 ms.
+    TimedSignal sighup(*getIOService(), SIGHUP, 200);
+    TimedSignal sighup2(*getIOService(), SIGHUP, 400);
+
+    // Write the config and then run launch() for 800 ms
+    time_duration elapsed_time;
+    runWithConfig("{ \"string_test\": \"first value\" }", 800, elapsed_time);
+
+    // Context is still available post launch.
+    // Check to see that our configuration value is what we expect.
+    std::string  actual_value = "";
+    ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
+    EXPECT_EQ("second value", actual_value);
+
+    // Verify that we saw two occurrences of the signal.
+    std::vector<int>& signals = controller_->getProcessedSignals();
+    ASSERT_EQ(2, signals.size());
+    EXPECT_EQ(SIGHUP, signals[0]);
+    EXPECT_EQ(SIGHUP, signals[1]);
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(DStubControllerTest, sigintShutdown) {
+    // Setup to raise SIGHUP in 1 ms.
+    TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+    // Write the config and then run launch() for 1000 ms
+    time_duration elapsed_time;
+    runWithConfig("{ \"string_test\": \"first value\" }", 1000, elapsed_time);
+
+    // Verify that we saw the signal.
+    std::vector<int>& signals = controller_->getProcessedSignals();
+    ASSERT_EQ(1, signals.size());
+    EXPECT_EQ(SIGINT, signals[0]);
+
+    // Duration should be significantly less than our max run time.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+}
+
+// Tests that the SIGTERM triggers a normal shutdown.
+TEST_F(DStubControllerTest, sigtermShutdown) {
+    // Setup to raise SIGHUP in 1 ms.
+    TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+    // Write the config and then run launch() for 1000 ms
+    time_duration elapsed_time;
+    runWithConfig("{ \"string_test\": \"first value\" }", 1000, elapsed_time);
+
+    // Verify that we saw the signal.
+    std::vector<int>& signals = controller_->getProcessedSignals();
+    ASSERT_EQ(1, signals.size());
+    EXPECT_EQ(SIGTERM, signals[0]);
+
+    // Duration should be significantly less than our max run time.
+    EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+}
+
 }; // end of isc::d2 namespace
 }; // end of isc namespace

+ 104 - 5
src/bin/d2/tests/d_test_stubs.cc

@@ -96,14 +96,14 @@ DStubProcess::shutdown(isc::data::ConstElementPtr /* args */) {
 }
 
 isc::data::ConstElementPtr
-DStubProcess::configure(isc::data::ConstElementPtr /*config_set*/) {
+DStubProcess::configure(isc::data::ConstElementPtr config_set) {
     if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
         // Simulates a process configure failure.
         return (isc::config::createAnswer(1,
                 "Simulated process configuration error."));
     }
 
-    return (isc::config::createAnswer(0, "Configuration accepted."));
+    return (getCfgMgr()->parseConfig(config_set));
 }
 
 isc::data::ConstElementPtr
@@ -154,7 +154,8 @@ DStubController::instance() {
 }
 
 DStubController::DStubController()
-    : DControllerBase(stub_app_name_, stub_bin_name_) {
+    : DControllerBase(stub_app_name_, stub_bin_name_),
+      processed_signals_(), record_signal_only_(false) {
 
     if (getenv("B10_FROM_BUILD")) {
         setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
@@ -213,9 +214,108 @@ const std::string DStubController::getCustomOpts() const {
     return (std::string(stub_option_x_));
 }
 
+void
+DStubController::processSignal(int signum){
+    processed_signals_.push_back(signum);
+    if (record_signal_only_) {
+        return;
+    }
+
+    DControllerBase::processSignal(signum);
+}
+
 DStubController::~DStubController() {
 }
 
+//************************** DControllerTest *************************
+
+void
+DControllerTest::writeFile(const std::string& content,
+                           const std::string& module_name) {
+    std::ofstream out(CFG_TEST_FILE, std::ios::trunc);
+    ASSERT_TRUE(out.is_open());
+
+    out << "{ \"" << (!module_name.empty() ? module_name
+                      : getController()->getAppName())
+        << "\": " << std::endl;
+
+    out << content;
+    out << " } " << std::endl;
+    out.close();
+}
+
+void
+DControllerTest::timedWriteCallback() {
+    writeFile(new_cfg_content_);
+}
+
+void
+DControllerTest::scheduleTimedWrite(const std::string& config,
+                                    int write_time_ms) {
+    new_cfg_content_ = config;
+    write_timer_.reset(new asiolink::IntervalTimer(*getIOService()));
+    write_timer_->setup(boost::bind(&DControllerTest::timedWriteCallback, this),
+                        write_time_ms, asiolink::IntervalTimer::ONE_SHOT);
+}
+
+void
+DControllerTest::runWithConfig(const std::string& config, int run_time_ms,
+                               time_duration& elapsed_time) {
+    // Create the config file.
+    writeFile(config);
+
+    // Shutdown (without error) after runtime.
+    isc::asiolink::IntervalTimer timer(*getIOService());
+    timer.setup(genShutdownCallback, run_time_ms);
+
+    // Record start time, and invoke launch().
+    // We catch and rethrow to allow testing error scenarios.
+    ptime start = microsec_clock::universal_time();
+    try  {
+        // Set up valid command line arguments
+        char* argv[] = { const_cast<char*>("progName"),
+                         const_cast<char*>("-c"),
+                         const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+                         const_cast<char*>("-v") };
+        launch(4, argv);
+    } catch (...) {
+        // calculate elasped time, then rethrow it
+        elapsed_time = microsec_clock::universal_time() - start;
+        throw;
+    }
+
+    elapsed_time = microsec_clock::universal_time() - start;
+}
+
+DProcessBasePtr
+DControllerTest:: getProcess() {
+    DProcessBasePtr p;
+    if (getController()) {
+        p = getController()->getProcess();
+    }
+    return (p);
+}
+
+DCfgMgrBasePtr
+DControllerTest::getCfgMgr() {
+    DCfgMgrBasePtr p;
+    if (getProcess()) {
+        p = getProcess()->getCfgMgr();
+    }
+
+    return (p);
+}
+
+DCfgContextBasePtr
+DControllerTest::getContext() {
+    DCfgContextBasePtr p;
+    if (getCfgMgr()) {
+        p = getCfgMgr()->getContext();
+    }
+
+    return (p);
+}
+
 // Initialize controller wrapper's static instance getter member.
 DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
 
@@ -290,7 +390,7 @@ DStubCfgMgr::DStubCfgMgr()
 DStubCfgMgr::~DStubCfgMgr() {
 }
 
-DCfgContextBasePtr 
+DCfgContextBasePtr
 DStubCfgMgr::createNewContext() {
     return (DCfgContextBasePtr (new DStubContext()));
 }
@@ -300,7 +400,6 @@ DStubCfgMgr::createConfigParser(const std::string& element_id) {
     isc::dhcp::ParserPtr parser;
     DStubContextPtr context
         = boost::dynamic_pointer_cast<DStubContext>(getContext());
-
     if (element_id == "bool_test") {
         parser.reset(new isc::dhcp::
                          BooleanParser(element_id,

+ 172 - 12
src/bin/d2/tests/d_test_stubs.h

@@ -23,6 +23,10 @@
 #include <d2/d_controller.h>
 #include <d2/d_cfg_mgr.h>
 
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+using namespace boost::posix_time;
+
 #include <gtest/gtest.h>
 
 #include <fstream>
@@ -213,6 +217,25 @@ public:
     /// @brief Defines the executable name used to construct the controller
     static const char* stub_bin_name_;
 
+    /// @brief Gets the list of signals that have been caught and processed.
+    std::vector<int>& getProcessedSignals() {
+        return (processed_signals_);
+    }
+
+    /// @brief Controls whether signals are processed in full or merely
+    /// recorded.
+    ///
+    /// If true, signal handling will stop after recording the signal.
+    /// Otherwise the base class signal handler,
+    /// DControllerBase::processSignals will also be invoked. This switch is
+    /// useful for ensuring that IOSignals are delivered as expected without
+    /// incurring the full impact such as reconfiguring or shutting down.
+    ///
+    /// @param value boolean which if true enables record-only behavior
+    void recordSignalOnly(bool value) {
+       record_signal_only_ = value;
+    }
+
 protected:
     /// @brief Handles additional command line options that are supported
     /// by DStubController.  This implementation supports an option "-x".
@@ -259,14 +282,33 @@ protected:
     /// @return returns a string containing the option letters.
     virtual const std::string getCustomOpts() const;
 
+    /// @brief Application-level "signal handler"
+    ///
+    /// Overrides the base class implementation such that this method
+    /// is invoked each time an IOSignal is processed.  It records the
+    /// signal received and unless we are in record-only behavior, it
+    /// in invokes the base class implementation.
+    ///
+    /// @param signum OS signal value received
+    virtual void processSignal(int signum);
+
 private:
     /// @brief Constructor is private to protect singleton integrity.
     DStubController();
 
+    /// @brief Vector to record the signal values received.
+    std::vector<int> processed_signals_;
+
+    /// @brief Boolean for controlling if signals are merely recorded.
+    bool record_signal_only_;
+
 public:
     virtual ~DStubController();
 };
 
+/// @brief Defines a pointer to a DStubController.
+typedef boost::shared_ptr<DStubController> DStubControllerPtr;
+
 /// @brief Abstract Test fixture class that wraps a DControllerBase. This class
 /// is a friend class of DControllerBase which allows it access to class
 /// content to facilitate testing.  It provides numerous wrapper methods for
@@ -285,7 +327,8 @@ public:
     ///
     /// @param instance_getter is a function pointer to the static instance
     /// method of the DControllerBase derivation under test.
-    DControllerTest(InstanceGetter instance_getter) {
+    DControllerTest(InstanceGetter instance_getter)
+         : write_timer_(), new_cfg_content_() {
         // Set the static fetcher member, then invoke it via getController.
         // This ensures the singleton is instantiated.
         instanceGetter_ = instance_getter;
@@ -296,6 +339,10 @@ public:
     /// Note the controller singleton is destroyed. This is essential to ensure
     /// a clean start between tests.
     virtual ~DControllerTest() {
+        if (write_timer_) {
+            write_timer_->cancel();
+        }
+
         getController().reset();
         static_cast<void>(unlink(CFG_TEST_FILE));
     }
@@ -447,20 +494,71 @@ public:
     /// @param content JSON text to be written to file
     /// @param module_name  content content to be written to file
     void writeFile(const std::string& content,
-                   const std::string& module_name = "") {
-        std::ofstream out(CFG_TEST_FILE, std::ios::trunc);
-        ASSERT_TRUE(out.is_open());
+                   const std::string& module_name = "");
 
-        out << "{ \"" << (!module_name.empty() ? module_name :
-                          getController()->getAppName())
-             << "\": " << std::endl;
+    /// @brief Method used as timer callback to invoke writeFile.
+    ///
+    /// Wraps a call to writeFile passing in new_cfg_content_.  This allows
+    /// the method to be bound as an IntervalTimer callback.
+    virtual void timedWriteCallback();
 
-        out << content;
-        out << " } " << std::endl;
-        out.close();
-    }
+    /// @brief Schedules the given content to overwrite the config file.
+    ///
+    /// Creates a one-shot IntervalTimer whose callback will overwrite the
+    /// configuration with the given content.  This allows the configuration
+    /// file to replaced write_time_ms after DControllerBase::launch() has
+    /// invoked runProcess().
+    ///
+    /// @param config JSON string containing the deisred content for the config
+    /// file.
+    /// @param write_time_ms time in milliseconds to delay before writing the
+    /// file.
+    void scheduleTimedWrite(const std::string& config, int write_time_ms);
+
+    /// @brief Convenience method for invoking standard, valid launch
+    ///
+    /// This method sets up a timed run of the DController::launch.  It does
+    /// the following:
+    /// - It creates command line argument variables argc/argv
+    /// - Invokes writeFile to create the config file with the given content
+    /// - Schedules a shutdown time timer to call DController::executeShutdown
+    /// after the interval
+    /// - Records the start time
+    /// - Invokes DController::launch() with the command line arguments
+    /// - After launch returns, it calculates the elapsed time and returns it
+    ///
+    /// @param config configuration file content to write before calling launch
+    /// @param run_time_ms  maximum amount of time to allow runProcess() to
+    /// continue.
+    /// @param[out] elapsed_time the actual time in ms spent in launch().
+    void runWithConfig(const std::string& config, int run_time_ms,
+                       time_duration& elapsed_time);
+
+    /// @brief Fetches the controller's process
+    ///
+    /// @return A pointer to the process which may be null if it has not yet
+    /// been instantiated.
+    DProcessBasePtr getProcess();
+
+    /// @brief Fetches the process's configuration manager
+    ///
+    /// @return A pointer to the manager which may be null if it has not yet
+    /// been instantiated.
+    DCfgMgrBasePtr getCfgMgr();
+
+    /// @brief Fetches the configuration manager's context
+    ///
+    /// @return A pointer to the context which may be null if it has not yet
+    /// been instantiated.
+    DCfgContextBasePtr getContext();
+
+    /// @brief Timer used for delayed configuration file writing.
+    asiolink::IntervalTimerPtr write_timer_;
 
-    /// Name of a config file used during tests
+    /// @brief String which contains the content delayed file writing will use.
+    std::string new_cfg_content_;
+
+    /// @brief Name of a config file used during tests
     static const char* CFG_TEST_FILE;
 };
 
@@ -683,6 +781,68 @@ public:
     isc::data::ConstElementPtr answer_;
 };
 
+/// @brief Implements a time-delayed signal
+///
+/// Given an IOService, a signal number, and a time period, this class will
+/// send (raise) the signal to the current process.
+class TimedSignal {
+public:
+    /// @brief Constructor
+    ///
+    /// @param io_service  IOService to run the timer
+    /// @param signum OS signal value (e.g. SIGHUP, SIGUSR1 ...)
+    /// @param milliseconds time in milliseconds to wait until the signal is
+    /// raised.
+    /// @param mode selects between a one-shot signal or a signal which repeats
+    /// at "milliseconds" interval.
+    TimedSignal(asiolink::IOService& io_service, int signum, int milliseconds,
+                const asiolink::IntervalTimer::Mode& mode =
+                asiolink::IntervalTimer::ONE_SHOT)
+        : timer_(new asiolink::IntervalTimer(io_service)) {
+        timer_->setup(SendSignalCallback(signum), milliseconds, mode);
+    }
+
+    /// @brief Cancels the given timer.
+    void cancel() {
+        if (timer_) {
+            timer_->cancel();
+        }
+    }
+
+    /// @brief Destructor.
+    ~TimedSignal() {
+        cancel();
+    }
+
+    /// @brief Callback for the TimeSignal's internal timer.
+    class SendSignalCallback: public std::unary_function<void, void> {
+    public:
+
+        /// @brief Constructor
+        ///
+        /// @param signum OS signal value of the signal to send
+        SendSignalCallback(int signum) : signum_(signum) {
+        }
+
+        /// @brief Callback method invoked when the timer expires
+        ///
+        /// Calls raise with the given signal which should generate that
+        /// signal to the given process.
+        void operator()() {
+            ASSERT_EQ(0, raise(signum_));
+            return;
+        }
+
+    private:
+        /// @brief Stores the OS signal value to send.
+        int signum_;
+    };
+
+private:
+    /// @brief Timer which controls when the signal is sent.
+    asiolink::IntervalTimerPtr timer_;
+};
+
 /// @brief Defines a small but valid DHCP-DDNS compliant configuration for
 /// testing configuration parsing fundamentals.
 extern const char* valid_d2_config;

+ 375 - 0
src/bin/d2/tests/io_service_signal_unittests.cc

@@ -0,0 +1,375 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d_test_stubs.h>
+#include <d2/io_service_signal.h>
+
+#include <gtest/gtest.h>
+
+#include <queue>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture for testing the use of IOSignals.
+///
+/// This fixture is exercises IOSignaling as it is intended to be used in
+/// an application in conjuction with util::SignalSet.
+class IOSignalTest : public ::testing::Test {
+public:
+    /// @brief IOService instance to process IO.
+    IOServicePtr io_service_;
+    /// @brief Failsafe timer to ensure test(s) do not hang.
+    isc::asiolink::IntervalTimer test_timer_;
+    /// @brief Maximum time should be allowed to run.
+    int test_time_ms_;
+
+    /// @brief SignalSet object so we can catch real signals.
+    util::SignalSetPtr signal_set_;
+
+    /// @brief IOSignalQueue so we can generate IOSignals.
+    IOSignalQueuePtr io_signal_queue_;
+
+    /// @brief Vector to record the signal values received.
+    std::vector<int> processed_signals_;
+    /// @brief The number of signals that must be received to stop the test.
+    int stop_at_count_;
+    /// @brief Boolean which causes IOSignalHandler to throw if true.
+    bool handler_throw_error_;
+
+    /// @brief Constructor
+    IOSignalTest() :
+        io_service_(new asiolink::IOService()), test_timer_(*io_service_),
+        test_time_ms_(0), signal_set_(),
+        io_signal_queue_(new IOSignalQueue(io_service_)),
+        processed_signals_(), stop_at_count_(0), handler_throw_error_(false) {
+    }
+
+    /// @brief Destructor
+    ~IOSignalTest() {
+        if (signal_set_) {
+            signal_set_->clear();
+        }
+
+        // clear the on-receipt handler
+        util::SignalSet::clearOnReceiptHandler();
+    }
+
+    /// @brief On-receipt signal handler used by unit tests.
+    ///
+    /// This function is registered with SignalSet as the "on-receipt" handler.
+    /// When an OS signal is caught it schedules an IOSignal.
+    ///
+    /// @param signum Signal being handled.
+    bool onReceiptHandler(int signum) {
+        // Queue up a signal binging processSignal instance method as the
+        // IOSignalHandler.
+        io_signal_queue_->pushSignal(signum,
+                                  boost::bind(&IOSignalTest::processSignal,
+                                              this, _1));
+
+        // Return true so SignalSet knows the signal has been consumed.
+        return (true);
+    }
+
+    /// @brief Method used as the IOSignalHandler.
+    ///
+    /// Records the value of the given signal and checks if the desired
+    /// number of signals have been received.  If so, the IOService is
+    /// stopped which will cause IOService::run() to exit, returning control
+    /// to the test.
+    ///
+    /// @param sequence_id id of the IOSignal received
+    void processSignal(IOSignalId sequence_id) {
+        // Pop the signal instance off the queue.  This should make us
+        // the only one holding it, so when we leave it should be freed.
+        IOSignalPtr signal = io_signal_queue_->popSignal(sequence_id);
+
+        // Remember the signal we got.
+        processed_signals_.push_back(signal->getSignum());
+
+        // If the flag is on, force a throw to test error handling.
+        if (handler_throw_error_) {
+            handler_throw_error_ = false;
+            isc_throw(BadValue, "processSignal throwing simulated error");
+        }
+
+        // If we've hit the number we want stop the IOService. This will cause
+        // run to exit.
+        if (processed_signals_.size() >= stop_at_count_) {
+            io_service_->stop();
+        }
+    }
+
+    /// @brief Sets the failsafe timer for the test to the given time.
+    ///
+    /// @param  test_time_ms maximum time in milliseconds the test should
+    /// be allowed to run.
+    void setTestTime(int test_time_ms) {
+        // Fail safe shutdown
+        test_time_ms_ = test_time_ms;
+        test_timer_.setup(boost::bind(&IOSignalTest::testTimerHandler,
+                                      this),
+                          test_time_ms_, asiolink::IntervalTimer::ONE_SHOT);
+    }
+
+    /// @brief Failsafe timer expiration handler.
+    void testTimerHandler() {
+        io_service_->stop();
+        FAIL() << "Test Time: " << test_time_ms_ << " expired";
+    }
+};
+
+// Used for constuctor tests.
+void dummyHandler(IOSignalId) {
+}
+
+// Tests IOSignal constructor.
+TEST(IOSignal, construction) {
+    IOServicePtr io_service(new asiolink::IOService());
+    IOSignalPtr signal;
+    IOSignalPtr signal2;
+
+    // Verify that handler cannot be empty.
+    ASSERT_THROW(signal.reset(new IOSignal(*io_service, SIGINT,
+                                           IOSignalHandler())),
+                 IOSignalError);
+
+    // Verify constructor with valid arguments works.
+    ASSERT_NO_THROW(signal.reset(new IOSignal(*io_service, SIGINT,
+                                              dummyHandler)));
+    // Verify sequence_id is set.
+    EXPECT_EQ(IOSignal::nextSequenceId()-1, signal->getSequenceId());
+
+    // Verify SIGINT is correct.
+    EXPECT_EQ(SIGINT, signal->getSignum());
+
+    // Make a second signal.
+    ASSERT_NO_THROW(signal2.reset(new IOSignal(*io_service, SIGUSR1,
+                                               dummyHandler)));
+
+    // Verify sequence_id is not the same as the previous one.
+    EXPECT_NE(signal2->getSequenceId(), signal->getSequenceId());
+
+    // Verify that the signal value is correct.
+    EXPECT_EQ(SIGUSR1, signal2->getSignum());
+}
+
+// Tests IOSignalQueue constructors and exercises queuing methods.
+TEST(IOSignalQueue, constructionAndQueuing) {
+    IOSignalQueuePtr queue;
+    IOServicePtr io_service;
+
+    // Verify constructing with an empty IOService will throw.
+    ASSERT_THROW(queue.reset(new IOSignalQueue(io_service)), IOSignalError);
+
+    // Verify valid construction works.
+    io_service.reset(new asiolink::IOService());
+    ASSERT_NO_THROW(queue.reset(new IOSignalQueue(io_service)));
+
+    // Verify an empty handler is not allowed.
+    ASSERT_THROW(queue->pushSignal(SIGINT, IOSignalHandler()), IOSignalError);
+
+    // Verify that we can queue valid entries.
+    std::vector<IOSignalId> ids;
+    ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGINT, dummyHandler)));
+    ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGUSR1, dummyHandler)));
+    ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGUSR2, dummyHandler)));
+
+    // Now verify that we can pop each one and what we pop is correct.
+    // Verify popping it again, throws.  We'll do it in a non-sequential order.
+
+    // Check the middle one.
+    IOSignalPtr signal;
+    ASSERT_NO_THROW(signal = queue->popSignal(ids[1]));
+    ASSERT_TRUE(signal);
+    EXPECT_EQ(ids[1], signal->getSequenceId());
+    EXPECT_EQ(SIGUSR1, signal->getSignum());
+    ASSERT_THROW(queue->popSignal(ids[1]), IOSignalError);
+
+    // Check the first one.
+    ASSERT_NO_THROW(signal = queue->popSignal(ids[0]));
+    ASSERT_TRUE(signal);
+    EXPECT_EQ(ids[0], signal->getSequenceId());
+    EXPECT_EQ(SIGINT, signal->getSignum());
+    ASSERT_THROW(queue->popSignal(ids[0]), IOSignalError);
+
+    // Check the last one.
+    ASSERT_NO_THROW(signal = queue->popSignal(ids[2]));
+    ASSERT_TRUE(signal);
+    EXPECT_EQ(ids[2], signal->getSequenceId());
+    EXPECT_EQ(SIGUSR2, signal->getSignum());
+    ASSERT_THROW(queue->popSignal(ids[2]), IOSignalError);
+
+    // Now we will test clearing the queue.  Queue three signals.
+    ids.clear();
+    for (int i = 0; i < 3; ++i) {
+        ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGINT, dummyHandler)));
+    }
+
+    // Now clear the queue.
+    ASSERT_NO_THROW(queue->clear());
+
+    // We should not be able to dequeue any of them.
+    for (int i = 0; i < 3; ++i) {
+        ASSERT_THROW(queue->popSignal(ids[i]), IOSignalError);
+    }
+}
+
+// Test the basic mechanics of IOSignal by handling one signal occurrence.
+TEST_F(IOSignalTest, singleSignalTest) {
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Register the onreceipt-handler with SignalSet.
+    // We set this up to catch the actual signal.  The onreceipt handler
+    // creates an IOSignal which should propagate the signal as a
+    // IOService event.
+    util::SignalSet::
+    setOnReceiptHandler(boost::bind(&IOSignalTest::onReceiptHandler,
+                                    this, _1));
+
+    // Register to receive SIGINT.
+    ASSERT_NO_THROW(signal_set_.reset(new util::SignalSet(SIGINT)));
+
+    // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
+    TimedSignal sig_int(*io_service_, SIGINT, 100);
+
+    // The first handler executed is the IOSignal's internal timer expirey
+    // callback.
+    io_service_->run_one();
+
+    // The next handler executed is IOSignal's handler.
+    io_service_->run_one();
+
+    // Verify that we processed the signal.
+    ASSERT_EQ(1, processed_signals_.size());
+
+    // Now check that signal value is correct.
+    EXPECT_EQ(SIGINT, processed_signals_[0]);
+}
+
+
+// Test verifies that signals can be delivered rapid-fire without falling over.
+TEST_F(IOSignalTest, hammer) {
+    // Set test fail safe.
+    setTestTime(5000);
+
+    // Register the onreceipt-handler with SignalSet, and register to receive
+    // SIGINT.
+    util::SignalSet::
+    setOnReceiptHandler(boost::bind(&IOSignalTest::onReceiptHandler,
+                                    this, _1));
+    ASSERT_NO_THROW(signal_set_.reset(new util::SignalSet(SIGINT)));
+
+    // Stop the test after 500 signals.
+    stop_at_count_ = 500;
+
+    // User a repeating TimedSignal so we should generate a signal every 1 ms
+    // until we hit our stop count.
+    TimedSignal sig_int(*io_service_, SIGINT, 1,
+                        asiolink::IntervalTimer::REPEATING);
+
+    // Start processing IO.  This should continue until we stop either by
+    // hitting the stop count or if things go wrong, max test time.
+    io_service_->run();
+
+    // Verify we received the expected number of signals.
+    EXPECT_EQ(stop_at_count_, processed_signals_.size());
+
+    // Now check that each signal value is correct. This is sort of a silly
+    // check but it does ensure things didn't go off the rails somewhere.
+    for (int i = 0; i < processed_signals_.size(); ++i) {
+        EXPECT_EQ(SIGINT, processed_signals_[i]);
+    }
+}
+
+// Verifies that handler exceptions are caught.
+TEST_F(IOSignalTest, handlerThrow) {
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Register the onreceipt-handler with SignalSet, and register to
+    // receive SIGINT.
+    util::SignalSet::
+    setOnReceiptHandler(boost::bind(&IOSignalTest::onReceiptHandler,
+                                    this, _1));
+    ASSERT_NO_THROW(signal_set_.reset(new util::SignalSet(SIGINT)));
+
+    // Set the stop after we've done at least 1 all the way through.
+    stop_at_count_ = 1;
+
+    // Use TimedSignal to generate SIGINT after we start IOService::run.
+    TimedSignal sig_int(*io_service_, SIGINT, 100,
+                        asiolink::IntervalTimer::REPEATING);
+
+    // Set the test flag to cause the handler to throw an exception.
+    handler_throw_error_ = true;
+
+    // Start processing IO.  This should fail with the handler exception.
+    ASSERT_NO_THROW(io_service_->run());
+
+    // Verify that the we hit the throw block.  The flag will be false
+    // we will have skipped the stop count check so number signals processed
+    // is stop_at_count_ + 1.
+    EXPECT_FALSE(handler_throw_error_);
+    EXPECT_EQ(stop_at_count_ + 1, processed_signals_.size());
+}
+
+// Verifies that we can handle a mixed set of signals.
+TEST_F(IOSignalTest, mixedSignals) {
+    // Set test fail safe.
+    setTestTime(1000);
+
+    // Register the onreceipt-handler with SignalSet, and register to
+    // receive SIGINT, SIGUSR1, and SIGUSR2.
+    util::SignalSet::
+    setOnReceiptHandler(boost::bind(&IOSignalTest::onReceiptHandler,
+                                    this, _1));
+    ASSERT_NO_THROW(signal_set_.reset(new util::SignalSet(SIGINT, SIGUSR1,
+                                      SIGUSR2)));
+
+    // Stop the test after 21 signals.
+    stop_at_count_ = 21;
+
+    // User a repeating TimedSignal so we should generate a signal every 1 ms
+    // until we hit our stop count.
+    TimedSignal sig_1(*io_service_, SIGINT, 1,
+                      asiolink::IntervalTimer::REPEATING);
+    TimedSignal sig_2(*io_service_, SIGUSR1, 1,
+                      asiolink::IntervalTimer::REPEATING);
+    TimedSignal sig_3(*io_service_, SIGUSR2, 1,
+                      asiolink::IntervalTimer::REPEATING);
+
+    // Start processing IO.  This should continue until we stop either by
+    // hitting the stop count or if things go wrong, max test time.
+    io_service_->run();
+
+    // Verify we received the expected number of signals.
+    ASSERT_EQ(stop_at_count_, processed_signals_.size());
+
+    // If the underlying implmemeation is orderly, the signals should have
+    // been processed in sets of three: SIGINT, SIGUSR, SIGUSR
+    // It is conceivable under some OS's that they might not occur in this
+    // order.
+    for (int i = 0; i < 21; i += 3) {
+        EXPECT_EQ(SIGINT, processed_signals_[i]);
+        EXPECT_EQ(SIGUSR1, processed_signals_[i+1]);
+        EXPECT_EQ(SIGUSR2, processed_signals_[i+2]);
+    }
+}
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace

+ 22 - 8
src/lib/asiolink/interval_timer.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2104 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -44,7 +44,9 @@ private:
 public:
     IntervalTimerImpl(IOService& io_service);
     ~IntervalTimerImpl();
-    void setup(const IntervalTimer::Callback& cbfunc, const long interval);
+    void setup(const IntervalTimer::Callback& cbfunc, const long interval,
+               const IntervalTimer::Mode& interval_mode
+               = IntervalTimer::REPEATING);
     void callback(const asio::error_code& error);
     void cancel() {
         timer_.cancel();
@@ -60,13 +62,18 @@ private:
     long interval_;
     // asio timer
     asio::deadline_timer timer_;
+
+    // Controls how the timer behaves after expiration.
+    IntervalTimer::Mode mode_;
+
     // interval_ will be set to this value in destructor in order to detect
     // use-after-free type of bugs.
     static const long INVALIDATED_INTERVAL = -1;
 };
 
 IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
-    interval_(0), timer_(io_service.get_io_service())
+    interval_(0), timer_(io_service.get_io_service()),
+    mode_(IntervalTimer::REPEATING)
 {}
 
 IntervalTimerImpl::~IntervalTimerImpl() {
@@ -75,7 +82,8 @@ IntervalTimerImpl::~IntervalTimerImpl() {
 
 void
 IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
-                         const long interval)
+                         const long interval,
+                         const IntervalTimer::Mode& mode)
 {
     // Interval should not be less than or equal to 0.
     if (interval <= 0) {
@@ -88,6 +96,8 @@ IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
     }
     cbfunc_ = cbfunc;
     interval_ = interval;
+    mode_ = mode;
+
     // Set initial expire time.
     // At this point the timer is not running yet and will not expire.
     // After calling IOService::run(), the timer will expire.
@@ -118,8 +128,11 @@ IntervalTimerImpl::callback(const asio::error_code& ec) {
     if (interval_ == 0 || ec) {
         // timer has been canceled. Do nothing.
     } else {
-        // Set next expire time.
-        update();
+        // If we should repeat, set next expire time.
+        if (mode_ == IntervalTimer::REPEATING) {
+            update();
+        }
+
         // Invoke the call back function.
         cbfunc_();
     }
@@ -135,8 +148,9 @@ IntervalTimer::~IntervalTimer() {
 }
 
 void
-IntervalTimer::setup(const Callback& cbfunc, const long interval) {
-    return (impl_->setup(cbfunc, interval));
+IntervalTimer::setup(const Callback& cbfunc, const long interval,
+                     const IntervalTimer::Mode& mode) {
+    return (impl_->setup(cbfunc, interval, mode));
 }
 
 void

+ 23 - 6
src/lib/asiolink/interval_timer.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -31,13 +31,15 @@ class IntervalTimerImpl;
 /// This class is implemented to use \c asio::deadline_timer as interval
 /// timer.
 ///
-/// \c setup() sets a timer to expire on (now + interval) and a call back
-/// function.
+/// \c setup() sets a timer to expire on (now + interval), a call back
+/// function, and an interval mode.
 ///
 /// \c IntervalTimerImpl::callback() is called by the timer when it expires.
 ///
-/// The function calls the call back function set by \c setup() and updates
-/// the timer to expire in (now + interval) milliseconds.
+/// The function calls the call back function set by \c setup() and if the
+/// the interval mode indicates a repeating interval, will reschedule the
+/// timer to expire in (now + interval) milliseconds.
+///
 /// The type of call back function is \c void(void).
 ///
 /// The call back function will not be called if the instance of this class is
@@ -60,6 +62,15 @@ public:
     /// \name The type of timer callback function
     typedef boost::function<void()> Callback;
 
+    /// \brief Defines possible timer modes used to setup a timer.
+    /// - REPEATING - Timer will reschedule itself after each expiration
+    /// - ONE_SHOT - Timer will expire after one interval and not reschedule.
+    enum Mode
+    {
+        REPEATING,
+        ONE_SHOT
+    };
+
     ///
     /// \name Constructors and Destructor
     ///
@@ -96,6 +107,9 @@ public:
     /// \param cbfunc A reference to a function \c void(void) to call back
     /// when the timer is expired (should not be an empty functor)
     /// \param interval Interval in milliseconds (greater than 0)
+    /// \param mode Determines if the timer will automatically reschedule after
+    /// each expiration (the default) or behave as a one-shot which will run
+    /// for a single interval and not reschedule.
     ///
     /// Note: IntervalTimer will not pass \c asio::error_code to
     /// call back function. In case the timer is canceled, the function
@@ -104,7 +118,8 @@ public:
     /// \throw isc::InvalidParameter cbfunc is empty
     /// \throw isc::BadValue interval is less than or equal to 0
     /// \throw isc::Unexpected internal runtime error
-    void setup(const Callback& cbfunc, const long interval);
+    void setup(const Callback& cbfunc, const long interval,
+                    const Mode& mode = REPEATING);
 
     /// Cancel the timer.
     ///
@@ -128,6 +143,8 @@ private:
     boost::shared_ptr<IntervalTimerImpl> impl_;
 };
 
+typedef boost::shared_ptr<isc::asiolink::IntervalTimer> IntervalTimerPtr;
+
 } // namespace asiolink
 } // namespace isc
 #endif // ASIOLINK_INTERVAL_TIMER_H

+ 82 - 1
src/lib/asiolink/tests/interval_timer_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -137,6 +137,20 @@ protected:
         IntervalTimer& timer_;
         int count_;
     };
+    class TimerCallBackAccumulator: public std::unary_function<void, void> {
+    public:
+        TimerCallBackAccumulator(IntervalTimerTest* test_obj, int &counter) :
+            test_obj_(test_obj), counter_(counter) {
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        // Reference to integer accumulator
+        int& counter_;
+    };
 protected:
     IOService io_service_;
     bool timer_called_;
@@ -285,3 +299,70 @@ TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     // Expect interval is updated: return value of getInterval() is updated
     EXPECT_EQ(itimer.getInterval(), 100);
 }
+
+// This test verifies that timers operate correctly based on their mode.
+TEST_F(IntervalTimerTest, intervalModeTest) {
+    // Create a timer to control the duration of the test.
+    IntervalTimer test_timer(io_service_);
+    test_timer.setup(TimerCallBack(this), 550);
+
+    // Create an timer which automatically reschedules itself.  Use the
+    // accumulator callback to increment local counter for it.
+    int repeater_count = 0;
+    IntervalTimer repeater(io_service_);
+    repeater.setup(TimerCallBackAccumulator(this, repeater_count), 100);
+
+    // Create a one-shot timer. Use the accumulator callback to increment
+    // local counter variable for it.
+    int one_shot_count = 0;
+    IntervalTimer one_shot(io_service_);
+    one_shot.setup(TimerCallBackAccumulator(this, one_shot_count), 100,
+                   IntervalTimer::ONE_SHOT);
+
+    // Run until the test_timer expires.
+    io_service_.run();
+
+    // Verify the repeating timer repeated and the one-shot did not.
+    EXPECT_EQ(repeater_count, 5);
+    EXPECT_EQ(one_shot_count, 1);
+}
+
+// This test verifies that the same timer can be reused in either mode.
+TEST_F(IntervalTimerTest, timerReuseTest) {
+    // Create a timer to control the duration of the test.
+    IntervalTimer test_timer(io_service_);
+    test_timer.setup(TimerCallBack(this), 550);
+
+    // Create a one-shot timer. Use the accumulator callback to increment
+    // local counter variable for it.
+    int one_shot_count = 0;
+    IntervalTimer one_shot(io_service_);
+    TimerCallBackAccumulator callback(this, one_shot_count);
+    one_shot.setup(callback, 100, IntervalTimer::ONE_SHOT);
+
+    // Run until a single event handler executes.  This should be our
+    // one-shot expiring.
+    io_service_.run_one();
+
+    // Verify the timer expired once.
+    ASSERT_EQ(one_shot_count, 1);
+
+    // Setup the one-shot to go again.
+    one_shot.setup(callback, 100, IntervalTimer::ONE_SHOT);
+
+    // Run until a single event handler executes.  This should be our
+    // one-shot expiring.
+    io_service_.run_one();
+
+    // Verify the timer expired once.
+    ASSERT_EQ(one_shot_count, 2);
+
+    // Setup the timer to be repeating.
+    one_shot.setup(callback, 100, IntervalTimer::REPEATING);
+
+    // Run until the test_timer expires.
+    io_service_.run();
+
+    // Verify the timer repeated.
+    EXPECT_GE(one_shot_count, 4);
+}

+ 78 - 9
src/lib/util/signal_set.cc

@@ -48,29 +48,87 @@ std::list<int>* getSignalStates() {
 
 /// @brief Internal signal handler for @c isc::util::io::SignalSet class.
 ///
-/// This signal handler adds a signal number for which it is being
-/// invoked to the queue of received signals. It prevents adding duplicated
-/// signals. All duplicated signals are dropped. This prevents hammering
-/// a process to invoke handlers (e.g. DHCP server reconfiguration), when
-/// many the same signals are received one after another.
+/// This handler catches all registered signals. When a signal arrives it
+/// passes the signal to invokeOnReceiptHandler for "on-receipt" processing.
+/// If this processing returns true if exists without further action,
+/// otherwise it adds the signal number to the queue of received signals.
+/// It prevents adding duplicated signals. All duplicated signals are dropped.
+/// This prevents hammering a process to invoke handlers (e.g. DHCP server
+/// reconfiguration), when many of the same signals are received one after
+/// another.
 ///
 /// @param sig Signal number.
 void internalHandler(int sig) {
+    if (SignalSet::invokeOnReceiptHandler(sig)) {
+        // Signal has been handled by the on-receipt handler.
+        return;
+    }
+
+    // Signal is using post-receipt handling, see if we've
+    // already received it.
     std::list<int>* states = getSignalStates();
     for (std::list<int>::const_iterator it = states->begin();
-         it != states->end(); ++it) {
-        if (sig == *it) {
-            return;
+        it != states->end(); ++it) {
+            if (sig == *it) {
+                return;
         }
     }
+
+    // First occurrence, so save it.
     states->push_back(sig);
 }
 
-}
+/// @brief Optional handler to execute at the time of signal receipt
+BoolSignalHandler onreceipt_handler_ = BoolSignalHandler();
+
+}; // end anon namespace
 
 namespace isc {
 namespace util {
 
+bool
+SignalSet::invokeOnReceiptHandler(int sig) {
+    if (!onreceipt_handler_) {
+        return (false);
+    }
+
+    // First we set the signal to SIG_IGN.  This causes any repeat occurrences
+    // to be discarded, not deferred as they would be if blocked.   Note that
+    // we save the current sig action so we can restore it later.
+    struct sigaction sa;
+    struct sigaction prev_sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = SIG_IGN;
+    if (sigaction(sig, &sa, &prev_sa) < 0) {
+        // Highly unlikely we can get here.
+        const char* errmsg = strerror(errno);
+        isc_throw(SignalSetError, "failed to set SIG_IGN for signal "
+                  << sig << ": " << errmsg);
+    }
+
+    // Call the registered handler.
+    bool signal_processed = false;
+    try {
+        signal_processed = onreceipt_handler_(sig);
+    } catch (const std::exception& ex) {
+        // Restore the handler.  We might fail to restore it, but we likely
+        // have bigger issues anyway.
+        sigaction(sig, &prev_sa, 0);
+        isc_throw(SignalSetError, "onreceipt_handler failed for signal "
+                  << sig << ": " << ex.what());
+    }
+
+    // Restore the sig action to reenable handling this signal.
+    if (sigaction(sig, &prev_sa, 0) < 0) {
+        // Highly unlikely we can get here.
+        const char* errmsg = strerror(errno);
+        isc_throw(SignalSetError, "failed to restore handler for signal "
+                  << sig << ": " << errmsg);
+    }
+
+    return (signal_processed);
+}
+
 SignalSet::SignalSet(const int sig0) {
     add(sig0);
 }
@@ -187,6 +245,7 @@ SignalSet::insert(const int sig) {
 void
 SignalSet::maskSignals(const int mask) const {
     sigset_t new_set;
+    sigemptyset(&new_set);
     for (std::set<int>::const_iterator it = getRegisteredSignals()->begin();
          it != getRegisteredSignals()->end(); ++it) {
         sigaddset(&new_set, *it);
@@ -225,5 +284,15 @@ SignalSet::remove(const int sig) {
     }
 }
 
+void
+SignalSet::setOnReceiptHandler(BoolSignalHandler handler) {
+    onreceipt_handler_ = handler;
+}
+
+void
+SignalSet::clearOnReceiptHandler() {
+    onreceipt_handler_ = BoolSignalHandler();
+}
+
 } // end of isc::util
 } // end of isc

+ 60 - 9
src/lib/util/signal_set.h

@@ -40,27 +40,45 @@ typedef boost::shared_ptr<SignalSet> SignalSetPtr;
 /// @brief Pointer to the signal handling function.
 typedef boost::function<void(int signum)> SignalHandler;
 
+/// @brief Pointer to a signal handling function which returns bool result.
+///
+/// The handler is expected to return true if the signal it was given has
+/// been processed (i.e. should not be recorded for deferred processing) or
+/// false in which case it will be recorded.
+typedef boost::function<bool(int signum)> BoolSignalHandler;
+
 /// @brief Represents a collection of signals handled in a customized way.
 ///
 /// Kea processes must handle selected signals in a specialized way. For
 /// example: SIGINT and SIGTERM must perform a graceful shut down of the
 /// server. The SIGHUP signal is used to trigger server's reconfiguration.
 ///
-/// This class allows specifying signals which should be handled in a
-/// specialized way as well as specifying a signal handler function.
-/// When a signal is received the signal handler function is called and
-/// the code of the received signal is recorded. This function doesn't
-/// do anything beyond recording the signal number to minimize the time
-/// spent on handling the signal and process interruption. The process
-/// can later check the signals received and call the handlers on its
-/// descretion by calling a @c isc::util::io::SignalSet::handleNext function.
+/// This class allows the caller to register one or more signals to catch
+/// and process.  Signals may be handled either immediately upon arrival and/or
+/// recorded and processed later.  To process signals immediately, the caller
+/// must register an "on-receipt" handler.  This handler is expected to return
+/// a true or false indicating whether or not the signal has been processed.
+/// Signal occurrences that are not processed by the on-receipt handler are
+/// remembered by SignalSet for deferred processing.  The caller can then query
+/// SignalSet at their discretion to determine if any signal occurrences are
+/// pending and process them.
+///
+/// SignalSet uses an internal handler to catch all registered signals. When
+/// a signal arrives the internal handler will first attempt to invoke the
+/// on-receipt handler.  If one has been registered it is passed the
+/// signal value as an argument and if it returns true upon completion, the
+/// internal handler will exit without further action.  If the on-receipt
+/// handler returned false or one is not registered, then internal handler
+/// will record the signal value for deferred processing.  Note that once a
+/// particular signal has been recorded, any further occurrences of that signal
+/// will be discarded until the original occurrence has been processed.  This
+/// guards against rapid-fire occurrences of the same signal.
 ///
 /// @note This class is not thread safe. It uses static variables and
 /// functions to track a global state of signal registration and received
 /// signals' queue.
 class SignalSet : public boost::noncopyable {
 public:
-
     /// @brief Constructor installing one signal.
     ///
     /// @param sig0 First signal.
@@ -128,6 +146,38 @@ public:
     /// @param sig A code of the signal to be removed.
     void remove(const int sig);
 
+    /// @brief Registers a handler as the onreceipt signal handler
+    ///
+    /// Sets the given handler as the handler to invoke immediately
+    /// upon receipt of a a registered signal.
+    ///
+    /// @note Currently, the on-receipt handler is stored as a static
+    /// value and hence there may only be one such handler at a time
+    /// for a given process.
+    ///
+    /// @param handler the signal handler to register
+    static void setOnReceiptHandler(BoolSignalHandler handler);
+
+    /// @brief Unregeisters the onreceipt signal handler
+    static void clearOnReceiptHandler();
+
+    /// @brief Invokes the onreceipt handler if it exists
+    ///
+    /// This static method is used by @c isc::util::io::SignalSet class to
+    /// invoke the registered handler (if one) immediately upon receipt of
+    /// a registered signal.
+    ///
+    /// Prior to invoking the handler, it sets signal action for the given
+    /// signal to SIG_IGN which prevents any repeat signal occurences from
+    /// queuing while the handler is executing.  Upon completion of the handler,
+    /// the signal action is restored which reenables receipt and handling of
+    /// the signal.
+    ///
+    /// @param sig Signal number.
+    /// @return Boolean false if no on-receipt handler was registered,
+    /// otherwise it is the value returned by the on-receipt handler.
+    static bool invokeOnReceiptHandler(int sig);
+
 private:
 
     /// @brief Blocks signals in the set.
@@ -178,6 +228,7 @@ private:
 
     /// @brief Stores the set of signals registered in this signal set.
     std::set<int> local_signals_;
+
 };
 
 }

+ 63 - 2
src/lib/util/tests/signal_set_unittest.cc

@@ -37,7 +37,8 @@ public:
     /// signal handler function.
     SignalSetTest()
         : signal_set_(),
-          secondary_signal_set_() {
+          secondary_signal_set_(),
+          onreceipt_trues_(0) {
         handler_calls_ = 0;
         signum_ = -1;
     }
@@ -54,7 +55,7 @@ public:
         }
     }
 
-    /// @brief Signal handler used by unit tests.
+    /// @brief Deferred processing signal handler used by unit tests.
     ///
     /// @param signum Signal being handled.
     static void testHandler(int signum) {
@@ -62,6 +63,22 @@ public:
         ++handler_calls_;
     }
 
+    /// @brief Immediate processing signal handler used by unit tests.
+    ///
+    /// The handler processes only SIGHUP.  All others will pass through.
+    ///
+    /// @param signum Signal being handled.
+    /// @return Boolean true if the handler has processed the signal, false
+    /// otherwise.
+    bool onReceiptHandler(int signum) {
+        if (signum == SIGHUP) {
+            ++onreceipt_trues_;
+            return (true);
+        }
+
+        return (false);
+    }
+
     /// @brief Number of handler calls so far.
     static int handler_calls_;
     /// @brief The last signal handled.
@@ -70,6 +87,8 @@ public:
     SignalSetPtr signal_set_;
     /// @brief Second signal set object.
     SignalSetPtr secondary_signal_set_;
+    /// @brief Number of true returns from onReceiptHandler so far.
+    int onreceipt_trues_;
 };
 
 int SignalSetTest::handler_calls_ = 0;
@@ -184,5 +203,47 @@ TEST_F(SignalSetTest, duplicates) {
     EXPECT_THROW(other->add(SIGHUP), SignalSetError);
 }
 
+/// Check that on-receipt processing works.
+TEST_F(SignalSetTest, onReceiptTests) {
+    // Install an on-receipt handler.
+    SignalSet::setOnReceiptHandler(boost::bind(&SignalSetTest::onReceiptHandler,
+                                               this, _1));
+    // Create a SignalSet for SIGHUP and SIGUSR1.
+    ASSERT_NO_THROW(signal_set_.reset(new SignalSet(SIGHUP, SIGUSR1)));
+
+    // Generate SIGHUP, which the on-receipt handler should process.
+    // Verify that the on-receipt handler processed the signal and that
+    // no signals are pending.
+    ASSERT_EQ(0, raise(SIGHUP));
+    EXPECT_EQ(1, onreceipt_trues_);
+    EXPECT_EQ(-1, signal_set_->getNext());
+
+    // Generate SIGHUP, which the on-receipt handler should NOT process.
+    // Verify the on-receipt handler did not the signal and that SIGUSR1
+    // is pending.
+    ASSERT_EQ(0, raise(SIGUSR1));
+    EXPECT_EQ(1, onreceipt_trues_);
+    EXPECT_EQ(SIGUSR1, signal_set_->getNext());
+
+    // Verify we can process SIGUSR1 with the deferred handler.
+    signal_set_->handleNext(boost::bind(&SignalSetTest::testHandler, _1));
+    EXPECT_EQ(1, handler_calls_);
+    EXPECT_EQ(SIGUSR1, signum_);
+
+    // Unregister the on-receipt handler.
+    SignalSet::clearOnReceiptHandler();
+
+    // Generate SIGHUP.
+    // Verify that the on-receipt handler did not process the signal, and
+    // that SIGHUP is pending.
+    ASSERT_EQ(0, raise(SIGHUP));
+    EXPECT_EQ(1, onreceipt_trues_);
+    EXPECT_EQ(SIGHUP, signal_set_->getNext());
+
+    // Verify we can process it with deferred handler.
+    signal_set_->handleNext(boost::bind(&SignalSetTest::testHandler, _1));
+    EXPECT_EQ(2, handler_calls_);
+    EXPECT_EQ(SIGHUP, signum_);
+}
 
 } // end of anonymous namespace