From 811a19c99a31ac2456aef7a57ffbb488ebdd72f5 Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Tue, 25 Aug 2015 13:56:33 +0200 Subject: [PATCH] [3970] Basic implementation of the TimerMgr singleton. --- src/lib/dhcpsrv/Makefile.am | 1 + src/lib/dhcpsrv/tests/Makefile.am | 1 + src/lib/dhcpsrv/tests/timer_mgr_unittest.cc | 200 ++++++++++++++++++++ src/lib/dhcpsrv/timer_mgr.cc | 81 ++++++++ src/lib/dhcpsrv/timer_mgr.h | 108 +++++++++++ 5 files changed, 391 insertions(+) create mode 100644 src/lib/dhcpsrv/tests/timer_mgr_unittest.cc create mode 100644 src/lib/dhcpsrv/timer_mgr.cc create mode 100644 src/lib/dhcpsrv/timer_mgr.h diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 1478e774ef..eeabbc3d97 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -120,6 +120,7 @@ libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h libkea_dhcpsrv_la_SOURCES += subnet_id.h libkea_dhcpsrv_la_SOURCES += subnet_selector.h +libkea_dhcpsrv_la_SOURCES += timer_mgr.cc timer_mgr.h libkea_dhcpsrv_la_SOURCES += triplet.h libkea_dhcpsrv_la_SOURCES += utils.h libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index a4c9f2217f..72eaec30fd 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -103,6 +103,7 @@ libdhcpsrv_unittests_SOURCES += subnet_unittest.cc libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h libdhcpsrv_unittests_SOURCES += triplet_unittest.cc libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h +libdhcpsrv_unittests_SOURCES += timer_mgr_unittest.cc libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES) if HAVE_MYSQL diff --git a/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc new file mode 100644 index 0000000000..ae207f2573 --- /dev/null +++ b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc @@ -0,0 +1,200 @@ +// Copyright (C) 2015 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 + +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 Waits for one ready handler to be executed. + /// + /// @param timeout Wait timeout. + /// @return false if the timeout has occurred, true otherwise. The returned + /// value of true indicates that the test was successful, i.e. the timer + /// ready handler had been executed before the timeout occurred. + bool waitForOne(const long timeout); + + /// @brief Waits for a specified amount of time to execute ready handlers. + /// + /// This method waits for exactly @c timeout amount of time for all ready + /// handlers to be executed. A caller can determine whether the expected + /// handlers have been executed by checking the @c calls_count_ entries. + /// + /// @param timeout Wait timeout. + void waitForMany(const long timeout); + +private: + + /// @brief Wait for one or many ready handlers. + /// + /// This method is called internally by the public methods + /// @c waitForOne and @c waitForMany. + /// + /// @param timeout Wait timeout. + /// @param wait_for_many A boolean flag indicating if the method should + /// wait for the specified amount of time to execute all handlers (if true) + /// or should wait for one ready handlers (if false). + /// + /// @return false if the timeout has occurred, true otherwise. + bool doWait(const long timeout, const bool wait_for_many); + + /// @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 for timeout. + /// + /// This callback is installed when the @c waitForOne or @c waitForMany + /// is executed to stop waiting after a given amount of time. It stops + /// the io service in the @c TimerMgr. + void timeoutCallback(asiolink::IOService& io_service); + + /// @brief Internal flag indicating if test timeout occurred. + /// + /// This flag is set by the @c timeoutCallback function when the timeout + /// has occurred. The @c waitWithTimeout returns 'false' if this value + /// is 'true', and 'true' if this value is 'false'. + bool timeout_occurred_; + +public: + + /// @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. + std::map calls_count_; + +}; + +void +TimerMgrTest::SetUp() { + calls_count_.clear(); + timeout_occurred_ = false; +} + +void +TimerMgrTest::TearDown() { +} + +void +TimerMgrTest::registerTimer(const std::string& timer_name, const long timer_interval, + const IntervalTimer::Mode& timer_mode) { + TimerMgr& timer_mgr = TimerMgr::instance(); + + ASSERT_NO_THROW( + timer_mgr.registerTimer(timer_name, boost::bind(&TimerMgrTest::timerCallback, + this, timer_name), + timer_interval, timer_mode) + ); + + calls_count_[timer_name] = 0; + +} + +bool +TimerMgrTest::waitForOne(const long timeout) { + return (doWait(timeout, false)); +} + +void +TimerMgrTest::waitForMany(const long timeout) { + static_cast(doWait(timeout, true)); +} + +bool +TimerMgrTest::doWait(const long timeout, const bool wait_for_many) { + IOService& io_service = TimerMgr::instance().getIOService(); + IntervalTimer timeout_timer(io_service); + timeout_timer.setup(boost::bind(&TimerMgrTest::timeoutCallback, this, + boost::ref(io_service)), timeout, + IntervalTimer::ONE_SHOT); + if (wait_for_many) { + io_service.run(); + + } else { + io_service.run_one(); + } + + if (timeout_occurred_) { + // Reset the flag so as it is set to false for another test. + timeout_occurred_ = false; + return (false); + } + + // No timeout, some ready handlers have been executed. + return (true); + +} + +void +TimerMgrTest::timerCallback(const std::string& timer_name) { + // Accumulate the number of calls to the timer handler. + ++calls_count_[timer_name]; +} + +void +TimerMgrTest::timeoutCallback(asiolink::IOService& io_service) { + // Timeout has occurred. Stop the io service to stop waiting for + // ready handlers. + io_service.stop(); + // Indicate that we hit the timeout. + timeout_occurred_ = true; +} + +TEST_F(TimerMgrTest, registerTimer) { + TimerMgr& timer_mgr = TimerMgr::instance(); + + ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1)); + TimerMgr::instance().setup("timer1"); + + EXPECT_TRUE(waitForOne(5)); + + EXPECT_EQ(1, calls_count_["timer1"]); +} + +} // end of anonymous namespace diff --git a/src/lib/dhcpsrv/timer_mgr.cc b/src/lib/dhcpsrv/timer_mgr.cc new file mode 100644 index 0000000000..dfbf61d745 --- /dev/null +++ b/src/lib/dhcpsrv/timer_mgr.cc @@ -0,0 +1,81 @@ +// Copyright (C) 2015 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 + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +TimerMgr& +TimerMgr::instance() { + static TimerMgr timer_mgr; + return (timer_mgr); +} + +TimerMgr::TimerMgr() + : io_service_(new IOService()) { +} + +void +TimerMgr::registerTimer(const std::string& timer_name, + const IntervalTimer::Callback& callback, + const long interval, + const IntervalTimer::Mode& scheduling_mode) { + + if (timer_name.empty()) { + isc_throw(BadValue, "registered timer name must not be empty"); + } + + if (registered_timers_.find(timer_name) != registered_timers_.end()) { + isc_throw(BadValue, "trying to register duplicate timer '" + << timer_name << "'"); + } + + WatchSocket watch_socket; + IntervalTimerPtr interval_timer(new IntervalTimer(getIOService())); + TimerInfo timer_info(watch_socket, interval_timer, callback, interval, + scheduling_mode); + registered_timers_.insert(std::pair(timer_name, timer_info)); +} + +void +TimerMgr::deregisterTimer(const std::string& timer_name) { +} + +void +TimerMgr::setup(const std::string& timer_name) { + TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name); + if (timer_info_it == registered_timers_.end()) { + isc_throw(BadValue, "unable to setup timer '" << timer_name << "': " + "no such timer registered"); + } + + const TimerInfo& timer_info = timer_info_it->second; + timer_info.interval_timer_->setup(timer_info.callback_, timer_info.interval_, + timer_info.scheduling_mode_); +} + +void +TimerMgr::localCallback(const std::string& timer_name) const { +} + + + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/timer_mgr.h b/src/lib/dhcpsrv/timer_mgr.h new file mode 100644 index 0000000000..5f1eb02b98 --- /dev/null +++ b/src/lib/dhcpsrv/timer_mgr.h @@ -0,0 +1,108 @@ +// Copyright (C) 2015 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 TIMER_MGR_H +#define TIMER_MGR_H + +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +class TimerMgr : public boost::noncopyable { +public: + + static TimerMgr& instance(); + + void registerTimer(const std::string& timer_name, + const asiolink::IntervalTimer::Callback& callback, + const long interval, + const asiolink::IntervalTimer::Mode& scheduling_mode); + + void deregisterTimer(const std::string& timer_name); + + void setup(const std::string& timer_name); + + asiolink::IOService& getIOService() const { + return (*io_service_); + } + +private: + + /// @brief Private default constructor. + /// + /// The @c TimerMgr is a singleton class which instance must be created + /// using the @c TimerMgr::instance method. Private constructor enforces + /// construction via @c TimerMgr::instance. + TimerMgr(); + + /// @brief Callback function to be executed for each interval timer when + /// its scheduled interval elapses. + /// + /// @param timer_name Unique timer name to be passed to the callback. + void localCallback(const std::string& timer_name) const; + + /// @brief Holds the pointer to the io service object. + asiolink::IOServicePtr io_service_; + + /// @brief Structure holding information for a single timer. + /// + /// This structure holds the instance of the watch socket being used to + /// signal that the timer is "ready". It also holds the instance of the + /// interval timer. + struct TimerInfo { + /// @brief Instance of the watch socket. + util::WatchSocket watch_socket_; + + /// @brief Instance of the interval timer. + asiolink::IntervalTimerPtr interval_timer_; + + asiolink::IntervalTimer::Callback callback_; + + long interval_; + + asiolink::IntervalTimer::Mode scheduling_mode_; + + TimerInfo(const util::WatchSocket& watch_socket, + const asiolink::IntervalTimerPtr& interval_timer, + const asiolink::IntervalTimer::Callback& callback, + const long interval, + const asiolink::IntervalTimer::Mode& mode) + : watch_socket_(watch_socket), + interval_timer_(interval_timer), + callback_(callback), + interval_(interval), + scheduling_mode_(mode) { }; + }; + + typedef std::map TimerInfoMap; + + /// @brief Holds mapping of the timer name to the watch socket and timer + /// instance. + /// + /// Each registered timer has a unique name which is used as a key to + /// the map. The timer is associated with an instance of the @c WatchSocket + /// which is marked ready when the interval for the particular elapses. + std::map registered_timers_; +}; + +} // end of namespace isc::dhcp +} // end of namespace isc + +#endif // TIMER_MGR_H -- 2.47.2