From: Thomas Markwalder Date: Mon, 16 Jun 2014 12:54:59 +0000 (-0400) Subject: [3407] Added IOSignal class to send signals to IOService X-Git-Tag: trac3434_base~4^2~10 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=231f440a0b22dcca364ee54fbba513ba6bf6162b;p=thirdparty%2Fkea.git [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 --- diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am index ec03164119..27d1a32021 100644 --- a/src/bin/d2/Makefile.am +++ b/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 diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes index cc99e18d70..1a0a9545a6 100644 --- a/src/bin/d2/d2_messages.mes +++ b/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. diff --git a/src/bin/d2/io_service_signal.cc b/src/bin/d2/io_service_signal.cc new file mode 100644 index 0000000000..9c097f6328 --- /dev/null +++ b/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 +#include +#include + +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 diff --git a/src/bin/d2/io_service_signal.h b/src/bin/d2/io_service_signal.h new file mode 100644 index 0000000000..8f91f2b2ce --- /dev/null +++ b/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 +#include +//#include + +#include + +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 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 { + 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 IOSignalPtr; + +/// @brief Defines a map of IOSignalPtr keyed by id +typedef std::map 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 IOSignalQueuePtr; + + +}; // end of isc::d2 namespace +}; // end of isc namespace + +#endif // IO_SERVICE_SIGNAL_H diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am index 9e587565db..d5a68becfb 100644 --- a/src/bin/d2/tests/Makefile.am +++ b/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 diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h index bfd6673e74..cd4249d81d 100644 --- a/src/bin/d2/tests/d_test_stubs.h +++ b/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 { + 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; diff --git a/src/bin/d2/tests/io_service_signal_unittests.cc b/src/bin/d2/tests/io_service_signal_unittests.cc new file mode 100644 index 0000000000..6f0b9c7f50 --- /dev/null +++ b/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 +#include + +#include + +#include + +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 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