summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/timer_mgr.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/timer_mgr.cc')
-rw-r--r--src/lib/dhcpsrv/timer_mgr.cc531
1 files changed, 531 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/timer_mgr.cc b/src/lib/dhcpsrv/timer_mgr.cc
new file mode 100644
index 0000000..0f3b68f
--- /dev/null
+++ b/src/lib/dhcpsrv/timer_mgr.cc
@@ -0,0 +1,531 @@
+// Copyright (C) 2016-2021 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/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exception>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include <stddef.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// @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 and other parameters pertaining to it.
+struct TimerInfo {
+ /// @brief Instance of the interval timer.
+ asiolink::IntervalTimer interval_timer_;
+
+ /// @brief Holds the pointer to the callback supplied when registering
+ /// the timer.
+ asiolink::IntervalTimer::Callback user_callback_;
+
+ /// @brief Interval timer interval supplied during registration.
+ long interval_;
+
+ /// @brief Interval timer scheduling mode supplied during registration.
+ asiolink::IntervalTimer::Mode scheduling_mode_;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service to be used by the
+ /// interval timer created.
+ /// @param user_callback Pointer to the callback function supplied
+ /// during the timer registration.
+ /// @param interval Timer interval in milliseconds.
+ /// @param mode Interval timer scheduling mode.
+ TimerInfo(asiolink::IOService& io_service,
+ const asiolink::IntervalTimer::Callback& user_callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& mode)
+ : interval_timer_(io_service),
+ user_callback_(user_callback),
+ interval_(interval),
+ scheduling_mode_(mode) { };
+};
+
+}
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A type definition for the pointer to @c TimerInfo structure.
+typedef boost::shared_ptr<TimerInfo> TimerInfoPtr;
+
+/// @brief A type definition for the map holding timers configuration.
+typedef std::map<std::string, TimerInfoPtr> TimerInfoMap;
+
+/// @brief Implementation of the @c TimerMgr
+class TimerMgrImpl {
+public:
+
+ /// @brief Constructor.
+ TimerMgrImpl();
+
+ /// @brief Sets IO service to be used by the Timer Manager.
+ ///
+ /// @param io_service Pointer to the new IO service.
+ void setIOService(const IOServicePtr& io_service);
+
+ /// @brief Registers new timer in the @c TimerMgr.
+ ///
+ /// @param timer_name Unique name for the timer.
+ /// @param callback Pointer to the callback function to be invoked
+ /// when the timer elapses, e.g. function processing expired leases
+ /// in the DHCP server.
+ /// @param interval Timer interval in milliseconds.
+ /// @param scheduling_mode Scheduling mode of the timer as described in
+ /// @c asiolink::IntervalTimer::Mode.
+ ///
+ /// @throw BadValue if the timer name is invalid or duplicate.
+ void registerTimer(const std::string& timer_name,
+ const asiolink::IntervalTimer::Callback& callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& scheduling_mode);
+
+
+ /// @brief Unregisters specified timer.
+ ///
+ /// This method cancels the timer if it is setup and removes the timer
+ /// from the internal collection of timers.
+ ///
+ /// @param timer_name Name of the timer to be unregistered.
+ ///
+ /// @throw BadValue if the specified timer hasn't been registered.
+ void unregisterTimer(const std::string& timer_name);
+
+ /// @brief Unregisters all timers.
+ ///
+ /// This method must be explicitly called prior to termination of the
+ /// process.
+ void unregisterTimers();
+
+ /// @brief Checks if the timer with a specified name has been registered.
+ ///
+ /// @param timer_name Name of the timer.
+ /// @return true if the timer with the specified name has been registered,
+ /// false otherwise.
+ bool isTimerRegistered(const std::string& timer_name);
+
+ /// @brief Returns the number of registered timers.
+ size_t timersCount() const;
+
+ /// @brief Schedules the execution of the interval timer.
+ ///
+ /// This method schedules the timer, i.e. the callback will be executed
+ /// after specified interval elapses. The interval has been specified
+ /// during timer registration. Depending on the mode selected during the
+ /// timer registration, the callback will be executed once after it has
+ /// been scheduled or until it is cancelled. Though, in the former case
+ /// the timer can be re-scheduled in the callback function.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void setup(const std::string& timer_name);
+
+ /// @brief Cancels the execution of the interval timer.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void cancel(const std::string& timer_name);
+
+private:
+
+ /// @name Internal methods called while holding the mutex in multi threading
+ /// mode.
+
+ /// @brief Registers new timer in the @c TimerMgr.
+ ///
+ /// @param timer_name Unique name for the timer.
+ /// @param callback Pointer to the callback function to be invoked
+ /// when the timer elapses, e.g. function processing expired leases
+ /// in the DHCP server.
+ /// @param interval Timer interval in milliseconds.
+ /// @param scheduling_mode Scheduling mode of the timer as described in
+ /// @c asiolink::IntervalTimer::Mode.
+ ///
+ /// @throw BadValue if the timer name is invalid or duplicate.
+ void registerTimerInternal(const std::string& timer_name,
+ const asiolink::IntervalTimer::Callback& callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& scheduling_mode);
+
+
+ /// @brief Unregisters specified timer.
+ ///
+ /// This method cancels the timer if it is setup and removes the timer
+ /// from the internal collection of timers.
+ ///
+ /// @param timer_name Name of the timer to be unregistered.
+ ///
+ /// @throw BadValue if the specified timer hasn't been registered.
+ void unregisterTimerInternal(const std::string& timer_name);
+
+ /// @brief Unregisters all timers.
+ ///
+ /// This method must be explicitly called prior to termination of the
+ /// process.
+ void unregisterTimersInternal();
+
+ /// @brief Schedules the execution of the interval timer.
+ ///
+ /// This method schedules the timer, i.e. the callback will be executed
+ /// after specified interval elapses. The interval has been specified
+ /// during timer registration. Depending on the mode selected during the
+ /// timer registration, the callback will be executed once after it has
+ /// been scheduled or until it is cancelled. Though, in the former case
+ /// the timer can be re-scheduled in the callback function.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void setupInternal(const std::string& timer_name);
+
+ /// @brief Cancels the execution of the interval timer.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void cancelInternal(const std::string& timer_name);
+
+ /// @brief Callback function to be executed for each interval timer when
+ /// its scheduled interval elapses.
+ ///
+ /// @param timer_name Unique timer name.
+ void timerCallback(const std::string& timer_name);
+
+ /// @brief Pointer to the io service.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Holds mapping of the timer name to timer instance and other
+ /// parameters pertaining to the timer.
+ TimerInfoMap registered_timers_;
+
+ /// @brief The mutex to protect the timer manager.
+ boost::scoped_ptr<std::mutex> mutex_;
+};
+
+TimerMgrImpl::TimerMgrImpl() : io_service_(new IOService()),
+ registered_timers_(), mutex_(new std::mutex) {
+}
+
+void
+TimerMgrImpl::setIOService(const IOServicePtr& io_service) {
+ if (!io_service) {
+ isc_throw(BadValue, "IO service object must not be null for TimerMgr");
+ }
+
+ io_service_ = io_service;
+}
+
+void
+TimerMgrImpl::registerTimer(const std::string& timer_name,
+ const IntervalTimer::Callback& callback,
+ const long interval,
+ const IntervalTimer::Mode& scheduling_mode) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ registerTimerInternal(timer_name, callback, interval, scheduling_mode);
+ } else {
+ registerTimerInternal(timer_name, callback, interval, scheduling_mode);
+ }
+}
+
+void
+TimerMgrImpl::registerTimerInternal(const std::string& timer_name,
+ const IntervalTimer::Callback& callback,
+ const long interval,
+ const IntervalTimer::Mode& scheduling_mode) {
+ // Timer name must not be empty.
+ if (timer_name.empty()) {
+ isc_throw(BadValue, "registered timer name must not be empty");
+ }
+
+ // Must not register two timers under the same name.
+ if (registered_timers_.find(timer_name) != registered_timers_.end()) {
+ isc_throw(BadValue, "trying to register duplicate timer '"
+ << timer_name << "'");
+ }
+
+ // Create a structure holding the configuration for the timer. It will
+ // create the instance if the IntervalTimer. It will also hold the
+ // callback, interval and scheduling mode parameters.
+ TimerInfoPtr timer_info(new TimerInfo(*io_service_, callback,
+ interval, scheduling_mode));
+
+ // Actually register the timer.
+ registered_timers_.insert(std::pair<std::string, TimerInfoPtr>(timer_name,
+ timer_info));
+}
+
+void
+TimerMgrImpl::unregisterTimer(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ unregisterTimerInternal(timer_name);
+ } else {
+ unregisterTimerInternal(timer_name);
+ }
+}
+
+void
+TimerMgrImpl::unregisterTimerInternal(const std::string& timer_name) {
+ // Find the timer with specified name.
+ TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
+
+ // Check if the timer has been registered.
+ if (timer_info_it == registered_timers_.end()) {
+ isc_throw(BadValue, "unable to unregister non existing timer '"
+ << timer_name << "'");
+ }
+
+ // Cancel any pending asynchronous operation and stop the timer.
+ cancelInternal(timer_name);
+
+ // Remove the timer.
+ registered_timers_.erase(timer_info_it);
+}
+
+void
+TimerMgrImpl::unregisterTimers() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ unregisterTimersInternal();
+ } else {
+ unregisterTimersInternal();
+ }
+}
+
+void
+TimerMgrImpl::unregisterTimersInternal() {
+ // Copy the map holding timers configuration. This is required so as
+ // we don't cut the branch which we're sitting on when we will be
+ // erasing the timers. We're going to iterate over the register timers
+ // and remove them with the call to unregisterTimer function. But this
+ // function will remove them from the register_timers_ map. If we
+ // didn't work on the copy here, our iterator would invalidate. The
+ // TimerInfo structure is copyable and since it is using the shared
+ // pointers the copy is not expensive. Also this function is called when
+ // the process terminates so it is not critical for performance.
+ TimerInfoMap registered_timers_copy(registered_timers_);
+
+ // Iterate over the existing timers and unregister them.
+ for (TimerInfoMap::iterator timer_info_it = registered_timers_copy.begin();
+ timer_info_it != registered_timers_copy.end(); ++timer_info_it) {
+ unregisterTimerInternal(timer_info_it->first);
+ }
+}
+
+bool
+TimerMgrImpl::isTimerRegistered(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (registered_timers_.find(timer_name) != registered_timers_.end());
+ } else {
+ return (registered_timers_.find(timer_name) != registered_timers_.end());
+ }
+}
+
+size_t
+TimerMgrImpl::timersCount() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (registered_timers_.size());
+ } else {
+ return (registered_timers_.size());
+ }
+}
+
+void
+TimerMgrImpl::setup(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ setupInternal(timer_name);
+ } else {
+ setupInternal(timer_name);
+ }
+}
+
+void
+TimerMgrImpl::setupInternal(const std::string& timer_name) {
+ // Check if the specified timer exists.
+ 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");
+ }
+
+ // Schedule the execution of the timer using the parameters supplied
+ // during the registration.
+ const TimerInfoPtr& timer_info = timer_info_it->second;
+ IntervalTimer::Callback cb = std::bind(&TimerMgrImpl::timerCallback, this,
+ timer_name);
+ timer_info->interval_timer_.setup(cb, timer_info->interval_,
+ timer_info->scheduling_mode_);
+}
+
+void
+TimerMgrImpl::cancel(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ cancelInternal(timer_name);
+ } else {
+ cancelInternal(timer_name);
+ }
+}
+
+void
+TimerMgrImpl::cancelInternal(const std::string& timer_name) {
+ // Find the timer of our interest.
+ TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name);
+ if (timer_info_it == registered_timers_.end()) {
+ isc_throw(BadValue, "unable to cancel timer '" << timer_name << "': "
+ "no such timer registered");
+ }
+ // Cancel the timer.
+ timer_info_it->second->interval_timer_.cancel();
+}
+
+void
+TimerMgrImpl::timerCallback(const std::string& timer_name) {
+ // Find the specified timer setup.
+ TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
+ if (timer_info_it != registered_timers_.end()) {
+
+ // Running user-defined operation for the timer. Logging it
+ // on the slightly lower debug level as there may be many
+ // such traces.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION)
+ .arg(timer_info_it->first);
+
+ std::string error_string;
+ try {
+ timer_info_it->second->user_callback_();
+
+ } catch (const std::exception& ex){
+ error_string = ex.what();
+
+ } catch (...) {
+ error_string = "unknown reason";
+ }
+
+ // Exception was thrown. Log an error.
+ if (!error_string.empty()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_CALLBACK_FAILED)
+ .arg(timer_info_it->first)
+ .arg(error_string);
+ }
+ }
+}
+
+const TimerMgrPtr&
+TimerMgr::instance() {
+ static TimerMgrPtr timer_mgr(new TimerMgr());
+ return (timer_mgr);
+}
+
+TimerMgr::TimerMgr()
+ : impl_(new TimerMgrImpl()) {
+}
+
+TimerMgr::~TimerMgr() {
+ impl_->unregisterTimers();
+}
+
+void
+TimerMgr::registerTimer(const std::string& timer_name,
+ const IntervalTimer::Callback& callback,
+ const long interval,
+ const IntervalTimer::Mode& scheduling_mode) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_REGISTER_TIMER)
+ .arg(timer_name)
+ .arg(interval);
+
+ impl_->registerTimer(timer_name, callback, interval, scheduling_mode);
+}
+
+void
+TimerMgr::unregisterTimer(const std::string& timer_name) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_UNREGISTER_TIMER)
+ .arg(timer_name);
+
+ impl_->unregisterTimer(timer_name);
+}
+
+void
+TimerMgr::unregisterTimers() {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS);
+
+ impl_->unregisterTimers();
+}
+
+bool
+TimerMgr::isTimerRegistered(const std::string& timer_name) {
+ return (impl_->isTimerRegistered(timer_name));
+}
+
+size_t
+TimerMgr::timersCount() const {
+ return (impl_->timersCount());
+}
+
+void
+TimerMgr::setup(const std::string& timer_name) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_START_TIMER)
+ .arg(timer_name);
+
+ impl_->setup(timer_name);
+}
+
+void
+TimerMgr::cancel(const std::string& timer_name) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_STOP_TIMER)
+ .arg(timer_name);
+
+ impl_->cancel(timer_name);
+}
+
+void
+TimerMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->setIOService(io_service);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc