|
@@ -18,6 +18,9 @@
|
|
|
#include <dhcpsrv/dhcpsrv_log.h>
|
|
|
#include <dhcpsrv/timer_mgr.h>
|
|
|
#include <exceptions/exceptions.h>
|
|
|
+#include <util/threads/sync.h>
|
|
|
+#include <util/threads/thread.h>
|
|
|
+#include <util/watch_socket.h>
|
|
|
#include <boost/bind.hpp>
|
|
|
#include <utility>
|
|
|
|
|
@@ -26,38 +29,294 @@ using namespace isc::asiolink;
|
|
|
using namespace isc::util;
|
|
|
using namespace isc::util::thread;
|
|
|
|
|
|
+namespace {
|
|
|
+
|
|
|
+/// @brief Simple RAII object setting value to true while in scope.
|
|
|
+///
|
|
|
+/// This class is useful to temporarly set the value to true and
|
|
|
+/// automatically reset it to false when the object is destroyed
|
|
|
+/// as a result of return or exception.
|
|
|
+class ScopedTrue {
|
|
|
+public:
|
|
|
+
|
|
|
+ /// @brief Constructor.
|
|
|
+ ///
|
|
|
+ /// Sets the boolean value to true.
|
|
|
+ ///
|
|
|
+ /// @param value reference to the value to be set to true.
|
|
|
+ ScopedTrue(bool& value, Mutex& mutex)
|
|
|
+ : value_(value), mutex_(mutex) {
|
|
|
+ Mutex::Locker lock(mutex_);
|
|
|
+ value_ = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// @brief Destructor.
|
|
|
+ ///
|
|
|
+ /// Sets the value to false.
|
|
|
+ ~ScopedTrue() {
|
|
|
+ Mutex::Locker lock(mutex_);
|
|
|
+ value_ = false;
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+
|
|
|
+ /// @brief Reference to the controlled value.
|
|
|
+ bool& value_;
|
|
|
+
|
|
|
+ /// @brief Mutex to be used to lock while performing write
|
|
|
+ /// operations.
|
|
|
+ Mutex& mutex_;
|
|
|
+};
|
|
|
+
|
|
|
+/// @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 watch socket.
|
|
|
+ util::WatchSocket watch_socket_;
|
|
|
+
|
|
|
+ /// @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)
|
|
|
+ : watch_socket_(),
|
|
|
+ interval_timer_(io_service),
|
|
|
+ user_callback_(user_callback),
|
|
|
+ interval_(interval),
|
|
|
+ scheduling_mode_(mode) { };
|
|
|
+};
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
namespace isc {
|
|
|
namespace dhcp {
|
|
|
|
|
|
-TimerMgr&
|
|
|
-TimerMgr::instance() {
|
|
|
- static TimerMgr timer_mgr;
|
|
|
- return (timer_mgr);
|
|
|
-}
|
|
|
+/// @brief A type definition for the pointer to @c TimerInfo structure.
|
|
|
+typedef boost::shared_ptr<TimerInfo> TimerInfoPtr;
|
|
|
|
|
|
-TimerMgr::TimerMgr()
|
|
|
- : io_service_(new IOService()), thread_(),
|
|
|
- registered_timers_() {
|
|
|
-}
|
|
|
+/// @brief A type definition for the map holding timers configuration.
|
|
|
+typedef std::map<std::string, TimerInfoPtr> TimerInfoMap;
|
|
|
|
|
|
-TimerMgr::~TimerMgr() {
|
|
|
- // Stop the thread, but do not unregister any timers. Unregistering
|
|
|
- // the timers could cause static deinitialization fiasco between the
|
|
|
- // TimerMgr and IfaceMgr. By now, the caller should have unregistered
|
|
|
- // the timers.
|
|
|
- stopThread();
|
|
|
+
|
|
|
+/// @brief Implementation of the @c TimerMgr
|
|
|
+class TimerMgrImpl {
|
|
|
+public:
|
|
|
+
|
|
|
+ /// @brief Constructor.
|
|
|
+ TimerMgrImpl();
|
|
|
+
|
|
|
+ /// @brief Returns a reference to IO service used by the @c TimerMgr.
|
|
|
+ asiolink::IOService& getIOService() const {
|
|
|
+ return (*io_service_);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// @brief Registers new timers 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.
|
|
|
+ /// @throw InvalidOperation if the worker thread is running.
|
|
|
+ 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. It removes the external
|
|
|
+ /// socket from the @c IfaceMgr and closes it. It finally 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 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.
|
|
|
+ ///
|
|
|
+ /// This method has no effect if the timer hasn't been scheduled with
|
|
|
+ /// the @c TimerMgr::setup method.
|
|
|
+ ///
|
|
|
+ /// @param timer_name Unique timer name.
|
|
|
+ ///
|
|
|
+ /// @throw BadValue if the timer hasn't been registered.
|
|
|
+ void cancel(const std::string& timer_name);
|
|
|
+
|
|
|
+ /// @brief Checks if the thread is running.
|
|
|
+ ///
|
|
|
+ /// @return true if the thread is running.
|
|
|
+ bool threadRunning() const;
|
|
|
+
|
|
|
+ /// @brief Starts thread.
|
|
|
+ void createThread();
|
|
|
+
|
|
|
+ /// @brief Stops thread gracefully.
|
|
|
+ ///
|
|
|
+ /// This methods unblocks worker thread if it is blocked waiting for
|
|
|
+ /// any handlers and stops it. Outstanding handlers are later executed
|
|
|
+ /// in the main thread and all watch sockets are cleared.
|
|
|
+ ///
|
|
|
+ /// @param run_pending_callbacks Indicates if the pending callbacks
|
|
|
+ /// should be executed (if true).
|
|
|
+ void controlledStopThread(const bool run_pending_callbacks);
|
|
|
+
|
|
|
+private:
|
|
|
+
|
|
|
+ /// @name Internal callbacks.
|
|
|
+ //@{
|
|
|
+ ///
|
|
|
+ /// @brief Callback function to be executed for each interval timer when
|
|
|
+ /// its scheduled interval elapses.
|
|
|
+ ///
|
|
|
+ /// This method marks the @c util::Watch socket associated with the
|
|
|
+ /// timer as ready (writes data to a pipe). This method will block until
|
|
|
+ /// @c TimerMgr::ifaceMgrCallback is executed from the main thread by the
|
|
|
+ /// @c IfaceMgr.
|
|
|
+ ///
|
|
|
+ /// @param timer_name Unique timer name to be passed to the callback.
|
|
|
+ void timerCallback(const std::string& timer_name);
|
|
|
+
|
|
|
+ /// @brief Callback function installed on the @c IfaceMgr and associated
|
|
|
+ /// with the particular timer.
|
|
|
+ ///
|
|
|
+ /// This callback function is executed by the @c IfaceMgr when the data
|
|
|
+ /// over the specific @c util::WatchSocket is received. This method clears
|
|
|
+ /// the socket (reads the data from the pipe) and executes the callback
|
|
|
+ /// supplied when the timer was registered.
|
|
|
+ ///
|
|
|
+ /// @param timer_name Unique timer name.
|
|
|
+ void ifaceMgrCallback(const std::string& timer_name);
|
|
|
+
|
|
|
+ //@}
|
|
|
+
|
|
|
+ /// @name Methods to handle ready sockets.
|
|
|
+ //@{
|
|
|
+ ///
|
|
|
+ /// @brief Clear ready sockets and optionally run callbacks.
|
|
|
+ ///
|
|
|
+ /// This method is called by the @c TimerMgr::stopThread method
|
|
|
+ /// to clear watch sockets which may be marked as ready. It will
|
|
|
+ /// also optionally run callbacks installed for the timers which
|
|
|
+ /// marked sockets as ready.
|
|
|
+ ///
|
|
|
+ /// @param run_pending_callbacks Indicates if the callbacks should
|
|
|
+ /// be executed for the sockets being cleared (if true).
|
|
|
+ void clearReadySockets(const bool run_pending_callbacks);
|
|
|
+
|
|
|
+ /// @brief Clears a socket and optionally runs a callback.
|
|
|
+ ///
|
|
|
+ /// This method clears the ready socket pointed to by the specified
|
|
|
+ /// iterator. If the @c run_callback is set, the callback will
|
|
|
+ /// also be executed.
|
|
|
+ ///
|
|
|
+ /// @param timer_info_iterator Iterator pointing to the timer
|
|
|
+ /// configuration structure.
|
|
|
+ /// @param run_callback Boolean value indicating if the callback
|
|
|
+ /// should be executed for the socket being cleared (if true).
|
|
|
+ ///
|
|
|
+ /// @tparam Iterator Iterator pointing to the timer configuration
|
|
|
+ /// structure.
|
|
|
+ template<typename Iterator>
|
|
|
+ void handleReadySocket(Iterator timer_info_iterator, const bool run_callback);
|
|
|
+
|
|
|
+ //@}
|
|
|
+
|
|
|
+ /// @brief Blocking wait for the socket to be cleared.
|
|
|
+ void waitForSocketClearing(WatchSocket& watch_socket);
|
|
|
+
|
|
|
+ /// @brief Signals that a watch socket has been cleared.
|
|
|
+ void signalSocketClearing();
|
|
|
+
|
|
|
+ /// @brief Pointer to the io service.
|
|
|
+ asiolink::IOServicePtr io_service_;
|
|
|
+
|
|
|
+ /// @brief Pointer to the worker thread.
|
|
|
+ ///
|
|
|
+ /// This is initially set to NULL until the thread is started using the
|
|
|
+ /// @c TimerMgr::startThread. The @c TimerMgr::stopThread sets it back
|
|
|
+ /// to NULL.
|
|
|
+ boost::shared_ptr<util::thread::Thread> thread_;
|
|
|
+
|
|
|
+ /// @brief Mutex used to synchronize main thread and the worker thread.
|
|
|
+ util::thread::Mutex mutex_;
|
|
|
+
|
|
|
+ /// @brief Conditional variable used to synchronize main thread and
|
|
|
+ /// worker thread.
|
|
|
+ util::thread::CondVar cond_var_;
|
|
|
+
|
|
|
+ /// @brief Boolean value indicating if the thread is being stopped.
|
|
|
+ bool stopping_;
|
|
|
+
|
|
|
+ /// @brief Holds mapping of the timer name to the watch socket, timer
|
|
|
+ /// instance and other parameters pertaining to the timer.
|
|
|
+ ///
|
|
|
+ /// Each registered timer has a unique name which is used as a key to
|
|
|
+ /// the map. The timer is associated with an instance of the @c WatchSocket
|
|
|
+ /// which is marked ready when the interval for the particular elapses.
|
|
|
+ TimerInfoMap registered_timers_;
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+TimerMgrImpl::TimerMgrImpl() :
|
|
|
+ io_service_(new IOService()), thread_(), mutex_(), cond_var_(),
|
|
|
+ stopping_(false), registered_timers_() {
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
+TimerMgrImpl::registerTimer(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()) {
|
|
@@ -90,7 +349,7 @@ TimerMgr::registerTimer(const std::string& timer_name,
|
|
|
// this may fail is when the socket failed to open which would have caused
|
|
|
// an exception in the previous call. So we should be safe here.
|
|
|
IfaceMgr::instance().addExternalSocket(timer_info->watch_socket_.getSelectFd(),
|
|
|
- boost::bind(&TimerMgr::ifaceMgrCallback,
|
|
|
+ boost::bind(&TimerMgrImpl::ifaceMgrCallback,
|
|
|
this, timer_name));
|
|
|
|
|
|
// Actually register the timer.
|
|
@@ -99,11 +358,7 @@ TimerMgr::registerTimer(const std::string& timer_name,
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::unregisterTimer(const std::string& timer_name) {
|
|
|
-
|
|
|
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
- DHCPSRV_TIMERMGR_UNREGISTER_TIMER)
|
|
|
- .arg(timer_name);
|
|
|
+TimerMgrImpl::unregisterTimer(const std::string& timer_name) {
|
|
|
|
|
|
if (thread_) {
|
|
|
isc_throw(InvalidOperation, "unable to unregister timer "
|
|
@@ -132,11 +387,7 @@ TimerMgr::unregisterTimer(const std::string& timer_name) {
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::unregisterTimers() {
|
|
|
-
|
|
|
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
- DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS);
|
|
|
-
|
|
|
+TimerMgrImpl::unregisterTimers() {
|
|
|
// 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
|
|
@@ -156,11 +407,7 @@ TimerMgr::unregisterTimers() {
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::setup(const std::string& timer_name) {
|
|
|
-
|
|
|
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
- DHCPSRV_TIMERMGR_START_TIMER)
|
|
|
- .arg(timer_name);
|
|
|
+TimerMgrImpl::setup(const std::string& timer_name) {
|
|
|
|
|
|
// Check if the specified timer exists.
|
|
|
TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name);
|
|
@@ -172,17 +419,14 @@ TimerMgr::setup(const std::string& timer_name) {
|
|
|
// Schedule the execution of the timer using the parameters supplied
|
|
|
// during the registration.
|
|
|
const TimerInfoPtr& timer_info = timer_info_it->second;
|
|
|
- timer_info->interval_timer_.setup(boost::bind(&TimerMgr::timerCallback, this, timer_name),
|
|
|
- timer_info->interval_,
|
|
|
+ IntervalTimer::Callback cb = boost::bind(&TimerMgrImpl::timerCallback, this,
|
|
|
+ timer_name);
|
|
|
+ timer_info->interval_timer_.setup(cb, timer_info->interval_,
|
|
|
timer_info->scheduling_mode_);
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::cancel(const std::string& timer_name) {
|
|
|
-
|
|
|
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
- DHCPSRV_TIMERMGR_STOP_TIMER)
|
|
|
- .arg(timer_name);
|
|
|
+TimerMgrImpl::cancel(const std::string& timer_name) {
|
|
|
|
|
|
// Find the timer of our interest.
|
|
|
TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name);
|
|
@@ -196,54 +440,45 @@ TimerMgr::cancel(const std::string& timer_name) {
|
|
|
timer_info_it->second->watch_socket_.clearReady();
|
|
|
}
|
|
|
|
|
|
-void
|
|
|
-TimerMgr::startThread() {
|
|
|
- // Do not start the thread if the thread is already there.
|
|
|
- if (!thread_) {
|
|
|
- // Only log it if we really start the thread.
|
|
|
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
- DHCPSRV_TIMERMGR_START_THREAD);
|
|
|
|
|
|
- // The thread will simply run IOService::run(), which is a blocking call
|
|
|
- // to keep running handlers for all timers according to how they have
|
|
|
- // been scheduled.
|
|
|
- thread_.reset(new Thread(boost::bind(&IOService::run, &getIOService())));
|
|
|
- }
|
|
|
+bool
|
|
|
+TimerMgrImpl::threadRunning() const {
|
|
|
+ return (static_cast<bool>(thread_));
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::stopThread(const bool run_pending_callbacks) {
|
|
|
- // If thread is not running, this is no-op.
|
|
|
- if (thread_) {
|
|
|
- // Only log it if we really have something to stop.
|
|
|
- LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
- DHCPSRV_TIMERMGR_STOP_THREAD);
|
|
|
-
|
|
|
- // Stop the IO Service. This will break the IOService::run executed in the
|
|
|
- // worker thread. The thread will now terminate.
|
|
|
- getIOService().stop();
|
|
|
- // Some of the watch sockets may be already marked as ready and
|
|
|
- // have some pending callbacks to be executed. If the caller
|
|
|
- // wants us to run the callbacks we clear the sockets and run
|
|
|
- // them. If pending callbacks shouldn't be executed, this will
|
|
|
- // only clear the sockets (which should be substantially faster).
|
|
|
- clearReadySockets(run_pending_callbacks);
|
|
|
- // Wait for the thread to terminate.
|
|
|
- thread_->wait();
|
|
|
- // Set the thread pointer to NULL to indicate that the thread is not running.
|
|
|
- thread_.reset();
|
|
|
- // IO Service has to be reset so as we can call run() on it again if we
|
|
|
- // desire (using the startThread()).
|
|
|
- getIOService().get_io_service().reset();
|
|
|
- }
|
|
|
+TimerMgrImpl::createThread() {
|
|
|
+ thread_.reset(new Thread(boost::bind(&IOService::run, &getIOService())));
|
|
|
}
|
|
|
-IOService&
|
|
|
-TimerMgr::getIOService() const {
|
|
|
- return (*io_service_);
|
|
|
+
|
|
|
+void
|
|
|
+TimerMgrImpl::controlledStopThread(const bool run_pending_callbacks) {
|
|
|
+ // Set the stopping flag to true while we're stopping. This will be
|
|
|
+ // automatically reset to false when the function exits or exception
|
|
|
+ // is thrown.
|
|
|
+ ScopedTrue scoped_true(stopping_, mutex_);
|
|
|
+
|
|
|
+ // Stop the IO Service. This will break the IOService::run executed in the
|
|
|
+ // worker thread. The thread will now terminate.
|
|
|
+ getIOService().stop();
|
|
|
+
|
|
|
+ // Some of the watch sockets may be already marked as ready and
|
|
|
+ // have some pending callbacks to be executed. If the caller
|
|
|
+ // wants us to run the callbacks we clear the sockets and run
|
|
|
+ // them. If pending callbacks shouldn't be executed, this will
|
|
|
+ // only clear the sockets (which should be substantially faster).
|
|
|
+ clearReadySockets(run_pending_callbacks);
|
|
|
+ // Wait for the thread to terminate.
|
|
|
+ thread_->wait();
|
|
|
+ // Set the thread pointer to NULL to indicate that the thread is not running.
|
|
|
+ thread_.reset();
|
|
|
+ // IO Service has to be reset so as we can call run() on it again if we
|
|
|
+ // desire (using the startThread()).
|
|
|
+ getIOService().get_io_service().reset();
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::timerCallback(const std::string& timer_name) {
|
|
|
+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()) {
|
|
@@ -251,12 +486,33 @@ TimerMgr::timerCallback(const std::string& timer_name) {
|
|
|
// interrupt the execution of the select() function. This is
|
|
|
// executed from the worker thread.
|
|
|
const TimerInfoPtr& timer_info = timer_info_it->second;
|
|
|
- timer_info->watch_socket_.markReady();
|
|
|
+
|
|
|
+ // This function is called from the worker thread and we don't want
|
|
|
+ // the worker thread to get exceptions out of here. It is unlikely
|
|
|
+ // that markReady() would cause any problems but theoretically
|
|
|
+ // possible. Hence, we rather log an error and leave.
|
|
|
+ try {
|
|
|
+ timer_info->watch_socket_.markReady();
|
|
|
+
|
|
|
+ } catch (const std::exception& ex) {
|
|
|
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_SOCKET_MARK_FAILED)
|
|
|
+ .arg(timer_name)
|
|
|
+ .arg(ex.what());
|
|
|
+
|
|
|
+ // Do not wait for clearing the socket because we were unable
|
|
|
+ // to mark it ready anyway.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Blocking wait for the socket to be cleared on the other
|
|
|
+ // end. This may be interrupted both if the socket is cleared
|
|
|
+ // or if the stopThread() has been called on the TimerMgr.
|
|
|
+ waitForSocketClearing(timer_info->watch_socket_);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::ifaceMgrCallback(const std::string& timer_name) {
|
|
|
+TimerMgrImpl::ifaceMgrCallback(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()) {
|
|
@@ -272,7 +528,7 @@ TimerMgr::ifaceMgrCallback(const std::string& timer_name) {
|
|
|
}
|
|
|
|
|
|
void
|
|
|
-TimerMgr::clearReadySockets(const bool run_pending_callbacks) {
|
|
|
+TimerMgrImpl::clearReadySockets(const bool run_pending_callbacks) {
|
|
|
for (TimerInfoMap::iterator timer_info_it = registered_timers_.begin();
|
|
|
timer_info_it != registered_timers_.end(); ++timer_info_it) {
|
|
|
handleReadySocket(timer_info_it, run_pending_callbacks);
|
|
@@ -281,10 +537,8 @@ TimerMgr::clearReadySockets(const bool run_pending_callbacks) {
|
|
|
|
|
|
template<typename Iterator>
|
|
|
void
|
|
|
-TimerMgr::handleReadySocket(Iterator timer_info_iterator,
|
|
|
+TimerMgrImpl::handleReadySocket(Iterator timer_info_iterator,
|
|
|
const bool run_callback) {
|
|
|
- timer_info_iterator->second->watch_socket_.clearReady();
|
|
|
-
|
|
|
if (run_callback) {
|
|
|
// Running user-defined operation for the timer. Logging it
|
|
|
// on the slightly lower debug level as there may be many
|
|
@@ -292,9 +546,164 @@ TimerMgr::handleReadySocket(Iterator timer_info_iterator,
|
|
|
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
|
|
|
DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION)
|
|
|
.arg(timer_info_iterator->first);
|
|
|
- timer_info_iterator->second->user_callback_();
|
|
|
+
|
|
|
+ std::string error_string;
|
|
|
+ try {
|
|
|
+ timer_info_iterator->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_iterator->first)
|
|
|
+ .arg(error_string);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // This shouldn't really fail, but if it does we want to log an
|
|
|
+ // error and make sure that the thread is not stuck waiting for
|
|
|
+ // the socket to clear.
|
|
|
+ timer_info_iterator->second->watch_socket_.clearReady();
|
|
|
+
|
|
|
+ } catch (const std::exception& ex) {
|
|
|
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_SOCKET_CLEAR_FAILED)
|
|
|
+ .arg(timer_info_iterator->first)
|
|
|
+ .arg(ex.what());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Whether it succeeded or not, clear the socket to make sure that
|
|
|
+ // the worker thread is not stuck waiting for the main thread.
|
|
|
+ signalSocketClearing();
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+TimerMgrImpl::waitForSocketClearing(WatchSocket& watch_socket) {
|
|
|
+ // Waiting for the specific socket to be cleared.
|
|
|
+ while (watch_socket.isReady()) {
|
|
|
+ Mutex::Locker lock(mutex_);
|
|
|
+ // If stopThread has been called there is no sense to further
|
|
|
+ // wait for the socket clearing. Leave from here to unblock the
|
|
|
+ // worker thread.
|
|
|
+ if (stopping_) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ cond_var_.wait(mutex_);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+TimerMgrImpl::signalSocketClearing() {
|
|
|
+ cond_var_.signal();
|
|
|
+}
|
|
|
+
|
|
|
+TimerMgr&
|
|
|
+TimerMgr::instance() {
|
|
|
+ static TimerMgr timer_mgr;
|
|
|
+ return (timer_mgr);
|
|
|
+}
|
|
|
+
|
|
|
+TimerMgr::TimerMgr()
|
|
|
+ : impl_(new TimerMgrImpl()) {
|
|
|
+}
|
|
|
+
|
|
|
+TimerMgr::~TimerMgr() {
|
|
|
+ // Stop the thread, but do not unregister any timers. Unregistering
|
|
|
+ // the timers could cause static deinitialization fiasco between the
|
|
|
+ // TimerMgr and IfaceMgr. By now, the caller should have unregistered
|
|
|
+ // the timers.
|
|
|
+ stopThread();
|
|
|
+
|
|
|
+ delete impl_;
|
|
|
+}
|
|
|
+
|
|
|
+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();
|
|
|
+}
|
|
|
+
|
|
|
+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::startThread() {
|
|
|
+ // Do not start the thread if the thread is already there.
|
|
|
+ if (!impl_->threadRunning()) {
|
|
|
+ // Only log it if we really start the thread.
|
|
|
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
+ DHCPSRV_TIMERMGR_START_THREAD);
|
|
|
+
|
|
|
+ impl_->createThread();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void
|
|
|
+TimerMgr::stopThread(const bool run_pending_callbacks) {
|
|
|
+ // If thread is not running, this is no-op.
|
|
|
+ if (impl_->threadRunning()) {
|
|
|
+ // Only log it if we really have something to stop.
|
|
|
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
|
|
|
+ DHCPSRV_TIMERMGR_STOP_THREAD);
|
|
|
+
|
|
|
+ impl_->controlledStopThread(run_pending_callbacks);
|
|
|
}
|
|
|
}
|
|
|
+IOService&
|
|
|
+TimerMgr::getIOService() const {
|
|
|
+ return (impl_->getIOService());
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
} // end of namespace isc::dhcp
|
|
|
} // end of namespace isc
|