Parcourir la source

[3407] Added IOSignal class to send signals to IOService

Created d2::IOSignal and d2::IOSignalQueue for propagating
caught OS signals to IOService instances.
Added TimedSignal test class.

New files:
    src/bin/d2/io_service_signal.cc
    src/bin/d2/io_service_signal.h
    src/bin/d2/tests/io_service_signal_unittests.cc
Thomas Markwalder il y a 11 ans
Parent
commit
231f440a0b

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

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

+ 5 - 0
src/bin/d2/d2_messages.mes

@@ -475,3 +475,8 @@ server.
 % DHCP_DDNS_UPDATE_RESPONSE_RECEIVED for transaction key: %1  to server: %2 status: %3
 This is a debug message issued when DHCP_DDNS receives sends a DNS update
 response from a DNS server.
+
+% DHCP_DDNS_SIGNAL_ERROR The 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.

+ 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 siganl so we can return it.
+    IOSignalPtr signal = ((*it).second);
+
+    // Delete it fromt 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 <util/signal_set.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);
+
+    ~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);
+
+    /// 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

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

@@ -64,6 +64,7 @@ d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../io_service_signal.cc ../io_service_signal.h
 d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
 d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
 d2_unittests_SOURCES += ../nc_remove.cc ../nc_remove.h
@@ -79,6 +80,7 @@ d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
+d2_unittests_SOURCES += io_service_signal_unittests.cc
 d2_unittests_SOURCES += labeled_value_unittests.cc
 d2_unittests_SOURCES += nc_add_unittests.cc
 d2_unittests_SOURCES += nc_remove_unittests.cc

+ 62 - 0
src/bin/d2/tests/d_test_stubs.h

@@ -683,6 +683,68 @@ public:
     isc::data::ConstElementPtr answer_;
 };
 
+/// @brief Implements a time-delayed signal
+///
+/// Given an IOService, a signal number, and a time period, this class will
+/// send (raise) the signal to the current process.
+class TimedSignal {
+public:
+    /// @brief Constructor
+    ///
+    /// @param io_service  IOService to run the timer
+    /// @param signum OS signal value (e.g. SIGHUP, SIGUSR1 ...)
+    /// @param milliseconds time in milliseconds to wait until the signal is
+    /// raised.
+    /// @param mode selects between a one-shot signal or a signal which repeats
+    /// at "milliseconds" interval.
+    TimedSignal(asiolink::IOService& io_service, int signum, int milliseconds,
+                const asiolink::IntervalTimer::Mode& mode =
+                asiolink::IntervalTimer::ONE_SHOT)
+        : timer_(new asiolink::IntervalTimer(io_service)) {
+        timer_->setup(SendSignalCallback(signum), milliseconds, mode);
+    }
+
+    /// @brief Cancels the given timer.
+    void cancel() {
+        if (timer_) {
+            timer_->cancel();
+        }
+    }
+
+    /// @brief Destructor.
+    ~TimedSignal() {
+        cancel();
+    }
+
+    /// @brief Callback for the TimeSignal's internal timer.
+    class SendSignalCallback: public std::unary_function<void, void> {
+    public:
+
+        /// @brief Constructor
+        ///
+        /// @param signum OS signal value of the signal to send
+        SendSignalCallback(int signum) : signum_(signum) {
+        }
+
+        /// @brief Callback method invoked when the timer expires
+        ///
+        /// Calls raise with the given signal which should generate that
+        /// signal to the given process.
+        void operator()() {
+            ASSERT_EQ(0, raise(signum_));
+            return;
+        }
+
+    private:
+        /// @brief Stores the OS signal value to send.
+        int signum_;
+    };
+
+private:
+    /// @brief Timer which controls when the signal is sent.
+    asiolink::IntervalTimerPtr timer_;
+};
+
 /// @brief Defines a small but valid DHCP-DDNS compliant configuration for
 /// testing configuration parsing fundamentals.
 extern const char* valid_d2_config;

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

@@ -0,0 +1,334 @@
+// 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;
+
+    // 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 2, we burned 1 with the failed constructor.
+    EXPECT_EQ(2, signal->getSequenceId());
+
+    // Verify SIGINT is correct.
+    EXPECT_EQ(SIGINT, signal->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 we can queue up a valid entry.
+    IOSignalId sequence_id = queue->pushSignal(SIGINT, dummyHandler);
+
+    // Verify we can pop the entry.
+    IOSignalPtr signal = queue->popSignal(sequence_id);
+    ASSERT_TRUE(signal);
+
+    // Verify the one we popped is right.
+    EXPECT_EQ(sequence_id, signal->getSequenceId());
+    EXPECT_EQ(SIGINT, signal->getSignum());
+
+    // Verify popping it again, throws.
+    ASSERT_THROW(queue->popSignal(sequence_id), 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