Browse Source

[3407] Added one-shot behavior to set asiolink::IntervalTimer

asiolink::IntervalTimer::setup() has been extended to accept a mode
parameter which determines if the timer will reschedule itself after
each expiration (default behavior), or if it should only run for a
single interval and stop (one-shot).
Thomas Markwalder 11 years ago
parent
commit
9d7128978d

+ 21 - 7
src/lib/asiolink/interval_timer.cc

@@ -44,7 +44,9 @@ private:
 public:
     IntervalTimerImpl(IOService& io_service);
     ~IntervalTimerImpl();
-    void setup(const IntervalTimer::Callback& cbfunc, const long interval);
+    void setup(const IntervalTimer::Callback& cbfunc, const long interval,
+               const IntervalTimer::Mode& interval_mode
+               = IntervalTimer::REPEATING);
     void callback(const asio::error_code& error);
     void cancel() {
         timer_.cancel();
@@ -60,13 +62,18 @@ private:
     long interval_;
     // asio timer
     asio::deadline_timer timer_;
+
+    // Controls how the timer behaves after expiration.
+    IntervalTimer::Mode mode_;
+
     // interval_ will be set to this value in destructor in order to detect
     // use-after-free type of bugs.
     static const long INVALIDATED_INTERVAL = -1;
 };
 
 IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
-    interval_(0), timer_(io_service.get_io_service())
+    interval_(0), timer_(io_service.get_io_service()),
+    mode_(IntervalTimer::REPEATING)
 {}
 
 IntervalTimerImpl::~IntervalTimerImpl() {
@@ -75,7 +82,8 @@ IntervalTimerImpl::~IntervalTimerImpl() {
 
 void
 IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
-                         const long interval)
+                         const long interval,
+                         const IntervalTimer::Mode& mode)
 {
     // Interval should not be less than or equal to 0.
     if (interval <= 0) {
@@ -88,6 +96,8 @@ IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
     }
     cbfunc_ = cbfunc;
     interval_ = interval;
+    mode_ = mode;
+
     // Set initial expire time.
     // At this point the timer is not running yet and will not expire.
     // After calling IOService::run(), the timer will expire.
@@ -118,8 +128,11 @@ IntervalTimerImpl::callback(const asio::error_code& ec) {
     if (interval_ == 0 || ec) {
         // timer has been canceled. Do nothing.
     } else {
-        // Set next expire time.
-        update();
+        // If we should repeat, set next expire time.
+        if (mode_ == IntervalTimer::REPEATING) {
+            update();
+        }
+
         // Invoke the call back function.
         cbfunc_();
     }
@@ -135,8 +148,9 @@ IntervalTimer::~IntervalTimer() {
 }
 
 void
-IntervalTimer::setup(const Callback& cbfunc, const long interval) {
-    return (impl_->setup(cbfunc, interval));
+IntervalTimer::setup(const Callback& cbfunc, const long interval,
+                     const IntervalTimer::Mode& mode) {
+    return (impl_->setup(cbfunc, interval, mode));
 }
 
 void

+ 22 - 5
src/lib/asiolink/interval_timer.h

@@ -31,13 +31,15 @@ class IntervalTimerImpl;
 /// This class is implemented to use \c asio::deadline_timer as interval
 /// timer.
 ///
-/// \c setup() sets a timer to expire on (now + interval) and a call back
-/// function.
+/// \c setup() sets a timer to expire on (now + interval), a call back
+/// function, and an interval mode.
 ///
 /// \c IntervalTimerImpl::callback() is called by the timer when it expires.
 ///
-/// The function calls the call back function set by \c setup() and updates
-/// the timer to expire in (now + interval) milliseconds.
+/// The function calls the call back function set by \c setup() and if the
+/// the interval mode indicates a repeating interval, will reschedule the
+/// timer to expire in (now + interval) milliseconds.
+///
 /// The type of call back function is \c void(void).
 ///
 /// The call back function will not be called if the instance of this class is
@@ -60,6 +62,15 @@ public:
     /// \name The type of timer callback function
     typedef boost::function<void()> Callback;
 
+    /// \brief Defines possible timer modes used to setup a timer.
+    /// - REPEATING - Timer will reschedule itself after each expiration
+    /// - ONE_SHOT - Timer will expire after one interval and not reschedule.
+    enum Mode
+    {
+        REPEATING,
+        ONE_SHOT
+    };
+
     ///
     /// \name Constructors and Destructor
     ///
@@ -96,6 +107,9 @@ public:
     /// \param cbfunc A reference to a function \c void(void) to call back
     /// when the timer is expired (should not be an empty functor)
     /// \param interval Interval in milliseconds (greater than 0)
+    /// \param mode Determines if the timer will automatically reschedule after
+    /// each expiration (the default) or behave as a one-shot which will run
+    /// for a single interval and not reschedule.
     ///
     /// Note: IntervalTimer will not pass \c asio::error_code to
     /// call back function. In case the timer is canceled, the function
@@ -104,7 +118,8 @@ public:
     /// \throw isc::InvalidParameter cbfunc is empty
     /// \throw isc::BadValue interval is less than or equal to 0
     /// \throw isc::Unexpected internal runtime error
-    void setup(const Callback& cbfunc, const long interval);
+    void setup(const Callback& cbfunc, const long interval,
+                    const Mode& = REPEATING);
 
     /// Cancel the timer.
     ///
@@ -128,6 +143,8 @@ private:
     boost::shared_ptr<IntervalTimerImpl> impl_;
 };
 
+typedef boost::shared_ptr<isc::asiolink::IntervalTimer> IntervalTimerPtr;
+
 } // namespace asiolink
 } // namespace isc
 #endif // ASIOLINK_INTERVAL_TIMER_H

+ 82 - 0
src/lib/asiolink/tests/interval_timer_unittest.cc

@@ -57,6 +57,7 @@ protected:
         }
         void operator()() {
             ++counter_;
+            std::cout << "inside counter cb: " << counter_ << std::endl;
             return;
         }
         int counter_;
@@ -137,6 +138,20 @@ protected:
         IntervalTimer& timer_;
         int count_;
     };
+    class TimerCallBackAccumulator: public std::unary_function<void, void> {
+    public:
+        TimerCallBackAccumulator(IntervalTimerTest* test_obj, int &counter) :
+            test_obj_(test_obj), counter_(counter) {
+        }
+        void operator()() {
+            ++counter_;
+            return;
+        }
+    private:
+        IntervalTimerTest* test_obj_;
+        // Reference to integer accumulator
+        int& counter_;
+    };
 protected:
     IOService io_service_;
     bool timer_called_;
@@ -285,3 +300,70 @@ TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     // Expect interval is updated: return value of getInterval() is updated
     EXPECT_EQ(itimer.getInterval(), 100);
 }
+
+// This test verifies that timers operate correclty based on their mode.
+TEST_F(IntervalTimerTest, intervalModeTest) {
+    // Create a timer to control the duration of the test.
+    IntervalTimer test_timer(io_service_);
+    test_timer.setup(TimerCallBack(this), 550);
+
+    // Create an timer which automatically reschedules itself.  Use the
+    // accumulator callback to increment local counter for it.
+    int repeater_count = 0;
+    IntervalTimer repeater(io_service_);
+    repeater.setup(TimerCallBackAccumulator(this, repeater_count), 100);
+
+    // Create a one-shot timer. Use the accumulator callback to increment
+    // local counter variable for it.
+    int one_shot_count = 0;
+    IntervalTimer one_shot(io_service_);
+    one_shot.setup(TimerCallBackAccumulator(this, one_shot_count), 100,
+                   IntervalTimer::ONE_SHOT);
+
+    // Run until the test_timer expires.
+    io_service_.run();
+
+    // Verify the repeating timer repeated and the one-shot did not.
+    EXPECT_EQ(repeater_count, 5);
+    EXPECT_EQ(one_shot_count, 1);
+}
+
+// This test verifies that the same timer can be reused in either mode.
+TEST_F(IntervalTimerTest, timerReuseTest) {
+    // Create a timer to control the duration of the test.
+    IntervalTimer test_timer(io_service_);
+    test_timer.setup(TimerCallBack(this), 550);
+
+    // Create a one-shot timer. Use the accumulator callback to increment
+    // local counter variable for it.
+    int one_shot_count = 0;
+    IntervalTimer one_shot(io_service_);
+    TimerCallBackAccumulator callback(this, one_shot_count);
+    one_shot.setup(callback, 100, IntervalTimer::ONE_SHOT);
+
+    // Run until a single event handler executes.  This should be our
+    // one-shot expiring.
+    io_service_.run_one();
+
+    // Verify the timer expired once.
+    ASSERT_EQ(one_shot_count, 1);
+
+    // Setup the one-shot to go again.
+    one_shot.setup(callback, 100, IntervalTimer::ONE_SHOT);
+
+    // Run until a single event handler executes.  This should be our
+    // one-shot expiring.
+    io_service_.run_one();
+
+    // Verify the timer expired once.
+    ASSERT_EQ(one_shot_count, 2);
+
+    // Setup the timer to be repeating.
+    one_shot.setup(callback, 100, IntervalTimer::REPEATING);
+
+    // Run until the test_timer expires.
+    io_service_.run();
+
+    // Verify the timer repeated.
+    EXPECT_GE(one_shot_count, 4);
+}