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
% 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.
--- /dev/null
+// 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
--- /dev/null
+// 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
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
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
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;
--- /dev/null
+// 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