summaryrefslogtreecommitdiffstats
path: root/src/lib/asiolink/interval_timer.cc
blob: 18088cb81fc77c3d753bebfe1c276c670586ba36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright (C) 2011-2023 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 <asiolink/interval_timer.h>
#include <asiolink/io_service.h>

#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>

#include <exceptions/exceptions.h>

#include <atomic>
#include <functional>
#include <mutex>

using namespace std;
namespace ph = std::placeholders;

namespace isc {
namespace asiolink {

/// This class holds a call back function of asynchronous operations.
/// To ensure the object is alive while an asynchronous operation refers
/// to it, we use shared_ptr and enable_shared_from_this.
/// The object will be destructed in case IntervalTimer has been destructed
/// and no asynchronous operation refers to it.
/// Please follow the link to get an example:
/// http://think-async.com/asio/asio-1.4.8/doc/asio/tutorial/tutdaytime3.html#asio.tutorial.tutdaytime3.the_tcp_connection_class
class IntervalTimerImpl :
    public boost::enable_shared_from_this<IntervalTimerImpl>,
    public boost::noncopyable {
public:

    /// @brief Constructor.
    ///
    /// @param io_service The IO service used to handle events.
    IntervalTimerImpl(IOService& io_service);

    /// @brief Destructor.
    ~IntervalTimerImpl();

    /// @brief Setup function to register callback and start timer.
    ///
    /// @param cbfunc The callback function registered on timer.
    /// @param interval The interval used to start the timer.
    /// @param interval_mode The interval mode used by the timer.
    void setup(const IntervalTimer::Callback& cbfunc, const long interval,
               const IntervalTimer::Mode& interval_mode
               = IntervalTimer::REPEATING);

    /// @brief Callback function which calls the registerd callback.
    ///
    /// @param error The error code retrieved from the timer.
    void callback(const boost::system::error_code& error);

    /// @brief Cancel timer.
    void cancel() {
        lock_guard<mutex> lk (mutex_);
        timer_.cancel();
        interval_ = 0;
        cbfunc_ = std::function<void()>();
    }

    /// @brief Get the timer interval.
    ///
    /// @return The timer interval.
    long getInterval() const { return (interval_); }

private:

    /// @brief Update function to update timer_ when it expires.
    ///
    /// Should be called in a thread safe context.
    void update();

    /// @brief The callback function to call when timer_ expires.
    IntervalTimer::Callback cbfunc_;

    /// @brief The interval in milliseconds.
    std::atomic<long> interval_;

    /// @brief The asio timer.
    boost::asio::deadline_timer timer_;

    /// @brief Controls how the timer behaves after expiration.
    IntervalTimer::Mode mode_;

    /// @brief Mutex to protect the internal state.
    std::mutex mutex_;

    /// @brief Invalid interval value.
    ///
    /// @ref 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()),
    mode_(IntervalTimer::REPEATING) {
}

IntervalTimerImpl::~IntervalTimerImpl() {
    interval_ = INVALIDATED_INTERVAL;
}

void
IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
                         const long interval,
                         const IntervalTimer::Mode& mode) {
    // Interval should not be less than 0.
    if (interval < 0) {
        isc_throw(isc::BadValue, "Interval should not be less than or "
                                 "equal to 0");
    }
    // Call back function should not be empty.
    if (!cbfunc) {
        isc_throw(isc::InvalidParameter, "Callback function is empty");
    }

    lock_guard<mutex> lk(mutex_);
    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.
    update();
}

void
IntervalTimerImpl::update() {
    try {
        // Update expire time to (current time + interval_).
        timer_.expires_from_now(boost::posix_time::millisec(long(interval_)));
        // Reset timer.
        // Pass a function bound with a shared_ptr to this.
        timer_.async_wait(std::bind(&IntervalTimerImpl::callback,
                                    shared_from_this(),
                                    ph::_1)); //error
    } catch (const boost::system::system_error& e) {
        isc_throw(isc::Unexpected, "Failed to update timer: " << e.what());
    } catch (const boost::bad_weak_ptr&) {
        // Can't happen. It means a severe internal bug.
    }
}

void
IntervalTimerImpl::callback(const boost::system::error_code& ec) {
    if (interval_ == INVALIDATED_INTERVAL) {
        isc_throw(isc::BadValue, "Interval internal state");
    }
    if (interval_ == 0 || ec) {
        // timer has been canceled. Do nothing.
    } else {
        {
            lock_guard<mutex> lk(mutex_);
            // If we should repeat, set next expire time.
            if (mode_ == IntervalTimer::REPEATING) {
                update();
            }
        }

        // Invoke the call back function.
        cbfunc_();
    }
}

IntervalTimer::IntervalTimer(IOService& io_service) :
    impl_(new IntervalTimerImpl(io_service)) {
}

IntervalTimer::~IntervalTimer() {
    // Cancel the timer to make sure cbfunc_() will not be called any more.
    cancel();
}

void
IntervalTimer::setup(const Callback& cbfunc, const long interval,
                     const IntervalTimer::Mode& mode) {
    return (impl_->setup(cbfunc, interval, mode));
}

void
IntervalTimer::cancel() {
    impl_->cancel();
}

long
IntervalTimer::getInterval() const {
    return (impl_->getInterval());
}

} // namespace asiolink
} // namespace isc