Coverage Report

Created: 2025-12-08 07:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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