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_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.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 += 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 += labeled_value.cc labeled_value.h
 b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
 b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
 b10_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.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
 - isc::d2::DControllerBase - provides all of the services necessary to manage
 an application process class derived from isc::d2::DProcess. These services include:
 an application process class derived from isc::d2::DProcess. These services include:
     - Command line argument handling
     - Command line argument handling
-    - Process instantiation and initialization0
+    - Process instantiation and initialization
     - Support for stand-alone execution
     - Support for stand-alone execution
     - Support for integrated operation as a BUNDY module (session management
     - Support for integrated operation as a BUNDY module (session management
       and event handling)
       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.
     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
     @todo Eventually, some sort of secure socket interface which supports remote
     control operations such as configuration changes or status reporting will
     control operations such as configuration changes or status reporting will
     likely be implemented.
     likely be implemented.
@@ -118,6 +114,90 @@ through the CPL layer:
 
 
 The CPL classes will likely move into a common library.
 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
 @section d2ProcesDerivation D2's CPL Derivations
 
 
 D2's core application classes are DDNS-specific derivations of the CPL as show
 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"
 @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
 configuration is committed by the administrator.  Additional information
 may be provided.
 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
 % DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
 This critical error message indicates that the initial application
 This critical error message indicates that the initial application
 configuration has failed. The service will start, but will not
 configuration has failed. The service will start, but will not
 process requests until the configuration has been corrected.
 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
 % DCTL_CONFIG_START parsing new configuration: %1
 A debug message indicating that the application process has received an
 A debug message indicating that the application process has received an
 updated configuration and has passed it to its configuration manager
 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
 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.
 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
 % 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
 This is an informational message issued when the application has been instructed
 to shutdown and has met the required criteria to exit.
 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
 This is informational message issued when the application has been instructed
 to shut down by the controller.
 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
 % DHCP_DDNS_STARTING_TRANSACTION Transaction Key: %1
 This is a debug message issued when DHCP-DDNS has begun a transaction for
 This is a debug message issued when DHCP-DDNS has begun a transaction for
 a given request.
 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
 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.
 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
 % 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
 This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
 server.
 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)
 DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
     : app_name_(app_name), bin_name_(bin_name),
     : app_name_(app_name), bin_name_(bin_name),
       verbose_(false), spec_file_name_(""),
       verbose_(false), spec_file_name_(""),
-      io_service_(new isc::asiolink::IOService()){
+      io_service_(new isc::asiolink::IOService()),
+      signal_set_(), io_signal_queue_() {
 }
 }
 
 
 void
 void
@@ -75,6 +76,7 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
                    "Application Process initialization failed: " << ex.what());
                    "Application Process initialization failed: " << ex.what());
     }
     }
 
 
+
     LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);
     LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE).arg(app_name_);
 
 
     // Step 3 is to load configuration from file.
     // 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
     // Everything is clear for launch, so start the application's
     // event loop.
     // event loop.
     try {
     try {
+        // Now that we have a proces, we can set up signal handling.
+        initSignalHandling();
+
         runProcess();
         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) {
     } catch (const std::exception& ex) {
         LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
         LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
                   .arg(app_name_).arg(ex.what());
                   .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,
         isc_throw (ProcessRunError,
                    "Application process event loop failed: " << ex.what());
                    "Application process event loop failed: " << ex.what());
     }
     }
@@ -288,6 +304,79 @@ DControllerBase::shutdownProcess(isc::data::ConstElementPtr args) {
 }
 }
 
 
 void
 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)
 DControllerBase::usage(const std::string & text)
 {
 {
     if (text != "") {
     if (text != "") {

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

@@ -19,9 +19,11 @@
 #include <d2/d2_asio.h>
 #include <d2/d2_asio.h>
 #include <d2/d2_log.h>
 #include <d2/d2_log.h>
 #include <d2/d_process.h>
 #include <d2/d_process.h>
+#include <d2/io_service_signal.h>
 #include <dhcpsrv/daemon.h>
 #include <dhcpsrv/daemon.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
+#include <util/signal_set.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
@@ -78,9 +80,12 @@ typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
 /// creation.
 /// creation.
 /// It provides the callback handlers for command and configuration events
 /// It provides the callback handlers for command and configuration events
 /// which could be triggered by an external source.  Such sources are intended
 /// 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.
 /// 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)
 /// NOTE: Derivations must supply their own static singleton instance method(s)
 /// for creating and fetching the instance. The base class declares the instance
 /// for creating and fetching the instance. The base class declares the instance
 /// member in order for it to be available for static callback functions.
 /// member in order for it to be available for static callback functions.
@@ -102,8 +107,9 @@ public:
     /// 1. parse command line arguments
     /// 1. parse command line arguments
     /// 2. instantiate and initialize the application process
     /// 2. instantiate and initialize the application process
     /// 3. load the configuration file
     /// 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
     /// It is intended to be called from main() and be given the command line
     /// arguments.
     /// arguments.
@@ -155,7 +161,7 @@ public:
     ///                   configuration data for this controller's application
     ///                   configuration data for this controller's application
     ///
     ///
     ///     module-config: a set of zero or more JSON elements which comprise
     ///     module-config: a set of zero or more JSON elements which comprise
-    ///                    the application'ss configuration values
+    ///                    the application's configuration values
     /// @endcode
     /// @endcode
     ///
     ///
     /// The method extracts the set of configuration elements for the
     /// The method extracts the set of configuration elements for the
@@ -279,6 +285,24 @@ protected:
         return ("");
         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.
     /// @brief Supplies whether or not verbose logging is enabled.
     ///
     ///
     /// @return returns true if verbose logging is enabled.
     /// @return returns true if verbose logging is enabled.
@@ -372,7 +396,7 @@ protected:
     /// to begin its shutdown process.
     /// to begin its shutdown process.
     ///
     ///
     /// Note, it is assumed that the process of shutting down is neither
     /// 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
     /// until the process has halted.  Rather it is used to convey the
     /// need to shutdown.  A successful return indicates that the shutdown
     /// need to shutdown.  A successful return indicates that the shutdown
     /// has successfully commenced, but does not indicate that the process
     /// 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
     /// @return returns an Element that contains the results of shutdown
     /// command composed of an integer status value (0 means successful,
     /// command composed of an integer status value (0 means successful,
     /// non-zero means failure), and a string explanation of the outcome.
     /// 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);
     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
     /// @brief Fetches the current process
     ///
     ///
-    /// @return the a pointer to the current process instance.
+    /// @return a pointer to the current process instance.
     DProcessBasePtr getProcess() {
     DProcessBasePtr getProcess() {
         return (process_);
         return (process_);
     }
     }
@@ -403,7 +464,7 @@ private:
     std::string app_name_;
     std::string app_name_;
 
 
     /// @brief Name of the service executable.
     /// @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.
     /// establish the logger name.
     std::string bin_name_;
     std::string bin_name_;
 
 
@@ -422,6 +483,12 @@ private:
     /// @brief Shared pointer to an IOService object, used for ASIO operations.
     /// @brief Shared pointer to an IOService object, used for ASIO operations.
     IOServicePtr io_service_;
     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.
     /// @brief Singleton instance value.
     static DControllerBasePtr controller_;
     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@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 
 
 PYTESTS = d2_test.py
 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 = $(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
 # Explicitly specify paths to dynamic libraries required by loadable python
 # modules. That is required on Mac OS systems. Otherwise we will get exception
 # 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 ; \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	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_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_builddir)/src/bin # for generated spec_config.h header
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 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_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.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 += ../labeled_value.cc ../labeled_value.h
 d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
 d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
 d2_unittests_SOURCES += ../nc_remove.cc ../nc_remove.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_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_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 += labeled_value_unittests.cc
 d2_unittests_SOURCES += nc_add_unittests.cc
 d2_unittests_SOURCES += nc_add_unittests.cc
 d2_unittests_SOURCES += nc_remove_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 <config/ccsession.h>
 #include <d_test_stubs.h>
 #include <d_test_stubs.h>
 #include <d2/d2_controller.h>
 #include <d2/d2_controller.h>
+#include <d2/d2_process.h>
 #include <d2/spec_config.h>
 #include <d2/spec_config.h>
 
 
 #include <boost/pointer_cast.hpp>
 #include <boost/pointer_cast.hpp>
@@ -51,6 +52,40 @@ public:
     /// @brief Destructor
     /// @brief Destructor
     ~D2ControllerTest() {
     ~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.
 /// @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
 /// This creates an interval timer to generate a normal shutdown and then
 /// launches with a valid, stand-alone command line and no simulated errors.
 /// launches with a valid, stand-alone command line and no simulated errors.
 TEST_F(D2ControllerTest, launchNormalShutdown) {
 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.
 /// @brief Configuration update event testing.
@@ -211,7 +225,107 @@ TEST_F(D2ControllerTest, executeCommandTests) {
     answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
     answer = executeCommand(SHUT_DOWN_COMMAND, arg_set);
     isc::config::parseAnswer(rcode, answer);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(COMMAND_SUCCESS, rcode);
     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
 }; // 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.
 /// has been constructed to exercise DControllerBase.
 class DStubControllerTest : public DControllerTest {
 class DStubControllerTest : public DControllerTest {
 public:
 public:
-
     /// @brief Constructor.
     /// @brief Constructor.
     /// Note the constructor passes in the static DStubController instance
     /// Note the constructor passes in the static DStubController instance
     /// method.
     /// method.
     DStubControllerTest() : DControllerTest (DStubController::instance) {
     DStubControllerTest() : DControllerTest (DStubController::instance) {
+        controller_ = boost::dynamic_pointer_cast<DStubController>
+                                                 (DControllerTest::
+                                                  getController());
     }
     }
 
 
     virtual ~DStubControllerTest() {
     virtual ~DStubControllerTest() {
     }
     }
+
+    DStubControllerPtr controller_;
 };
 };
 
 
 /// @brief Basic Controller instantiation testing.
 /// @brief Basic Controller instantiation testing.
@@ -183,35 +187,15 @@ TEST_F(DStubControllerTest, launchProcessInitError) {
 /// launches with a valid, command line, with a valid configuration file
 /// launches with a valid, command line, with a valid configuration file
 ///  and no simulated errors.
 ///  and no simulated errors.
 TEST_F(DStubControllerTest, launchNormalShutdown) {
 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
     // Verify that duration of the run invocation is the same as the
     // timer duration.  This demonstrates that the shutdown was driven
     // timer duration.  This demonstrates that the shutdown was driven
     // by an io_service event and callback.
     // 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.
 /// @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
 /// the process event loop. It launches wih a valid, stand-alone command line
 /// and no simulated errors.  Launch should throw ProcessRunError.
 /// and no simulated errors.  Launch should throw ProcessRunError.
 TEST_F(DStubControllerTest, launchRuntimeError) {
 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
     // Use an asiolink IntervalTimer and callback to generate the
     // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
     // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
     isc::asiolink::IntervalTimer timer(*getIOService());
     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
     // Verify that duration of the run invocation is the same as the
     // timer duration.  This demonstrates that the shutdown was driven
     // timer duration.  This demonstrates that the shutdown was driven
     // by an io_service event and callback.
     // 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.
 /// @brief Configuration update event testing.
@@ -380,5 +349,122 @@ TEST_F(DStubControllerTest, executeCommandTests) {
     EXPECT_EQ(COMMAND_ERROR, rcode);
     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::d2 namespace
 }; // end of isc 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
 isc::data::ConstElementPtr
-DStubProcess::configure(isc::data::ConstElementPtr /*config_set*/) {
+DStubProcess::configure(isc::data::ConstElementPtr config_set) {
     if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
     if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
         // Simulates a process configure failure.
         // Simulates a process configure failure.
         return (isc::config::createAnswer(1,
         return (isc::config::createAnswer(1,
                 "Simulated process configuration error."));
                 "Simulated process configuration error."));
     }
     }
 
 
-    return (isc::config::createAnswer(0, "Configuration accepted."));
+    return (getCfgMgr()->parseConfig(config_set));
 }
 }
 
 
 isc::data::ConstElementPtr
 isc::data::ConstElementPtr
@@ -154,7 +154,8 @@ DStubController::instance() {
 }
 }
 
 
 DStubController::DStubController()
 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")) {
     if (getenv("B10_FROM_BUILD")) {
         setSpecFileName(std::string(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_));
     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() {
 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.
 // Initialize controller wrapper's static instance getter member.
 DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
 DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
 
 
@@ -290,7 +390,7 @@ DStubCfgMgr::DStubCfgMgr()
 DStubCfgMgr::~DStubCfgMgr() {
 DStubCfgMgr::~DStubCfgMgr() {
 }
 }
 
 
-DCfgContextBasePtr 
+DCfgContextBasePtr
 DStubCfgMgr::createNewContext() {
 DStubCfgMgr::createNewContext() {
     return (DCfgContextBasePtr (new DStubContext()));
     return (DCfgContextBasePtr (new DStubContext()));
 }
 }
@@ -300,7 +400,6 @@ DStubCfgMgr::createConfigParser(const std::string& element_id) {
     isc::dhcp::ParserPtr parser;
     isc::dhcp::ParserPtr parser;
     DStubContextPtr context
     DStubContextPtr context
         = boost::dynamic_pointer_cast<DStubContext>(getContext());
         = boost::dynamic_pointer_cast<DStubContext>(getContext());
-
     if (element_id == "bool_test") {
     if (element_id == "bool_test") {
         parser.reset(new isc::dhcp::
         parser.reset(new isc::dhcp::
                          BooleanParser(element_id,
                          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_controller.h>
 #include <d2/d_cfg_mgr.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 <gtest/gtest.h>
 
 
 #include <fstream>
 #include <fstream>
@@ -213,6 +217,25 @@ public:
     /// @brief Defines the executable name used to construct the controller
     /// @brief Defines the executable name used to construct the controller
     static const char* stub_bin_name_;
     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:
 protected:
     /// @brief Handles additional command line options that are supported
     /// @brief Handles additional command line options that are supported
     /// by DStubController.  This implementation supports an option "-x".
     /// by DStubController.  This implementation supports an option "-x".
@@ -259,14 +282,33 @@ protected:
     /// @return returns a string containing the option letters.
     /// @return returns a string containing the option letters.
     virtual const std::string getCustomOpts() const;
     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:
 private:
     /// @brief Constructor is private to protect singleton integrity.
     /// @brief Constructor is private to protect singleton integrity.
     DStubController();
     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:
 public:
     virtual ~DStubController();
     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
 /// @brief Abstract Test fixture class that wraps a DControllerBase. This class
 /// is a friend class of DControllerBase which allows it access to class
 /// is a friend class of DControllerBase which allows it access to class
 /// content to facilitate testing.  It provides numerous wrapper methods for
 /// 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
     /// @param instance_getter is a function pointer to the static instance
     /// method of the DControllerBase derivation under test.
     /// 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.
         // Set the static fetcher member, then invoke it via getController.
         // This ensures the singleton is instantiated.
         // This ensures the singleton is instantiated.
         instanceGetter_ = instance_getter;
         instanceGetter_ = instance_getter;
@@ -296,6 +339,10 @@ public:
     /// Note the controller singleton is destroyed. This is essential to ensure
     /// Note the controller singleton is destroyed. This is essential to ensure
     /// a clean start between tests.
     /// a clean start between tests.
     virtual ~DControllerTest() {
     virtual ~DControllerTest() {
+        if (write_timer_) {
+            write_timer_->cancel();
+        }
+
         getController().reset();
         getController().reset();
         static_cast<void>(unlink(CFG_TEST_FILE));
         static_cast<void>(unlink(CFG_TEST_FILE));
     }
     }
@@ -447,20 +494,71 @@ public:
     /// @param content JSON text to be written to file
     /// @param content JSON text to be written to file
     /// @param module_name  content content to be written to file
     /// @param module_name  content content to be written to file
     void writeFile(const std::string& content,
     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;
     static const char* CFG_TEST_FILE;
 };
 };
 
 
@@ -683,6 +781,68 @@ public:
     isc::data::ConstElementPtr answer_;
     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
 /// @brief Defines a small but valid DHCP-DDNS compliant configuration for
 /// testing configuration parsing fundamentals.
 /// testing configuration parsing fundamentals.
 extern const char* valid_d2_config;
 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -44,7 +44,9 @@ private:
 public:
 public:
     IntervalTimerImpl(IOService& io_service);
     IntervalTimerImpl(IOService& io_service);
     ~IntervalTimerImpl();
     ~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 callback(const asio::error_code& error);
     void cancel() {
     void cancel() {
         timer_.cancel();
         timer_.cancel();
@@ -60,13 +62,18 @@ private:
     long interval_;
     long interval_;
     // asio timer
     // asio timer
     asio::deadline_timer 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
     // interval_ will be set to this value in destructor in order to detect
     // use-after-free type of bugs.
     // use-after-free type of bugs.
     static const long INVALIDATED_INTERVAL = -1;
     static const long INVALIDATED_INTERVAL = -1;
 };
 };
 
 
 IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
 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() {
 IntervalTimerImpl::~IntervalTimerImpl() {
@@ -75,7 +82,8 @@ IntervalTimerImpl::~IntervalTimerImpl() {
 
 
 void
 void
 IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
 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.
     // Interval should not be less than or equal to 0.
     if (interval <= 0) {
     if (interval <= 0) {
@@ -88,6 +96,8 @@ IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
     }
     }
     cbfunc_ = cbfunc;
     cbfunc_ = cbfunc;
     interval_ = interval;
     interval_ = interval;
+    mode_ = mode;
+
     // Set initial expire time.
     // Set initial expire time.
     // At this point the timer is not running yet and will not expire.
     // At this point the timer is not running yet and will not expire.
     // After calling IOService::run(), the timer will expire.
     // After calling IOService::run(), the timer will expire.
@@ -118,8 +128,11 @@ IntervalTimerImpl::callback(const asio::error_code& ec) {
     if (interval_ == 0 || ec) {
     if (interval_ == 0 || ec) {
         // timer has been canceled. Do nothing.
         // timer has been canceled. Do nothing.
     } else {
     } else {
-        // Set next expire time.
-        update();
+        // If we should repeat, set next expire time.
+        if (mode_ == IntervalTimer::REPEATING) {
+            update();
+        }
+
         // Invoke the call back function.
         // Invoke the call back function.
         cbfunc_();
         cbfunc_();
     }
     }
@@ -135,8 +148,9 @@ IntervalTimer::~IntervalTimer() {
 }
 }
 
 
 void
 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
 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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
 /// This class is implemented to use \c asio::deadline_timer as interval
 /// timer.
 /// 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.
 /// \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 type of call back function is \c void(void).
 ///
 ///
 /// The call back function will not be called if the instance of this class is
 /// 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
     /// \name The type of timer callback function
     typedef boost::function<void()> Callback;
     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
     /// \name Constructors and Destructor
     ///
     ///
@@ -96,6 +107,9 @@ public:
     /// \param cbfunc A reference to a function \c void(void) to call back
     /// \param cbfunc A reference to a function \c void(void) to call back
     /// when the timer is expired (should not be an empty functor)
     /// when the timer is expired (should not be an empty functor)
     /// \param interval Interval in milliseconds (greater than 0)
     /// \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
     /// Note: IntervalTimer will not pass \c asio::error_code to
     /// call back function. In case the timer is canceled, the function
     /// call back function. In case the timer is canceled, the function
@@ -104,7 +118,8 @@ public:
     /// \throw isc::InvalidParameter cbfunc is empty
     /// \throw isc::InvalidParameter cbfunc is empty
     /// \throw isc::BadValue interval is less than or equal to 0
     /// \throw isc::BadValue interval is less than or equal to 0
     /// \throw isc::Unexpected internal runtime error
     /// \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.
     /// Cancel the timer.
     ///
     ///
@@ -128,6 +143,8 @@ private:
     boost::shared_ptr<IntervalTimerImpl> impl_;
     boost::shared_ptr<IntervalTimerImpl> impl_;
 };
 };
 
 
+typedef boost::shared_ptr<isc::asiolink::IntervalTimer> IntervalTimerPtr;
+
 } // namespace asiolink
 } // namespace asiolink
 } // namespace isc
 } // namespace isc
 #endif // ASIOLINK_INTERVAL_TIMER_H
 #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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -137,6 +137,20 @@ protected:
         IntervalTimer& timer_;
         IntervalTimer& timer_;
         int count_;
         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:
 protected:
     IOService io_service_;
     IOService io_service_;
     bool timer_called_;
     bool timer_called_;
@@ -285,3 +299,70 @@ TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     // Expect interval is updated: return value of getInterval() is updated
     // Expect interval is updated: return value of getInterval() is updated
     EXPECT_EQ(itimer.getInterval(), 100);
     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.
 /// @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.
 /// @param sig Signal number.
 void internalHandler(int sig) {
 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();
     std::list<int>* states = getSignalStates();
     for (std::list<int>::const_iterator it = states->begin();
     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);
     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 isc {
 namespace util {
 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) {
 SignalSet::SignalSet(const int sig0) {
     add(sig0);
     add(sig0);
 }
 }
@@ -187,6 +245,7 @@ SignalSet::insert(const int sig) {
 void
 void
 SignalSet::maskSignals(const int mask) const {
 SignalSet::maskSignals(const int mask) const {
     sigset_t new_set;
     sigset_t new_set;
+    sigemptyset(&new_set);
     for (std::set<int>::const_iterator it = getRegisteredSignals()->begin();
     for (std::set<int>::const_iterator it = getRegisteredSignals()->begin();
          it != getRegisteredSignals()->end(); ++it) {
          it != getRegisteredSignals()->end(); ++it) {
         sigaddset(&new_set, *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::util
 } // end of isc
 } // 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.
 /// @brief Pointer to the signal handling function.
 typedef boost::function<void(int signum)> SignalHandler;
 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.
 /// @brief Represents a collection of signals handled in a customized way.
 ///
 ///
 /// Kea processes must handle selected signals in a specialized way. For
 /// Kea processes must handle selected signals in a specialized way. For
 /// example: SIGINT and SIGTERM must perform a graceful shut down of the
 /// example: SIGINT and SIGTERM must perform a graceful shut down of the
 /// server. The SIGHUP signal is used to trigger server's reconfiguration.
 /// 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
 /// @note This class is not thread safe. It uses static variables and
 /// functions to track a global state of signal registration and received
 /// functions to track a global state of signal registration and received
 /// signals' queue.
 /// signals' queue.
 class SignalSet : public boost::noncopyable {
 class SignalSet : public boost::noncopyable {
 public:
 public:
-
     /// @brief Constructor installing one signal.
     /// @brief Constructor installing one signal.
     ///
     ///
     /// @param sig0 First signal.
     /// @param sig0 First signal.
@@ -128,6 +146,38 @@ public:
     /// @param sig A code of the signal to be removed.
     /// @param sig A code of the signal to be removed.
     void remove(const int sig);
     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:
 private:
 
 
     /// @brief Blocks signals in the set.
     /// @brief Blocks signals in the set.
@@ -178,6 +228,7 @@ private:
 
 
     /// @brief Stores the set of signals registered in this signal set.
     /// @brief Stores the set of signals registered in this signal set.
     std::set<int> local_signals_;
     std::set<int> local_signals_;
+
 };
 };
 
 
 }
 }

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

@@ -37,7 +37,8 @@ public:
     /// signal handler function.
     /// signal handler function.
     SignalSetTest()
     SignalSetTest()
         : signal_set_(),
         : signal_set_(),
-          secondary_signal_set_() {
+          secondary_signal_set_(),
+          onreceipt_trues_(0) {
         handler_calls_ = 0;
         handler_calls_ = 0;
         signum_ = -1;
         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.
     /// @param signum Signal being handled.
     static void testHandler(int signum) {
     static void testHandler(int signum) {
@@ -62,6 +63,22 @@ public:
         ++handler_calls_;
         ++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.
     /// @brief Number of handler calls so far.
     static int handler_calls_;
     static int handler_calls_;
     /// @brief The last signal handled.
     /// @brief The last signal handled.
@@ -70,6 +87,8 @@ public:
     SignalSetPtr signal_set_;
     SignalSetPtr signal_set_;
     /// @brief Second signal set object.
     /// @brief Second signal set object.
     SignalSetPtr secondary_signal_set_;
     SignalSetPtr secondary_signal_set_;
+    /// @brief Number of true returns from onReceiptHandler so far.
+    int onreceipt_trues_;
 };
 };
 
 
 int SignalSetTest::handler_calls_ = 0;
 int SignalSetTest::handler_calls_ = 0;
@@ -184,5 +203,47 @@ TEST_F(SignalSetTest, duplicates) {
     EXPECT_THROW(other->add(SIGHUP), SignalSetError);
     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
 } // end of anonymous namespace