/src/kea/src/lib/dhcpsrv/timer_mgr.cc
Line | Count | Source |
1 | | // Copyright (C) 2016-2024 Internet Systems Consortium, Inc. ("ISC") |
2 | | // |
3 | | // This Source Code Form is subject to the terms of the Mozilla Public |
4 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
6 | | |
7 | | #include <config.h> |
8 | | |
9 | | #include <asiolink/interval_timer.h> |
10 | | #include <asiolink/io_service.h> |
11 | | #include <dhcpsrv/dhcpsrv_log.h> |
12 | | #include <dhcpsrv/timer_mgr.h> |
13 | | #include <exceptions/exceptions.h> |
14 | | #include <util/multi_threading_mgr.h> |
15 | | |
16 | | #include <boost/scoped_ptr.hpp> |
17 | | |
18 | | #include <exception> |
19 | | #include <functional> |
20 | | #include <map> |
21 | | #include <mutex> |
22 | | #include <ostream> |
23 | | #include <string> |
24 | | #include <utility> |
25 | | |
26 | | #include <stddef.h> |
27 | | |
28 | | using namespace isc; |
29 | | using namespace isc::asiolink; |
30 | | using namespace isc::util; |
31 | | |
32 | | namespace { |
33 | | |
34 | | /// @brief Structure holding information for a single timer. |
35 | | /// |
36 | | /// This structure holds the instance of the watch socket being used to |
37 | | /// signal that the timer is "ready". It also holds the instance of the |
38 | | /// interval timer and other parameters pertaining to it. |
39 | | struct TimerInfo { |
40 | | /// @brief Instance of the interval timer. |
41 | | asiolink::IntervalTimer interval_timer_; |
42 | | |
43 | | /// @brief Holds the pointer to the callback supplied when registering |
44 | | /// the timer. |
45 | | asiolink::IntervalTimer::Callback user_callback_; |
46 | | |
47 | | /// @brief Interval timer interval supplied during registration. |
48 | | long interval_; |
49 | | |
50 | | /// @brief Interval timer scheduling mode supplied during registration. |
51 | | asiolink::IntervalTimer::Mode scheduling_mode_; |
52 | | |
53 | | /// @brief Constructor. |
54 | | /// |
55 | | /// @param io_service Reference to the IO service to be used by the |
56 | | /// interval timer created. |
57 | | /// @param user_callback Pointer to the callback function supplied |
58 | | /// during the timer registration. |
59 | | /// @param interval Timer interval in milliseconds. |
60 | | /// @param mode Interval timer scheduling mode. |
61 | | TimerInfo(const asiolink::IOServicePtr& io_service, |
62 | | const asiolink::IntervalTimer::Callback& user_callback, |
63 | | const long interval, |
64 | | const asiolink::IntervalTimer::Mode& mode) |
65 | 51.0k | : interval_timer_(io_service), |
66 | 51.0k | user_callback_(user_callback), |
67 | 51.0k | interval_(interval), |
68 | 51.0k | scheduling_mode_(mode) { }; |
69 | | }; |
70 | | |
71 | | } |
72 | | |
73 | | namespace isc { |
74 | | namespace dhcp { |
75 | | |
76 | | /// @brief A type definition for the pointer to @c TimerInfo structure. |
77 | | typedef boost::shared_ptr<TimerInfo> TimerInfoPtr; |
78 | | |
79 | | /// @brief A type definition for the map holding timers configuration. |
80 | | typedef std::map<std::string, TimerInfoPtr> TimerInfoMap; |
81 | | |
82 | | /// @brief Implementation of the @c TimerMgr |
83 | | class TimerMgrImpl { |
84 | | public: |
85 | | |
86 | | /// @brief Constructor. |
87 | | TimerMgrImpl(); |
88 | | |
89 | | /// @brief Sets IO service to be used by the Timer Manager. |
90 | | /// |
91 | | /// @param io_service Pointer to the IO service. |
92 | | void setIOService(const IOServicePtr& io_service); |
93 | | |
94 | | /// @brief Registers new timer in the @c TimerMgr. |
95 | | /// |
96 | | /// @param timer_name Unique name for the timer. |
97 | | /// @param callback Pointer to the callback function to be invoked |
98 | | /// when the timer elapses, e.g. function processing expired leases |
99 | | /// in the DHCP server. |
100 | | /// @param interval Timer interval in milliseconds. |
101 | | /// @param scheduling_mode Scheduling mode of the timer as described in |
102 | | /// @c asiolink::IntervalTimer::Mode. |
103 | | /// |
104 | | /// @throw BadValue if the timer name is invalid or duplicate. |
105 | | void registerTimer(const std::string& timer_name, |
106 | | const asiolink::IntervalTimer::Callback& callback, |
107 | | const long interval, |
108 | | const asiolink::IntervalTimer::Mode& scheduling_mode); |
109 | | |
110 | | /// @brief Unregisters specified timer. |
111 | | /// |
112 | | /// This method cancels the timer if it is setup and removes the timer |
113 | | /// from the internal collection of timers. |
114 | | /// |
115 | | /// @param timer_name Name of the timer to be unregistered. |
116 | | /// |
117 | | /// @throw BadValue if the specified timer hasn't been registered. |
118 | | void unregisterTimer(const std::string& timer_name); |
119 | | |
120 | | /// @brief Unregisters all timers. |
121 | | /// |
122 | | /// This method must be explicitly called prior to termination of the |
123 | | /// process. |
124 | | void unregisterTimers(); |
125 | | |
126 | | /// @brief Checks if the timer with a specified name has been registered. |
127 | | /// |
128 | | /// @param timer_name Name of the timer. |
129 | | /// @return true if the timer with the specified name has been registered, |
130 | | /// false otherwise. |
131 | | bool isTimerRegistered(const std::string& timer_name); |
132 | | |
133 | | /// @brief Returns the number of registered timers. |
134 | | size_t timersCount() const; |
135 | | |
136 | | /// @brief Schedules the execution of the interval timer. |
137 | | /// |
138 | | /// This method schedules the timer, i.e. the callback will be executed |
139 | | /// after specified interval elapses. The interval has been specified |
140 | | /// during timer registration. Depending on the mode selected during the |
141 | | /// timer registration, the callback will be executed once after it has |
142 | | /// been scheduled or until it is cancelled. Though, in the former case |
143 | | /// the timer can be re-scheduled in the callback function. |
144 | | /// |
145 | | /// @param timer_name Unique timer name. |
146 | | /// |
147 | | /// @throw BadValue if the timer hasn't been registered. |
148 | | void setup(const std::string& timer_name); |
149 | | |
150 | | /// @brief Cancels the execution of the interval timer. |
151 | | /// |
152 | | /// @param timer_name Unique timer name. |
153 | | /// |
154 | | /// @throw BadValue if the timer hasn't been registered. |
155 | | void cancel(const std::string& timer_name); |
156 | | |
157 | | private: |
158 | | |
159 | | /// @name Internal methods called while holding the mutex in multi threading |
160 | | /// mode. |
161 | | |
162 | | /// @brief Registers new timer in the @c TimerMgr. |
163 | | /// |
164 | | /// @param timer_name Unique name for the timer. |
165 | | /// @param callback Pointer to the callback function to be invoked |
166 | | /// when the timer elapses, e.g. function processing expired leases |
167 | | /// in the DHCP server. |
168 | | /// @param interval Timer interval in milliseconds. |
169 | | /// @param scheduling_mode Scheduling mode of the timer as described in |
170 | | /// @c asiolink::IntervalTimer::Mode. |
171 | | /// |
172 | | /// @throw BadValue if the timer name is invalid or duplicate. |
173 | | void registerTimerInternal(const std::string& timer_name, |
174 | | const asiolink::IntervalTimer::Callback& callback, |
175 | | const long interval, |
176 | | const asiolink::IntervalTimer::Mode& scheduling_mode); |
177 | | |
178 | | /// @brief Unregisters specified timer. |
179 | | /// |
180 | | /// This method cancels the timer if it is setup and removes the timer |
181 | | /// from the internal collection of timers. |
182 | | /// |
183 | | /// @param timer_name Name of the timer to be unregistered. |
184 | | /// |
185 | | /// @throw BadValue if the specified timer hasn't been registered. |
186 | | void unregisterTimerInternal(const std::string& timer_name); |
187 | | |
188 | | /// @brief Unregisters all timers. |
189 | | /// |
190 | | /// This method must be explicitly called prior to termination of the |
191 | | /// process. |
192 | | void unregisterTimersInternal(); |
193 | | |
194 | | /// @brief Schedules the execution of the interval timer. |
195 | | /// |
196 | | /// This method schedules the timer, i.e. the callback will be executed |
197 | | /// after specified interval elapses. The interval has been specified |
198 | | /// during timer registration. Depending on the mode selected during the |
199 | | /// timer registration, the callback will be executed once after it has |
200 | | /// been scheduled or until it is cancelled. Though, in the former case |
201 | | /// the timer can be re-scheduled in the callback function. |
202 | | /// |
203 | | /// @param timer_name Unique timer name. |
204 | | /// |
205 | | /// @throw BadValue if the timer hasn't been registered. |
206 | | void setupInternal(const std::string& timer_name); |
207 | | |
208 | | /// @brief Cancels the execution of the interval timer. |
209 | | /// |
210 | | /// @param timer_name Unique timer name. |
211 | | /// |
212 | | /// @throw BadValue if the timer hasn't been registered. |
213 | | void cancelInternal(const std::string& timer_name); |
214 | | |
215 | | /// @brief Callback function to be executed for each interval timer when |
216 | | /// its scheduled interval elapses. |
217 | | /// |
218 | | /// @param timer_name Unique timer name. |
219 | | void timerCallback(const std::string& timer_name); |
220 | | |
221 | | /// @brief Pointer to the io service. |
222 | | asiolink::IOServicePtr io_service_; |
223 | | |
224 | | /// @brief Holds mapping of the timer name to timer instance and other |
225 | | /// parameters pertaining to the timer. |
226 | | TimerInfoMap registered_timers_; |
227 | | |
228 | | /// @brief The mutex to protect the timer manager. |
229 | | boost::scoped_ptr<std::mutex> mutex_; |
230 | | }; |
231 | | |
232 | 21 | TimerMgrImpl::TimerMgrImpl() : io_service_(new IOService()), |
233 | 21 | registered_timers_(), mutex_(new std::mutex) { |
234 | 21 | } |
235 | | |
236 | | void |
237 | 33.4k | TimerMgrImpl::setIOService(const IOServicePtr& io_service) { |
238 | 33.4k | if (!io_service) { |
239 | 0 | isc_throw(BadValue, "IO service object must not be null for TimerMgr"); |
240 | 0 | } |
241 | | |
242 | 33.4k | io_service_ = io_service; |
243 | 33.4k | } |
244 | | |
245 | | void |
246 | | TimerMgrImpl::registerTimer(const std::string& timer_name, |
247 | | const IntervalTimer::Callback& callback, |
248 | | const long interval, |
249 | 51.0k | const IntervalTimer::Mode& scheduling_mode) { |
250 | 51.0k | if (MultiThreadingMgr::instance().getMode()) { |
251 | 0 | std::lock_guard<std::mutex> lock(*mutex_); |
252 | 0 | registerTimerInternal(timer_name, callback, interval, scheduling_mode); |
253 | 51.0k | } else { |
254 | 51.0k | registerTimerInternal(timer_name, callback, interval, scheduling_mode); |
255 | 51.0k | } |
256 | 51.0k | } |
257 | | |
258 | | void |
259 | | TimerMgrImpl::registerTimerInternal(const std::string& timer_name, |
260 | | const IntervalTimer::Callback& callback, |
261 | | const long interval, |
262 | 51.0k | const IntervalTimer::Mode& scheduling_mode) { |
263 | | // Timer name must not be empty. |
264 | 51.0k | if (timer_name.empty()) { |
265 | 0 | isc_throw(BadValue, "registered timer name must not be empty"); |
266 | 0 | } |
267 | | |
268 | | // Must not register two timers under the same name. |
269 | 51.0k | if (registered_timers_.find(timer_name) != registered_timers_.end()) { |
270 | 0 | isc_throw(BadValue, "trying to register duplicate timer '" |
271 | 0 | << timer_name << "'"); |
272 | 0 | } |
273 | | |
274 | | // Create a structure holding the configuration for the timer. It will |
275 | | // create the instance if the IntervalTimer. It will also hold the |
276 | | // callback, interval and scheduling mode parameters. |
277 | 51.0k | TimerInfoPtr timer_info(new TimerInfo(io_service_, callback, |
278 | 51.0k | interval, scheduling_mode)); |
279 | | |
280 | | // Actually register the timer. |
281 | 51.0k | registered_timers_.insert(std::pair<std::string, TimerInfoPtr>(timer_name, |
282 | 51.0k | timer_info)); |
283 | 51.0k | } |
284 | | |
285 | | void |
286 | 12.7k | TimerMgrImpl::unregisterTimer(const std::string& timer_name) { |
287 | 12.7k | if (MultiThreadingMgr::instance().getMode()) { |
288 | 0 | std::lock_guard<std::mutex> lock(*mutex_); |
289 | 0 | unregisterTimerInternal(timer_name); |
290 | 12.7k | } else { |
291 | 12.7k | unregisterTimerInternal(timer_name); |
292 | 12.7k | } |
293 | 12.7k | } |
294 | | |
295 | | void |
296 | 51.0k | TimerMgrImpl::unregisterTimerInternal(const std::string& timer_name) { |
297 | | // Find the timer with specified name. |
298 | 51.0k | TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name); |
299 | | |
300 | | // Check if the timer has been registered. |
301 | 51.0k | if (timer_info_it == registered_timers_.end()) { |
302 | 0 | isc_throw(BadValue, "unable to unregister non existing timer '" |
303 | 0 | << timer_name << "'"); |
304 | 0 | } |
305 | | |
306 | | // Cancel any pending asynchronous operation and stop the timer. |
307 | 51.0k | cancelInternal(timer_name); |
308 | | |
309 | | // Remove the timer. |
310 | 51.0k | registered_timers_.erase(timer_info_it); |
311 | 51.0k | } |
312 | | |
313 | | void |
314 | 53.8k | TimerMgrImpl::unregisterTimers() { |
315 | 53.8k | if (MultiThreadingMgr::instance().getMode()) { |
316 | 0 | std::lock_guard<std::mutex> lock(*mutex_); |
317 | 0 | unregisterTimersInternal(); |
318 | 53.8k | } else { |
319 | 53.8k | unregisterTimersInternal(); |
320 | 53.8k | } |
321 | 53.8k | } |
322 | | |
323 | | void |
324 | 53.8k | TimerMgrImpl::unregisterTimersInternal() { |
325 | | // Copy the map holding timers configuration. This is required so as |
326 | | // we don't cut the branch which we're sitting on when we will be |
327 | | // erasing the timers. We're going to iterate over the register timers |
328 | | // and remove them with the call to unregisterTimer function. But this |
329 | | // function will remove them from the register_timers_ map. If we |
330 | | // didn't work on the copy here, our iterator would invalidate. The |
331 | | // TimerInfo structure is copyable and since it is using the shared |
332 | | // pointers the copy is not expensive. Also this function is called when |
333 | | // the process terminates so it is not critical for performance. |
334 | 53.8k | TimerInfoMap registered_timers_copy(registered_timers_); |
335 | | |
336 | | // Iterate over the existing timers and unregister them. |
337 | 53.8k | for (auto const& timer_info_it : registered_timers_copy) { |
338 | 38.2k | unregisterTimerInternal(timer_info_it.first); |
339 | 38.2k | } |
340 | 53.8k | } |
341 | | |
342 | | bool |
343 | 38.2k | TimerMgrImpl::isTimerRegistered(const std::string& timer_name) { |
344 | 38.2k | if (MultiThreadingMgr::instance().getMode()) { |
345 | 0 | std::lock_guard<std::mutex> lock(*mutex_); |
346 | 0 | return (registered_timers_.find(timer_name) != registered_timers_.end()); |
347 | 38.2k | } else { |
348 | 38.2k | return (registered_timers_.find(timer_name) != registered_timers_.end()); |
349 | 38.2k | } |
350 | 38.2k | } |
351 | | |
352 | | size_t |
353 | 0 | TimerMgrImpl::timersCount() const { |
354 | 0 | if (MultiThreadingMgr::instance().getMode()) { |
355 | 0 | std::lock_guard<std::mutex> lock(*mutex_); |
356 | 0 | return (registered_timers_.size()); |
357 | 0 | } else { |
358 | 0 | return (registered_timers_.size()); |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | | void |
363 | 51.1k | TimerMgrImpl::setup(const std::string& timer_name) { |
364 | 51.1k | if (MultiThreadingMgr::instance().getMode()) { |
365 | 152 | std::lock_guard<std::mutex> lock(*mutex_); |
366 | 152 | setupInternal(timer_name); |
367 | 51.0k | } else { |
368 | 51.0k | setupInternal(timer_name); |
369 | 51.0k | } |
370 | 51.1k | } |
371 | | |
372 | | void |
373 | 51.1k | TimerMgrImpl::setupInternal(const std::string& timer_name) { |
374 | | // Check if the specified timer exists. |
375 | 51.1k | TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name); |
376 | 51.1k | if (timer_info_it == registered_timers_.end()) { |
377 | 0 | isc_throw(BadValue, "unable to setup timer '" << timer_name << "': " |
378 | 0 | "no such timer registered"); |
379 | 0 | } |
380 | | |
381 | | // Schedule the execution of the timer using the parameters supplied |
382 | | // during the registration. |
383 | 51.1k | const TimerInfoPtr& timer_info = timer_info_it->second; |
384 | 51.1k | IntervalTimer::Callback cb = std::bind(&TimerMgrImpl::timerCallback, this, |
385 | 51.1k | timer_name); |
386 | 51.1k | timer_info->interval_timer_.setup(cb, timer_info->interval_, |
387 | 51.1k | timer_info->scheduling_mode_); |
388 | 51.1k | } |
389 | | |
390 | | void |
391 | 0 | TimerMgrImpl::cancel(const std::string& timer_name) { |
392 | 0 | if (MultiThreadingMgr::instance().getMode()) { |
393 | 0 | std::lock_guard<std::mutex> lock(*mutex_); |
394 | 0 | cancelInternal(timer_name); |
395 | 0 | } else { |
396 | 0 | cancelInternal(timer_name); |
397 | 0 | } |
398 | 0 | } |
399 | | |
400 | | void |
401 | 51.0k | TimerMgrImpl::cancelInternal(const std::string& timer_name) { |
402 | | // Find the timer of our interest. |
403 | 51.0k | TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name); |
404 | 51.0k | if (timer_info_it == registered_timers_.end()) { |
405 | 0 | isc_throw(BadValue, "unable to cancel timer '" << timer_name << "': " |
406 | 0 | "no such timer registered"); |
407 | 0 | } |
408 | | // Cancel the timer. |
409 | 51.0k | timer_info_it->second->interval_timer_.cancel(); |
410 | 51.0k | } |
411 | | |
412 | | void |
413 | 152 | TimerMgrImpl::timerCallback(const std::string& timer_name) { |
414 | | // Find the specified timer setup. |
415 | 152 | TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name); |
416 | 152 | if (timer_info_it != registered_timers_.end()) { |
417 | | |
418 | | // Running user-defined operation for the timer. Logging it |
419 | | // on the slightly lower debug level as there may be many |
420 | | // such traces. |
421 | 152 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, |
422 | 0 | DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION) |
423 | 0 | .arg(timer_info_it->first); |
424 | | |
425 | 152 | std::string error_string; |
426 | 152 | try { |
427 | 152 | timer_info_it->second->user_callback_(); |
428 | | |
429 | 152 | } catch (const std::exception& ex){ |
430 | 0 | error_string = ex.what(); |
431 | |
|
432 | 0 | } catch (...) { |
433 | 0 | error_string = "unknown reason"; |
434 | 0 | } |
435 | | |
436 | | // Exception was thrown. Log an error. |
437 | 152 | if (!error_string.empty()) { |
438 | 0 | LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_CALLBACK_FAILED) |
439 | 0 | .arg(timer_info_it->first) |
440 | 0 | .arg(error_string); |
441 | 0 | } |
442 | 152 | } |
443 | 152 | } |
444 | | |
445 | | const TimerMgrPtr& |
446 | 240k | TimerMgr::instance() { |
447 | 240k | static TimerMgrPtr timer_mgr(new TimerMgr()); |
448 | 240k | return (timer_mgr); |
449 | 240k | } |
450 | | |
451 | | TimerMgr::TimerMgr() |
452 | 21 | : impl_(new TimerMgrImpl()) { |
453 | 21 | } |
454 | | |
455 | 21 | TimerMgr::~TimerMgr() { |
456 | 21 | impl_->unregisterTimers(); |
457 | 21 | } |
458 | | |
459 | | void |
460 | | TimerMgr::registerTimer(const std::string& timer_name, |
461 | | const IntervalTimer::Callback& callback, |
462 | | const long interval, |
463 | 51.0k | const IntervalTimer::Mode& scheduling_mode) { |
464 | | |
465 | 51.0k | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, |
466 | 0 | DHCPSRV_TIMERMGR_REGISTER_TIMER) |
467 | 0 | .arg(timer_name) |
468 | 0 | .arg(interval); |
469 | | |
470 | 51.0k | impl_->registerTimer(timer_name, callback, interval, scheduling_mode); |
471 | 51.0k | } |
472 | | |
473 | | void |
474 | 12.7k | TimerMgr::unregisterTimer(const std::string& timer_name) { |
475 | | |
476 | 12.7k | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, |
477 | 0 | DHCPSRV_TIMERMGR_UNREGISTER_TIMER) |
478 | 0 | .arg(timer_name); |
479 | | |
480 | 12.7k | impl_->unregisterTimer(timer_name); |
481 | 12.7k | } |
482 | | |
483 | | void |
484 | 53.8k | TimerMgr::unregisterTimers() { |
485 | | |
486 | 53.8k | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, |
487 | 53.8k | DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS); |
488 | | |
489 | 53.8k | impl_->unregisterTimers(); |
490 | 53.8k | } |
491 | | |
492 | | bool |
493 | 38.2k | TimerMgr::isTimerRegistered(const std::string& timer_name) { |
494 | 38.2k | return (impl_->isTimerRegistered(timer_name)); |
495 | 38.2k | } |
496 | | |
497 | | size_t |
498 | 0 | TimerMgr::timersCount() const { |
499 | 0 | return (impl_->timersCount()); |
500 | 0 | } |
501 | | |
502 | | void |
503 | 51.1k | TimerMgr::setup(const std::string& timer_name) { |
504 | | |
505 | 51.1k | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, |
506 | 0 | DHCPSRV_TIMERMGR_START_TIMER) |
507 | 0 | .arg(timer_name); |
508 | | |
509 | 51.1k | impl_->setup(timer_name); |
510 | 51.1k | } |
511 | | |
512 | | void |
513 | 0 | TimerMgr::cancel(const std::string& timer_name) { |
514 | |
|
515 | 0 | LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, |
516 | 0 | DHCPSRV_TIMERMGR_STOP_TIMER) |
517 | 0 | .arg(timer_name); |
518 | |
|
519 | 0 | impl_->cancel(timer_name); |
520 | 0 | } |
521 | | |
522 | | void |
523 | 33.4k | TimerMgr::setIOService(const IOServicePtr& io_service) { |
524 | 33.4k | impl_->setIOService(io_service); |
525 | 33.4k | } |
526 | | |
527 | | } // end of namespace isc::dhcp |
528 | | } // end of namespace isc |