123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- // Copyright (C) 2016 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
- // file, You can obtain one at http://mozilla.org/MPL/2.0/.
- #include <config.h>
- #include <asiolink/asio_wrapper.h>
- #include <dhcp/iface_mgr.h>
- #include <dhcpsrv/timer_mgr.h>
- #include <exceptions/exceptions.h>
- #include <util/stopwatch.h>
- #include <boost/bind.hpp>
- #include <gtest/gtest.h>
- #include <sstream>
- #include <unistd.h>
- using namespace isc;
- using namespace isc::dhcp;
- using namespace isc::asiolink;
- namespace {
- /// @brief Test fixture class for @c TimerMgr.
- class TimerMgrTest : public ::testing::Test {
- private:
- /// @brief Prepares the class for a test.
- virtual void SetUp();
- /// @brief Cleans up after the test.
- virtual void TearDown();
- public:
- /// @brief Wrapper method for registering a new timer.
- ///
- /// This method registers a new timer in the @c TimerMgr. It associates a
- /// @c timerCallback method with a timer. This method registers a number of
- /// calls to the particular timer in the @c calls_count_ map.
- ///
- /// @param timer_name Unique timer name.
- /// @param timer_interval Timer interval.
- /// @param mode Interval timer mode, which defaults to
- /// @c IntervalTimer::ONE_SHOT.
- void registerTimer(const std::string& timer_name, const long timer_interval,
- const IntervalTimer::Mode& timer_mode = IntervalTimer::ONE_SHOT);
- /// @brief Wait for one or many ready handlers.
- ///
- /// @param timeout Wait timeout in milliseconds.
- /// @param call_receive Indicates if the @c IfaceMgr::receive6
- /// should be called to run pending callbacks and clear
- /// watch sockets.
- void doWait(const long timeout, const bool call_receive = true);
- /// @brief Generic callback for timers under test.
- ///
- /// This callback increases the calls count for specified timer name.
- ///
- /// @param timer_name Name of the timer for which callback counter should
- /// be increased.
- void timerCallback(const std::string& timer_name);
- /// @brief Callback which generates exception.
- ///
- /// This callback is used to test that the @c TimerMgr can handle
- /// the case when the callback generates exceptions.
- void timerCallbackWithException();
- /// @brief Create a generic callback function for the timer.
- ///
- /// This is just a wrapped to make it a bit more convenient
- /// in the test.
- boost::function<void ()> makeCallback(const std::string& timer_name);
- /// @brief Create a callback which generates exception.
- boost::function<void ()> makeCallbackWithException();
- /// @brief Callback for timeout.
- ///
- /// This callback indicates the test timeout by setting the
- /// @c timeout_ member.
- void timeoutCallback();
- /// @brief Type definition for a map holding calls counters for
- /// timers.
- typedef std::map<std::string, unsigned int> CallsCount;
- /// @brief Holds the calls count for test timers.
- ///
- /// The key of this map holds the timer names. The value holds the number
- /// of calls to the timer handlers.
- CallsCount calls_count_;
- /// @brief Instance of @c TimerMgr used by the tests.
- TimerMgrPtr timer_mgr_;
- };
- void
- TimerMgrTest::SetUp() {
- timer_mgr_ = TimerMgr::instance();
- calls_count_.clear();
- // Make sure there are no dangling threads.
- timer_mgr_->stopThread();
- }
- void
- TimerMgrTest::TearDown() {
- // Make sure there are no dangling threads.
- timer_mgr_->stopThread();
- // Remove all timers.
- timer_mgr_->unregisterTimers();
- }
- void
- TimerMgrTest::registerTimer(const std::string& timer_name, const long timer_interval,
- const IntervalTimer::Mode& timer_mode) {
- // Register the timer with the generic callback that counts the
- // number of callback invocations.
- ASSERT_NO_THROW(
- timer_mgr_->registerTimer(timer_name, makeCallback(timer_name), timer_interval,
- timer_mode)
- );
- calls_count_[timer_name] = 0;
- }
- void
- TimerMgrTest::doWait(const long timeout, const bool call_receive) {
- util::Stopwatch stopwatch;
- while (stopwatch.getTotalMilliseconds() < timeout) {
- if (call_receive) {
- // Block for one 1 millisecond.
- IfaceMgr::instancePtr()->receive6(0, 1000);
- }
- }
- }
- void
- TimerMgrTest::timerCallback(const std::string& timer_name) {
- // Accumulate the number of calls to the timer handler.
- ++calls_count_[timer_name];
- // The timer installed is the ONE_SHOT timer, so we have
- // to reschedule the timer.
- timer_mgr_->setup(timer_name);
- }
- void
- TimerMgrTest::timerCallbackWithException() {
- isc_throw(Exception, "timerCallbackWithException");
- }
- boost::function<void ()>
- TimerMgrTest::makeCallback(const std::string& timer_name) {
- return (boost::bind(&TimerMgrTest::timerCallback, this, timer_name));
- }
- boost::function<void ()>
- TimerMgrTest::makeCallbackWithException() {
- return (boost::bind(&TimerMgrTest::timerCallbackWithException, this));
- }
- // This test checks that certain errors are returned when invalid
- // parameters are specified when registering a timer, or when
- // the registration can't be made.
- TEST_F(TimerMgrTest, registerTimer) {
- // Empty timer name is not allowed.
- ASSERT_THROW(timer_mgr_->registerTimer("", makeCallback("timer1"), 1,
- IntervalTimer::ONE_SHOT),
- BadValue);
- // Add a timer with a correct name.
- ASSERT_NO_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1,
- IntervalTimer::ONE_SHOT));
- // Adding the timer with the same name as the existing timer is not
- // allowed.
- ASSERT_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1,
- IntervalTimer::ONE_SHOT),
- BadValue);
- // Start worker thread.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- // Can't register the timer when the thread is running.
- ASSERT_THROW(timer_mgr_->registerTimer("timer1", makeCallback("timer1"), 1,
- IntervalTimer::ONE_SHOT),
- InvalidOperation);
- // Stop the thread and retry.
- ASSERT_NO_THROW(timer_mgr_->stopThread());
- EXPECT_NO_THROW(timer_mgr_->registerTimer("timer1", makeCallback("timer1"), 1,
- IntervalTimer::ONE_SHOT));
- }
- // This test verifies that it is possible to unregister a timer from
- // the TimerMgr.
- TEST_F(TimerMgrTest, unregisterTimer) {
- // Register a timer and start it.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
- ASSERT_EQ(1, timer_mgr_->timersCount());
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->startThread());
- // Wait for the timer to execute several times.
- doWait(100);
- // Stop the thread but execute pending callbacks.
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- // Remember how many times the timer's callback was executed.
- const unsigned int calls_count = calls_count_["timer1"];
- ASSERT_GT(calls_count, 0);
- // Check that an attempt to unregister a non-existing timer would
- // result in exeception.
- ASSERT_THROW(timer_mgr_->unregisterTimer("timer2"), BadValue);
- // Number of timers shouldn't have changed.
- ASSERT_EQ(1, timer_mgr_->timersCount());
- // Now unregister the correct one.
- ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1"));
- ASSERT_EQ(0, timer_mgr_->timersCount());
- // Start the thread again and wait another 100ms.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(100);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- // The number of calls for the timer1 shouldn't change as the
- // timer had been unregistered.
- EXPECT_EQ(calls_count_["timer1"], calls_count);
- }
- // This test verifies taht it is possible to unregister all timers.
- TEST_F(TimerMgrTest, unregisterTimers) {
- // Register 10 timers.
- for (int i = 1; i <= 20; ++i) {
- std::ostringstream s;
- s << "timer" << i;
- ASSERT_NO_FATAL_FAILURE(registerTimer(s.str(), 1))
- << "fatal failure occurred while registering "
- << s.str();
- ASSERT_EQ(i, timer_mgr_->timersCount())
- << "invalid number of registered timers returned";
- ASSERT_NO_THROW(timer_mgr_->setup(s.str()))
- << "exception thrown while calling setup() for the "
- << s.str();
- }
- // Start worker thread and wait for 500ms.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- // Make sure that all timers have been executed at least once.
- for (CallsCount::iterator it = calls_count_.begin();
- it != calls_count_.end(); ++it) {
- unsigned int calls_count = it->second;
- ASSERT_GT(calls_count, 0)
- << "expected calls counter for timer"
- << (std::distance(calls_count_.begin(), it) + 1)
- << " greater than 0";
- }
- // Copy counters for all timers.
- CallsCount calls_count(calls_count_);
- // Let's unregister all timers.
- ASSERT_NO_THROW(timer_mgr_->unregisterTimers());
- // Make sure there are no timers registered.
- ASSERT_EQ(0, timer_mgr_->timersCount());
- // Start worker thread again and wait for 500ms.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- // The calls counter shouldn't change because there are
- // no timers registered.
- EXPECT_TRUE(calls_count == calls_count_);
- }
- // This test checks that it is not possible to unregister timers
- // while the thread is running.
- TEST_F(TimerMgrTest, unregisterTimerWhileRunning) {
- // Register two timers.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 1));
- // Start the thread and make sure we can't unregister them.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- EXPECT_THROW(timer_mgr_->unregisterTimer("timer1"), InvalidOperation);
- EXPECT_THROW(timer_mgr_->unregisterTimers(), InvalidOperation);
- // No need to stop the thread as it will be stopped by the
- // test fixture destructor.
- }
- // This test verifies that the timer execution can be cancelled.
- TEST_F(TimerMgrTest, cancel) {
- // Register timer.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
- // Kick in the timer and wait for 500ms.
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread());
- // Cancelling non-existing timer should fail.
- EXPECT_THROW(timer_mgr_->cancel("timer2"), BadValue);
- // Cancelling the good one should pass, even when the worker
- // thread is running.
- ASSERT_NO_THROW(timer_mgr_->cancel("timer1"));
- // Remember how many calls have been invoked and wait for
- // another 500ms.
- unsigned int calls_count = calls_count_["timer1"];
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(500);
- // Stop thread before we setup again.
- ASSERT_NO_THROW(timer_mgr_->stopThread());
- // The number of calls shouldn't change because the timer had been
- // cancelled.
- ASSERT_EQ(calls_count, calls_count_["timer1"]);
- // Setup the timer again.
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- // Restart the thread.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(500);
- // New calls should be recorded.
- EXPECT_GT(calls_count_["timer1"], calls_count);
- }
- // This test verifies that the callbacks for the scheduled timers are
- // actually called.
- TEST_F(TimerMgrTest, scheduleTimers) {
- // Register two timers: 'timer1' and 'timer2'. The first timer will
- // be executed at the 50ms interval. The second one at the 100ms
- // interval.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 50));
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 100));
- // Kick in the timers.
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->setup("timer2"));
- // We can start the worker thread before we even kick in the timers.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- // Run IfaceMgr::receive6() in the loop for 1000ms. This function
- // will read data from the watch sockets created when the timers
- // were registered. The data is delivered to the watch sockets
- // at the interval of the timers, which should break the blocking
- // call to receive6(). As a result, the callbacks associated
- // with the watch sockets should be called.
- doWait(1000);
- // Stop the worker thread, which would halt the execution of
- // the timers.
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- // We have been running the timer for 1000ms at the interval of
- // 50ms. The maximum number of callbacks is 20. However, the
- // callback itself takes time. Stoping the thread takes time.
- // So, the real number differs significantly. We don't know
- // exactly how many have been executed. It should be more
- // than 10 for sure. But we really made up the numbers here.
- EXPECT_GT(calls_count_["timer1"], 10);
- // For the second timer it should be more than 5.
- EXPECT_GT(calls_count_["timer2"], 5);
- // Because the interval of the 'timer1' is lower than the
- // interval of the 'timer2' the number of calls should
- // be higher for the 'timer1'.
- EXPECT_GT(calls_count_["timer1"], calls_count_["timer2"]);
- // Remember the number of calls from 'timer1' and 'timer2'.
- unsigned int calls_count_timer1 = calls_count_["timer1"];
- unsigned int calls_count_timer2 = calls_count_["timer2"];
- // Unregister the 'timer1'.
- ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1"));
- // Restart the thread.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- // Wait another 500ms. The 'timer1' was unregistered so it
- // should not make any more calls. The 'timer2' should still
- // work as previously.
- doWait(500);
- // The number of calls shouldn't have changed.
- EXPECT_EQ(calls_count_timer1, calls_count_["timer1"]);
- // There should be some new calls registered for the 'timer2'.
- EXPECT_GT(calls_count_["timer2"], calls_count_timer2);
- }
- // This test verifies that it is possible to force that the pending
- // timer callbacks are executed when the worker thread is stopped.
- TEST_F(TimerMgrTest, stopThreadWithRunningHandlers) {
- // Register 'timer1'.
- ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
- // Kick in the timer.
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- ASSERT_NO_THROW(timer_mgr_->startThread());
- // Run the thread for 100ms. This should run some timers. The 'false'
- // value indicates that the IfaceMgr::receive6 is not called, so the
- // watch socket is never cleared.
- doWait(100, false);
- // There should be no calls registered for the timer1.
- EXPECT_EQ(0, calls_count_["timer1"]);
- // Stop the worker thread without completing pending callbacks.
- ASSERT_NO_THROW(timer_mgr_->stopThread(false));
- // There should be still not be any calls registered.
- EXPECT_EQ(0, calls_count_["timer1"]);
- // We can restart the worker thread before we even kick in the timers.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- // Run the thread for 100ms. This should run some timers. The 'false'
- // value indicates that the IfaceMgr::receive6 is not called, so the
- // watch socket is never cleared.
- doWait(100, false);
- // There should be no calls registered for the timer1.
- EXPECT_EQ(0, calls_count_["timer1"]);
- // Stop the worker thread with completing pending callbacks.
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- // There should be one call registered.
- EXPECT_EQ(1, calls_count_["timer1"]);
- }
- // This test verifies that exceptions emitted from the callback would
- // be handled by the TimerMgr.
- TEST_F(TimerMgrTest, callbackWithException) {
- // Create timer which will trigger callback generating exception.
- ASSERT_NO_THROW(
- timer_mgr_->registerTimer("timer1", makeCallbackWithException(), 1,
- IntervalTimer::ONE_SHOT)
- );
- // Setup the timer.
- ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
- // Start thread. We hope that exception will be caught by the @c TimerMgr
- // and will not kill the process.
- ASSERT_NO_THROW(timer_mgr_->startThread());
- doWait(500);
- ASSERT_NO_THROW(timer_mgr_->stopThread(true));
- }
- } // end of anonymous namespace
|