From: Francis Dupont Date: Sun, 29 Mar 2020 15:06:55 +0000 (+0200) Subject: [#1096] Switched to boost asio signal set X-Git-Tag: Kea-1.7.7~36 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=59d19f5c01c217ee449c489805bfcae6156ee8af;p=thirdparty%2Fkea.git [#1096] Switched to boost asio signal set --- diff --git a/src/lib/process/d_controller.cc b/src/lib/process/d_controller.cc index d6c97e3eb0..f971eb0119 100644 --- a/src/lib/process/d_controller.cc +++ b/src/lib/process/d_controller.cc @@ -32,7 +32,7 @@ DControllerBase::DControllerBase(const char* app_name, const char* bin_name) : app_name_(app_name), bin_name_(bin_name), verbose_(false), check_only_(false), io_service_(new isc::asiolink::IOService()), - io_signal_queue_() { + io_signal_set_() { } void @@ -723,37 +723,15 @@ void DControllerBase::initSignalHandling() { /// @todo block everything we don't handle - // Create our signal queue. - io_signal_queue_.reset(new IOSignalQueue(io_service_)); - - // Install the on-receipt handler - util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase:: - osSignalHandler, - this, _1)); + // Create our signal set. + io_signal_set_.reset(new IOSignalSet(io_service_, + boost::bind(&DControllerBase:: + processSignal, + this, _1))); // Register for the signals we wish to handle. - signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM)); -} - -bool -DControllerBase::osSignalHandler(int signum) { - // Create a IOSignal to propagate the signal to IOService. - io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase:: - ioSignalHandler, - this, _1)); - return (true); -} - -void -DControllerBase::ioSignalHandler(IOSignalId sequence_id) { - // Pop the signal instance off the queue. This should make us - // the only one holding it, so when we leave it should be freed. - // (note that popSignal will throw if signal is not found, which - // in turn will caught, logged, and swallowed by IOSignal callback - // invocation code.) - IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id); - - // Now call virtual signal processing method. - processSignal(io_signal->getSignum()); + io_signal_set_->add(SIGHUP); + io_signal_set_->add(SIGINT); + io_signal_set_->add(SIGTERM); } void diff --git a/src/lib/process/d_controller.h b/src/lib/process/d_controller.h index e6c6354205..16db298b5d 100644 --- a/src/lib/process/d_controller.h +++ b/src/lib/process/d_controller.h @@ -407,9 +407,8 @@ protected: /// @brief Application-level signal processing method. /// - /// This method is the last step in processing a OS signal occurrence. It - /// is invoked when an IOSignal's internal timer callback is executed by - /// IOService. It currently supports the following signals as follows: + /// This method is the last step in processing a OS signal occurrence. + /// It currently supports the following signals as follows: /// -# SIGHUP - instigates reloading the configuration file /// -# SIGINT - instigates a graceful shutdown /// -# SIGTERM - instigates a graceful shutdown @@ -580,32 +579,6 @@ protected: /// which listens for SIGHUP, SIGINT, and SIGTERM. void initSignalHandling(); - /// @brief Handler for processing OS-level signals - /// - /// This method is installed as the SignalSet "on-receipt" handler. Upon - /// invocation, it uses the controller's IOSignalQueue to schedule an - /// IOSignal with for the given signal value. - /// - /// @param signum OS signal value (e.g. SIGINT, SIGUSR1 ...) to received - /// - /// @return SignalSet "on-receipt" handlers are required to return a - /// boolean indicating if the OS signal has been processed (true) or if it - /// should be saved for deferred processing (false). Currently this - /// method processes all received signals, so it always returns true. - bool osSignalHandler(int signum); - - /// @brief Handler for processing IOSignals - /// - /// This method is supplied as the callback when IOSignals are scheduled. - /// It fetches the IOSignal for the given sequence_id and then invokes - /// the virtual method, @c processSignal() passing it the signal value - /// obtained from the IOSignal. This allows derivations to supply a - /// custom signal processing method, while ensuring IOSignalQueue - /// integrity. - /// - /// @param sequence_id id of the IOSignal instance "received" - void ioSignalHandler(IOSignalId sequence_id); - /// @brief Fetches the current process /// /// @return a pointer to the current process instance. @@ -664,8 +637,8 @@ private: /// @brief Shared pointer to an IOService object, used for ASIO operations. asiolink::IOServicePtr io_service_; - /// @brief Queue for propagating caught signals to the IOService. - IOSignalQueuePtr io_signal_queue_; + /// @brief ASIO signal set. + IOSignalSetPtr io_signal_set_; /// @brief Singleton instance value. static DControllerBasePtr controller_; diff --git a/src/lib/process/io_service_signal.cc b/src/lib/process/io_service_signal.cc index 70c2e3e70f..5df59797a9 100644 --- a/src/lib/process/io_service_signal.cc +++ b/src/lib/process/io_service_signal.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -6,112 +6,91 @@ #include -#include #include #include +#include + +#include +#include +#include + +using namespace isc::asiolink; namespace isc { namespace process { -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); +/// Implementation class of IOSignalSet. +class IOSignalSetImpl : + public boost::enable_shared_from_this +{ +private: + // Prohibit copy. + IOSignalSetImpl(const IOSignalSetImpl& source); + IOSignalSetImpl& operator=(const IOSignalSetImpl& source); +public: + IOSignalSetImpl(IOServicePtr io_service, IOSignalHandler handler); + ~IOSignalSetImpl(){} + void install(); + void add(int signum); + private: + boost::asio::signal_set signal_set_; + IOSignalHandler handler_; + void callback(const boost::system::error_code& ec, int signum); +}; + +IOSignalSetImpl::IOSignalSetImpl(IOServicePtr io_service, + IOSignalHandler handler) + : signal_set_(io_service->get_io_service()), handler_(handler) { } -IOSignal::~IOSignal() { - if (timer_) { - // In the unlikely event that the timer hasn't expired cancel it. - timer_->cancel(); +void +IOSignalSetImpl::callback(const boost::system::error_code& ec, int signum) { + if (ec && ec.value() == boost::asio::error::operation_aborted) { + return; } -} - -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"); + install(); + if (!ec && (signum > 0)) { + try { + handler_(signum); + } catch (const std::exception& ex) { + // We log it and swallow it so we don't undermine IOService::run. + LOG_ERROR(dctl_logger, DCTL_SIGNAL_ERROR) + .arg(signum) + .arg(ex.what()); + } } } 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, DCTL_SIGNAL_ERROR) - .arg(sequence_id_).arg(ex.what()); - } - - return; +IOSignalSetImpl::install() { + signal_set_.async_wait(boost::bind(&IOSignalSetImpl::callback, + shared_from_this(), _1, _2)); } -IOSignalQueue::IOSignalQueue(asiolink::IOServicePtr& io_service) - : io_service_(io_service), signals_() { - if (!io_service_) { - isc_throw(IOSignalError, "IOSignalQueue - io_service cannot be NULL"); +void +IOSignalSetImpl::add(int signum) { + try { + signal_set_.add(signum); + } catch (const boost::system::system_error& ex) { + isc_throw(isc::Unexpected, "Failed to add signal " << signum + << ": " << ex.what()); } } -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); +IOSignalSet::IOSignalSet(IOServicePtr io_service, IOSignalHandler handler) : + impl_(new IOSignalSetImpl(io_service, handler)) +{ + // It can throw but the error is fatal... + impl_->install(); } -IOSignalPtr -IOSignalQueue::popSignal(IOSignalId sequence_id) { - // Look for the signal in the queue. - IOSignalMap::iterator it = signals_.find(sequence_id); - if (it == signals_.end()) { - // This really shouldn't happen unless we are in the weeds. - isc_throw (IOSignalError, "popSignal - " - "signal not found for sequence_id: " << sequence_id); - } - - // Save the signal so we can return it. - IOSignalPtr signal = ((*it).second); - - // Delete it from the queue. - signals_.erase(it); - - // Return the signal. - return (signal); +IOSignalSet::~IOSignalSet() { + impl_.reset(); } void -IOSignalQueue::clear() { - signals_.clear(); +IOSignalSet::add(int signum) { + impl_->add(signum); } }; // end of isc::process namespace diff --git a/src/lib/process/io_service_signal.h b/src/lib/process/io_service_signal.h index a393c164a5..22fe53b5d3 100644 --- a/src/lib/process/io_service_signal.h +++ b/src/lib/process/io_service_signal.h @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -8,250 +8,45 @@ #define IO_SERVICE_SIGNAL_H #include -#include -#include -#include -#include +#include namespace isc { namespace process { -/// @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; +typedef boost::function IOSignalHandler; + +class IOSignalSetImpl; /// @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::process::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::process::IOSignalQueue::popSignal() passing it the -/// sequence_id parameter passed to the handler. This method returns a -/// pointer to instigating IOSignal from which the value of OS signal (i.e. -/// SIGINT, SIGUSR1...) can be obtained. Note that calling popSignal() -/// removes the IOSignalPtr from the queue, which should reduce its -/// reference count to zero upon exiting the handler (unless a deliberate -/// copy of it is made). -/// -/// A typical IOSignalHandler might be structured as follows: -/// @code -/// -/// void processSignal(IOSignalId sequence_id) { -/// // Pop the signal instance off the queue. -/// IOSignalPtr signal = io_signal_queue_->popSignal(sequence_id); -/// -/// int os_signal_value = signal->getSignum(); -/// : -/// // logic based on the signal value -/// : -/// } -/// -/// @endcode -/// -/// IOSignal handler invocation code will catch, log ,and then swallow any -/// exceptions thrown by a IOSignalHandler invocation. This is done to protect -/// the integrity IOService context. -/// -class IOSignal { -public: - /// @brief Constructor - /// - /// @param io_service IOService to which to send the signal - /// @param signum value of the signal to send - /// @param handler the handler to run when IOService "receives" the - /// signal - /// - /// @throw IOSignalError if handler is null - IOSignal(asiolink::IOService& io_service, int signum, - IOSignalHandler handler); - - /// @brief Destructor - ~IOSignal(); - - /// @brief Static method for generating IOSignal sequence_ids. - /// - /// Generates and returns the next IOSignalId. This method is intentionally - /// static in the event a process is using generating signals to more than - /// IOService. It assures that each IOSignal is unique with the process - /// space. - /// - /// @return The next sequential value as an IOSignalId. - static IOSignalId nextSequenceId() { - static IOSignalId next_id_ = 0; - return (++next_id_); - } - - /// @brief Gets the IOSignal's sequence_id - /// - /// @return The sequence_id of the signal. - IOSignalId getSequenceId() const { - return (sequence_id_); - } - - /// @brief Gets the OS signal value this IOSignal represents. - /// - /// @return The OS signal value (i.e. SIGINT, SIGUSR1...) - int getSignum() const { - return (signum_); - } - - /// @brief Defines the callback used by IOSignal's internal timer. - /// - /// This class stores the sequence_id of the IOSignal being sent and the - /// IOSignalHandler to invoke when delivering the signal. The () operator - /// is called by IOService when the timer expires. This method invokes - /// the IOSignalHandler passing it the sequence_id. - class TimerCallback : public std::unary_function { - 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 retrieved via the popSignal() method it is removed from the map. -class IOSignalQueue { +/// as a ready event with a callback using boost ASIO. +class IOSignalSet { public: - /// @brief Constructor + /// @brief Constructor. /// - /// @param io_service the IOService to which to send signals. - /// @throw IOSignalError if io_service is NULL. - IOSignalQueue (asiolink::IOServicePtr& io_service); + /// @param io_service IOService to which to send the signal. + /// @param handler Handler to call when a signal is received. + IOSignalSet(asiolink::IOServicePtr io_service, IOSignalHandler handler); /// @brief Destructor. - ~IOSignalQueue(); + ~IOSignalSet(); - /// @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 delivered. - /// - /// @return The sequence_id of the newly created signal. + /// @brief Add a signal to the list of signals to handle. /// - /// @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 unlikely. - 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(); + /// @param signum Signal number. + /// @throw Unexpected on error. + void add(int signum); private: - /// @brief Pointer to the IOService which will receive the signals. - asiolink::IOServicePtr io_service_; - - /// @brief A map of the IOSignals pushed through this queue. - IOSignalMap signals_; + /// @brief Pointer to the implementation. + boost::shared_ptr impl_; }; -/// @brief Defines a pointer to an IOSignalQueue. -typedef boost::shared_ptr IOSignalQueuePtr; - +/// @brief Defines a pointer to an IOSignalSet. +typedef boost::shared_ptr IOSignalSetPtr; }; // end of isc::process namespace }; // end of isc namespace diff --git a/src/lib/process/tests/d_controller_unittests.cc b/src/lib/process/tests/d_controller_unittests.cc index 875cea2993..accc6dc6b1 100644 --- a/src/lib/process/tests/d_controller_unittests.cc +++ b/src/lib/process/tests/d_controller_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/src/lib/process/tests/io_service_signal_unittests.cc b/src/lib/process/tests/io_service_signal_unittests.cc index c4e5a6f84b..4e04a2fc4d 100644 --- a/src/lib/process/tests/io_service_signal_unittests.cc +++ b/src/lib/process/tests/io_service_signal_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -6,7 +6,6 @@ #include -#include #include #include @@ -18,82 +17,59 @@ namespace isc { namespace process { -/// @brief Test fixture for testing the use of IOSignals. +/// @brief Test fixture for testing the use of IO service signals. /// -/// This fixture is exercises IOSignaling as it is intended to be used in -/// an application in conjunction with util::SignalSet. +/// This fixture is exercises io service signaling it is intended to be used in +/// an application in place of util::SignalSet. class IOSignalTest : public ::testing::Test { public: /// @brief IOService instance to process IO. asiolink::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 IOSignalSet object. + IOSignalSetPtr io_signal_set_; /// @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 + /// @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_)), + test_time_ms_(0), io_signal_set_(), 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(); + io_signal_set_.reset(new IOSignalSet( + io_service_, + boost::bind(&IOSignalTest::processSignal, + this, _1))); } - /// @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 Destructor. + ~IOSignalTest() {} - /// @brief Method used as the IOSignalHandler. + /// @brief Method used as the IOSignalSet handler. /// /// 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); - + /// @param signum signal number. + void processSignal(int signum) { // Remember the signal we got. - processed_signals_.push_back(signal->getSignum()); + processed_signals_.push_back(signum); // If the flag is on, force a throw to test error handling. if (handler_throw_error_) { @@ -128,99 +104,7 @@ public: }; // Used for constructor tests. -void dummyHandler(IOSignalId) { -} - -// Tests IOSignal constructor. -TEST(IOSignal, construction) { - asiolink::IOServicePtr io_service(new asiolink::IOService()); - IOSignalPtr signal; - IOSignalPtr signal2; - - // Verify that handler cannot be empty. - ASSERT_THROW(signal.reset(new IOSignal(*io_service, SIGINT, - IOSignalHandler())), - IOSignalError); - - // Verify constructor with valid arguments works. - ASSERT_NO_THROW(signal.reset(new IOSignal(*io_service, SIGINT, - dummyHandler))); - // Verify sequence_id is set. - EXPECT_EQ(IOSignal::nextSequenceId()-1, signal->getSequenceId()); - - // Verify SIGINT is correct. - EXPECT_EQ(SIGINT, signal->getSignum()); - - // Make a second signal. - ASSERT_NO_THROW(signal2.reset(new IOSignal(*io_service, SIGUSR1, - dummyHandler))); - - // Verify sequence_id is not the same as the previous one. - EXPECT_NE(signal2->getSequenceId(), signal->getSequenceId()); - - // Verify that the signal value is correct. - EXPECT_EQ(SIGUSR1, signal2->getSignum()); -} - -// Tests IOSignalQueue constructors and exercises queuing methods. -TEST(IOSignalQueue, constructionAndQueuing) { - IOSignalQueuePtr queue; - asiolink::IOServicePtr io_service; - - // Verify constructing with an empty IOService will throw. - ASSERT_THROW(queue.reset(new IOSignalQueue(io_service)), IOSignalError); - - // Verify valid construction works. - io_service.reset(new asiolink::IOService()); - ASSERT_NO_THROW(queue.reset(new IOSignalQueue(io_service))); - - // Verify an empty handler is not allowed. - ASSERT_THROW(queue->pushSignal(SIGINT, IOSignalHandler()), IOSignalError); - - // Verify that we can queue valid entries. - std::vector ids; - ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGINT, dummyHandler))); - ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGUSR1, dummyHandler))); - ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGUSR2, dummyHandler))); - - // Now verify that we can pop each one and what we pop is correct. - // Verify popping it again, throws. We'll do it in a non-sequential order. - - // Check the middle one. - IOSignalPtr signal; - ASSERT_NO_THROW(signal = queue->popSignal(ids[1])); - ASSERT_TRUE(signal); - EXPECT_EQ(ids[1], signal->getSequenceId()); - EXPECT_EQ(SIGUSR1, signal->getSignum()); - ASSERT_THROW(queue->popSignal(ids[1]), IOSignalError); - - // Check the first one. - ASSERT_NO_THROW(signal = queue->popSignal(ids[0])); - ASSERT_TRUE(signal); - EXPECT_EQ(ids[0], signal->getSequenceId()); - EXPECT_EQ(SIGINT, signal->getSignum()); - ASSERT_THROW(queue->popSignal(ids[0]), IOSignalError); - - // Check the last one. - ASSERT_NO_THROW(signal = queue->popSignal(ids[2])); - ASSERT_TRUE(signal); - EXPECT_EQ(ids[2], signal->getSequenceId()); - EXPECT_EQ(SIGUSR2, signal->getSignum()); - ASSERT_THROW(queue->popSignal(ids[2]), IOSignalError); - - // Now we will test clearing the queue. Queue three signals. - ids.clear(); - for (int i = 0; i < 3; ++i) { - ASSERT_NO_THROW(ids.push_back(queue->pushSignal(SIGINT, dummyHandler))); - } - - // Now clear the queue. - ASSERT_NO_THROW(queue->clear()); - - // We should not be able to dequeue any of them. - for (int i = 0; i < 3; ++i) { - ASSERT_THROW(queue->popSignal(ids[i]), IOSignalError); - } +void dummyHandler(int) { } // Test the basic mechanics of IOSignal by handling one signal occurrence. @@ -228,16 +112,8 @@ 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))); + ASSERT_NO_THROW(io_signal_set_->add(SIGINT)); // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run. TimedSignal sig_int(*io_service_, SIGINT, 100); @@ -249,6 +125,9 @@ TEST_F(IOSignalTest, singleSignalTest) { // The next handler executed is IOSignal's handler. io_service_->run_one(); + // Polling once to be sure. + io_service_->poll(); + // Verify that we processed the signal. ASSERT_EQ(1, processed_signals_.size()); @@ -256,19 +135,14 @@ TEST_F(IOSignalTest, singleSignalTest) { 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. We want to allow at least 100 ms per signal, // plus a bit more so 6 seconds ought to be enough. setTestTime(6000); - // 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))); + // Register to receive SIGINT. + ASSERT_NO_THROW(io_signal_set_->add(SIGINT)); // Stop the test after 50 signals. This allows 100ms+ per signal // so even sluggish VMs should handle it. @@ -298,12 +172,8 @@ 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))); + // Register to receive SIGINT. + ASSERT_NO_THROW(io_signal_set_->add(SIGINT)); // Set the stop after we've done at least 1 all the way through. stop_at_count_ = 1; @@ -330,13 +200,10 @@ 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))); + // Register to receive SIGINT, SIGUSR1, and SIGUSR2. + ASSERT_NO_THROW(io_signal_set_->add(SIGINT)); + ASSERT_NO_THROW(io_signal_set_->add(SIGUSR1)); + ASSERT_NO_THROW(io_signal_set_->add(SIGUSR2)); // Stop the test after 21 signals. Needs to be a multiple of 3. stop_at_count_ = 21; diff --git a/src/lib/process/testutils/d_test_stubs.h b/src/lib/process/testutils/d_test_stubs.h index 64bbd4f4fb..ac6ff13ff8 100644 --- a/src/lib/process/testutils/d_test_stubs.h +++ b/src/lib/process/testutils/d_test_stubs.h @@ -7,7 +7,7 @@ #ifndef D_TEST_STUBS_H #define D_TEST_STUBS_H -#include +#include #include #include