Browse Source

[3970] Basic implementation of the TimerMgr singleton.

Marcin Siodelski 9 years ago
parent
commit
811a19c99a

+ 1 - 0
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

+ 1 - 0
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

+ 200 - 0
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 <config.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <boost/bind.hpp>
+#include <gtest/gtest.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 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<std::string, unsigned int> 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<void>(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

+ 81 - 0
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 <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+
+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<std::string, TimerInfo>(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

+ 108 - 0
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 <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <util/watch_socket.h>
+#include <boost/noncopyable.hpp>
+#include <map>
+#include <string>
+
+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<std::string, TimerInfo> 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<std::string, TimerInfo> registered_timers_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // TIMER_MGR_H