Browse Source

[master] Merge branch 'trac3970'

Marcin Siodelski 9 years ago
parent
commit
bc85030553

+ 0 - 1
src/lib/dhcp_ddns/Makefile.am

@@ -34,7 +34,6 @@ libkea_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
 libkea_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
 libkea_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
 libkea_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
 libkea_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
 libkea_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
 libkea_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
-libkea_dhcp_ddns_la_SOURCES += watch_socket.cc watch_socket.h
 
 
 nodist_libkea_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
 nodist_libkea_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
 
 

+ 4 - 10
src/lib/dhcp_ddns/dhcp_ddns_messages.mes

@@ -89,14 +89,8 @@ caught in the application's send completion handler.  This is a programmatic
 error that needs to be reported.  Dependent upon the nature of the error the
 error that needs to be reported.  Dependent upon the nature of the error the
 client may or may not continue operating normally.
 client may or may not continue operating normally.
 
 
-% DHCP_DDNS_WATCH_SINK_CLOSE_ERROR Sink-side watch socket failed to close: %1
+% DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR watch socket failed to close: %1
 This is an error message that indicates the application was unable to close
 This is an error message that indicates the application was unable to close
-the inbound side of a NCR sender's watch socket.  While technically possible
-this error is highly unlikely to occur and should not impair the application's
-ability to process requests.
-
-% DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR Source-side watch socket failed to close: %1
-This is an error message that indicates the application was unable to close
-the outbound side of a NCR sender's watch socket.  While technically possible
-this error is highly unlikely to occur and should not impair the application's
-ability to process requests.
+the inbound or outbound side of a NCR sender's watch socket. While technically
+possible the error is highly unlikely to occur and should not impair the
+application's ability to process requests.

+ 14 - 2
src/lib/dhcp_ddns/ncr_udp.cc

@@ -260,7 +260,8 @@ NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
 
 
     send_callback_->setDataSource(server_endpoint_);
     send_callback_->setDataSource(server_endpoint_);
 
 
-    watch_socket_.reset(new WatchSocket());
+    closeWatchSocket();
+    watch_socket_.reset(new util::WatchSocket());
 }
 }
 
 
 void
 void
@@ -288,6 +289,7 @@ NameChangeUDPSender::close() {
 
 
     socket_.reset();
     socket_.reset();
 
 
+    closeWatchSocket();
     watch_socket_.reset();
     watch_socket_.reset();
 }
 }
 
 
@@ -372,7 +374,17 @@ NameChangeUDPSender::ioReady() {
     return (false);
     return (false);
 }
 }
 
 
-
+void
+NameChangeUDPSender::closeWatchSocket() {
+    if (watch_socket_) {
+        std::string error_string;
+        watch_socket_->closeSocket(error_string);
+        if (!error_string.empty()) {
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR)
+                .arg(error_string);
+        }
+    }
+}
 
 
 }; // end of isc::dhcp_ddns namespace
 }; // end of isc::dhcp_ddns namespace
 }; // end of isc namespace
 }; // end of isc namespace

+ 10 - 3
src/lib/dhcp_ddns/ncr_udp.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -112,8 +112,8 @@
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_endpoint.h>
 #include <asiolink/udp_socket.h>
 #include <asiolink/udp_socket.h>
 #include <dhcp_ddns/ncr_io.h>
 #include <dhcp_ddns/ncr_io.h>
-#include <dhcp_ddns/watch_socket.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
+#include <util/watch_socket.h>
 
 
 #include <boost/shared_array.hpp>
 #include <boost/shared_array.hpp>
 
 
@@ -548,6 +548,13 @@ public:
     virtual bool ioReady();
     virtual bool ioReady();
 
 
 private:
 private:
+
+    /// @brief Closes watch socket if the socket is open.
+    ///
+    /// This method closes watch socket if its instance exists and if the
+    /// socket is open. An error message is logged when this operation fails.
+    void closeWatchSocket();
+
     /// @brief IP address from which to send.
     /// @brief IP address from which to send.
     isc::asiolink::IOAddress ip_address_;
     isc::asiolink::IOAddress ip_address_;
 
 
@@ -579,7 +586,7 @@ private:
     bool reuse_address_;
     bool reuse_address_;
 
 
     /// @brief Pointer to WatchSocket instance supplying the "select-fd".
     /// @brief Pointer to WatchSocket instance supplying the "select-fd".
-    WatchSocketPtr watch_socket_;
+    util::WatchSocketPtr watch_socket_;
 };
 };
 
 
 } // namespace isc::dhcp_ddns
 } // namespace isc::dhcp_ddns

+ 0 - 1
src/lib/dhcp_ddns/tests/Makefile.am

@@ -30,7 +30,6 @@ libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
 libdhcp_ddns_unittests_SOURCES += test_utils.cc test_utils.h
 libdhcp_ddns_unittests_SOURCES += test_utils.cc test_utils.h
-libdhcp_ddns_unittests_SOURCES += watch_socket_unittests.cc
 
 
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
 

+ 3 - 3
src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc

@@ -362,7 +362,7 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
     int select_fd = sender.getSelectFd();
     int select_fd = sender.getSelectFd();
 
 
     // Verify select_fd is valid and currently shows no ready to read.
     // Verify select_fd is valid and currently shows no ready to read.
-    ASSERT_NE(dhcp_ddns::WatchSocket::SOCKET_NOT_VALID, select_fd);
+    ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd);
 
 
     // Make sure select_fd does evaluates to not ready via select and
     // Make sure select_fd does evaluates to not ready via select and
     // that ioReady() method agrees.
     // that ioReady() method agrees.
@@ -747,7 +747,7 @@ TEST(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
     close(sender.getSelectFd());
     close(sender.getSelectFd());
 
 
     // Send should fail as we interferred by closing the select-fd.
     // Send should fail as we interferred by closing the select-fd.
-    ASSERT_THROW(sender.sendRequest(ncr), WatchSocketError);
+    ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
 
 
     // Verify we didn't invoke the handler.
     // Verify we didn't invoke the handler.
     EXPECT_EQ(0, ncr_handler.pass_count_);
     EXPECT_EQ(0, ncr_handler.pass_count_);
@@ -827,7 +827,7 @@ TEST(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
     // Interfere by reading part of the marker from the select-fd.
     // Interfere by reading part of the marker from the select-fd.
     uint32_t buf = 0;
     uint32_t buf = 0;
     ASSERT_EQ((read (select_fd, &buf, 1)), 1);
     ASSERT_EQ((read (select_fd, &buf, 1)), 1);
-    ASSERT_NE(WatchSocket::MARKER, buf);
+    ASSERT_NE(util::WatchSocket::MARKER, buf);
 
 
     // Run one handler. This should execute the send completion handler
     // Run one handler. This should execute the send completion handler
     // after sending the message.  Duing completion handling clearing the
     // after sending the message.  Duing completion handling clearing the

+ 1 - 0
src/lib/dhcpsrv/Makefile.am

@@ -120,6 +120,7 @@ libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += subnet_id.h
 libkea_dhcpsrv_la_SOURCES += subnet_id.h
 libkea_dhcpsrv_la_SOURCES += subnet_selector.h
 libkea_dhcpsrv_la_SOURCES += subnet_selector.h
+libkea_dhcpsrv_la_SOURCES += timer_mgr.cc timer_mgr.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h
 libkea_dhcpsrv_la_SOURCES += utils.h
 libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h

+ 3 - 3
src/lib/dhcpsrv/d2_client_mgr.cc

@@ -30,7 +30,7 @@ namespace dhcp {
 
 
 D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
 D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
     name_change_sender_(), private_io_service_(),
     name_change_sender_(), private_io_service_(),
-    registered_select_fd_(dhcp_ddns::WatchSocket::SOCKET_NOT_VALID) {
+    registered_select_fd_(util::WatchSocket::SOCKET_NOT_VALID) {
     // Default constructor initializes with a disabled configuration.
     // Default constructor initializes with a disabled configuration.
 }
 }
 
 
@@ -275,9 +275,9 @@ D2ClientMgr::amSending() const {
 void
 void
 D2ClientMgr::stopSender() {
 D2ClientMgr::stopSender() {
     /// Unregister sender's select-fd.
     /// Unregister sender's select-fd.
-    if (registered_select_fd_ != dhcp_ddns::WatchSocket::SOCKET_NOT_VALID) {
+    if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) {
         IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
         IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
-        registered_select_fd_ = dhcp_ddns::WatchSocket::SOCKET_NOT_VALID;
+        registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID;
     }
     }
 
 
     // If its not null, call stop.
     // If its not null, call stop.

+ 1 - 1
src/lib/dhcpsrv/d2_client_mgr.h

@@ -409,7 +409,7 @@ protected:
     /// @brief Fetches the select-fd that is currently registered.
     /// @brief Fetches the select-fd that is currently registered.
     ///
     ///
     /// @return The currently registered select-fd or
     /// @return The currently registered select-fd or
-    /// dhcp_ddns::WatchSocket::SOCKET_NOT_VALID.
+    /// util::WatchSocket::SOCKET_NOT_VALID.
     ///
     ///
     /// @note This is only exposed for testing purposes.
     /// @note This is only exposed for testing purposes.
     int getRegisteredSelectFd();
     int getRegisteredSelectFd();

+ 50 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -544,6 +544,56 @@ lease from the PostgreSQL database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 A debug message issued when the server is attempting to update IPv6
 lease from the PostgreSQL database for the specified address.
 lease from the PostgreSQL database for the specified address.
 
 
+% DHCPSRV_TIMERMGR_REGISTER_TIMER registering timer: %1, using interval: %2 ms
+A debug message issued when the new interval timer is registered in
+the Timer Manager. This timer will have a callback function
+associated with it, and this function will be executed according
+to the interval specified. The unique name of the timer and the
+interval at which the callback function will be executed is
+included in the message.
+
+% DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION running operation for timer: %1
+A debug message issued when the Timer Manager is about to
+run a periodic operation associated with the given timer.
+An example of such operation is a periodic cleanup of
+expired leases. The name of the timer is included in the
+message.
+
+% DHCPSRV_TIMERMGR_START_THREAD starting thread for timers
+A debug message issued when the Timer Manager is starting a
+worker thread to run started timers. The worker thread is
+typically started right after all timers have been registered
+and runs until timers need to be reconfigured, e.g. their
+interval is changed, new timers are registered or existing
+timers are unregistered.
+
+% DHCPSRV_TIMERMGR_START_TIMER starting timer: %1
+A debug message issued when the registered interval timer is
+being started. If this operation is successful the timer will
+periodically execute the operation associated with it. The
+name of the started timer is included in the message.
+
+% DHCPSRV_TIMERMGR_STOP_THREAD stopping thread for timers
+A debug message issued when the Timer Manager is stopping
+the worker thread which executes interval timers. When the
+thread is stopped no timers will be executed. The thread is
+typically stopped at the server reconfiguration or when the
+server shuts down.
+
+% DHCPSRV_TIMERMGR_STOP_TIMER stopping timer: %1
+A debug message issued when the registered interval timer is
+being stopped. The timer remains registered and can be restarted
+if necessary. The name of the timer is included in the message.
+
+% DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS unregistering all timers
+A debug message issued when all registered interval timers are
+being unregistered from the Timer Manager.
+
+% DHCPSRV_TIMERMGR_UNREGISTER_TIMER unregistering timer: %1
+A debug message issued when one of the registered interval timers
+is unregistered from the Timer Manager. The name of the timer is
+included in the message.
+
 % DHCPSRV_UNEXPECTED_NAME database access parameters passed through '%1', expected 'lease-database'
 % DHCPSRV_UNEXPECTED_NAME database access parameters passed through '%1', expected 'lease-database'
 The parameters for access the lease database were passed to the server through
 The parameters for access the lease database were passed to the server through
 the named configuration parameter, but the code was expecting them to be
 the named configuration parameter, but the code was expecting them to be

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -104,6 +104,7 @@ libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
 libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
 libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
 libdhcpsrv_unittests_SOURCES += triplet_unittest.cc
 libdhcpsrv_unittests_SOURCES += triplet_unittest.cc
 libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h
 libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h
+libdhcpsrv_unittests_SOURCES += timer_mgr_unittest.cc
 
 
 libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 if HAVE_MYSQL
 if HAVE_MYSQL

+ 473 - 0
src/lib/dhcpsrv/tests/timer_mgr_unittest.cc

@@ -0,0 +1,473 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asio.hpp>
+#include <config.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for @c TimerMgr.
+class TimerMgrTest : public ::testing::Test {
+private:
+    /// @brief Prepares the class for a test.
+    virtual void SetUp();
+
+    /// @brief Cleans up after the test.
+    virtual void TearDown();
+
+    /// @brief IO service used by the test fixture class.
+    IOService io_service_;
+
+    /// @brief Boolean flag which indicates that the timeout
+    /// for the @c doWait function has been reached.
+    bool timeout_;
+
+public:
+
+    /// @brief Wrapper method for registering a new timer.
+    ///
+    /// This method registers a new timer in the @c TimerMgr. It associates a
+    /// @c timerCallback method with a timer. This method registers a number of
+    /// calls to the particular timer in the @c calls_count_ map.
+    ///
+    /// @param timer_name Unique timer name.
+    /// @param timer_interval Timer interval.
+    /// @param mode Interval timer mode, which defaults to
+    /// @c IntervalTimer::ONE_SHOT.
+    void registerTimer(const std::string& timer_name, const long timer_interval,
+                       const IntervalTimer::Mode& timer_mode = IntervalTimer::ONE_SHOT);
+
+    /// @brief Wait for one or many ready handlers.
+    ///
+    /// @param timeout Wait timeout in milliseconds.
+    /// @param call_receive Indicates if the @c IfaceMgr::receive6
+    /// should be called to run pending callbacks and clear
+    /// watch sockets.
+    void doWait(const long timeout, const bool call_receive = true);
+
+    /// @brief Generic callback for timers under test.
+    ///
+    /// This callback increases the calls count for specified timer name.
+    ///
+    /// @param timer_name Name of the timer for which callback counter should
+    /// be increased.
+    void timerCallback(const std::string& timer_name);
+
+    /// @brief Create a generic callback function for the timer.
+    ///
+    /// This is just a wrapped to make it a bit more convenient
+    /// in the test.
+    boost::function<void ()> makeCallback(const std::string& timer_name);
+
+    /// @brief Callback for timeout.
+    ///
+    /// This callback indicates the test timeout by setting the
+    /// @c timeout_ member.
+    void timeoutCallback();
+
+    /// @brief Type definition for a map holding calls counters for
+    /// timers.
+    typedef std::map<std::string, unsigned int> CallsCount;
+
+    /// @brief Holds the calls count for test timers.
+    ///
+    /// The key of this map holds the timer names. The value holds the number
+    /// of calls to the timer handlers.
+    CallsCount calls_count_;
+
+};
+
+void
+TimerMgrTest::SetUp() {
+    calls_count_.clear();
+    timeout_ = false;
+    // Make sure there are no dangling threads.
+    TimerMgr::instance().stopThread();
+}
+
+void
+TimerMgrTest::TearDown() {
+    // Make sure there are no dangling threads.
+    TimerMgr::instance().stopThread();
+    // Remove all timers.
+    TimerMgr::instance().unregisterTimers();
+}
+
+void
+TimerMgrTest::registerTimer(const std::string& timer_name, const long timer_interval,
+                            const IntervalTimer::Mode& timer_mode) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register the timer with the generic callback that counts the
+    // number of callback invocations.
+    ASSERT_NO_THROW(
+        timer_mgr.registerTimer(timer_name, makeCallback(timer_name), timer_interval,
+                                timer_mode)
+    );
+
+    calls_count_[timer_name] = 0;
+
+}
+
+void
+TimerMgrTest::doWait(const long timeout, const bool call_receive) {
+    IntervalTimer timeout_timer(io_service_);
+    timeout_timer.setup(boost::bind(&TimerMgrTest::timeoutCallback, this), timeout,
+                        IntervalTimer::ONE_SHOT);
+
+    // The timeout flag will be set by the timeoutCallback if the test
+    // lasts for too long. In this case we will return from here.
+    while (!timeout_) {
+        if (call_receive) {
+            // Block for one 1 millisecond.
+            IfaceMgr::instance().receive6(0, 1000);
+        }
+        // Run ready handlers from the local IO service to execute
+        // the timeout callback if necessary.
+        io_service_.get_io_service().poll_one();
+    }
+
+    timeout_ = false;
+}
+
+void
+TimerMgrTest::timerCallback(const std::string& timer_name) {
+    // Accumulate the number of calls to the timer handler.
+    ++calls_count_[timer_name];
+
+    // The timer installed is the ONE_SHOT timer, so we have
+    // to reschedule the timer.
+    TimerMgr::instance().setup(timer_name);
+}
+
+boost::function<void ()>
+TimerMgrTest::makeCallback(const std::string& timer_name) {
+    return (boost::bind(&TimerMgrTest::timerCallback, this, timer_name));
+}
+
+
+void
+TimerMgrTest::timeoutCallback() {
+    // Timeout occurred. Stop and reset IO service and mark
+    // the timeout flag.
+    io_service_.stop();
+    io_service_.get_io_service().reset();
+    timeout_ = true;
+}
+
+// This test checks that certain errors are returned when invalid
+// parameters are specified when registering a timer, or when
+// the registration can't be made.
+TEST_F(TimerMgrTest, registerTimer) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Empty timer name is not allowed.
+    ASSERT_THROW(timer_mgr.registerTimer("", makeCallback("timer1"), 1,
+                                         IntervalTimer::ONE_SHOT),
+                 BadValue);
+
+    // Add a timer with a correct name.
+    ASSERT_NO_THROW(timer_mgr.registerTimer("timer2", makeCallback("timer2"), 1,
+                                         IntervalTimer::ONE_SHOT));
+    // Adding the timer with the same name as the existing timer is not
+    // allowed.
+    ASSERT_THROW(timer_mgr.registerTimer("timer2", makeCallback("timer2"), 1,
+                                         IntervalTimer::ONE_SHOT),
+                 BadValue);
+
+    // Start worker thread.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Can't register the timer when the thread is running.
+    ASSERT_THROW(timer_mgr.registerTimer("timer1", makeCallback("timer1"), 1,
+                                         IntervalTimer::ONE_SHOT),
+                 InvalidOperation);
+
+    // Stop the thread and retry.
+    ASSERT_NO_THROW(timer_mgr.stopThread());
+    EXPECT_NO_THROW(timer_mgr.registerTimer("timer1", makeCallback("timer1"), 1,
+                                            IntervalTimer::ONE_SHOT));
+
+}
+
+// This test verifies that it is possible to unregister a timer from
+// the TimerMgr.
+TEST_F(TimerMgrTest, unregisterTimer) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register a timer and start it.
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+    ASSERT_NO_THROW(timer_mgr.setup("timer1"));
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Wait for the timer to execute several times.
+    doWait(100);
+
+    // Stop the thread but execute pending callbacks.
+    ASSERT_NO_THROW(timer_mgr.stopThread(true));
+
+    // Remember how many times the timer's callback was executed.
+    const unsigned int calls_count = calls_count_["timer1"];
+    ASSERT_GT(calls_count, 0);
+
+    // Check that an attempt to unregister a non-existing timer would
+    // result in exeception.
+    EXPECT_THROW(timer_mgr.unregisterTimer("timer2"), BadValue);
+
+    // Now unregister the correct one.
+    ASSERT_NO_THROW(timer_mgr.unregisterTimer("timer1"));
+
+    // Start the thread again and wait another 100ms.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+    doWait(100);
+    ASSERT_NO_THROW(timer_mgr.stopThread(true));
+
+    // The number of calls for the timer1 shouldn't change as the
+    // timer had been unregistered.
+    EXPECT_EQ(calls_count_["timer1"], calls_count);
+}
+
+// This test verifies taht it is possible to unregister all timers.
+/// @todo This test is disabled because it may occassionally hang
+/// due to bug in the ASIO implementation shipped with Kea.
+/// Replacing it with the ASIO implementation from BOOST does
+/// solve the problem. See ticket #4009. Until this ticket is
+/// implemented, the test should remain disabled.
+TEST_F(TimerMgrTest, DISABLED_unregisterTimers) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register 10 timers.
+    for (int i = 1; i <= 20; ++i) {
+        std::ostringstream s;
+        s << "timer" << i;
+        ASSERT_NO_FATAL_FAILURE(registerTimer(s.str(), 1))
+            << "fatal failure occurred while registering "
+            << s.str();
+        ASSERT_NO_THROW(timer_mgr.setup(s.str()))
+            << "exception thrown while calling setup() for the "
+            << s.str();
+    }
+
+    // Start worker thread and wait for 500ms.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+    doWait(500);
+    ASSERT_NO_THROW(timer_mgr.stopThread(true));
+
+    // Make sure that all timers have been executed at least once.
+    for (CallsCount::iterator it = calls_count_.begin();
+         it != calls_count_.end(); ++it) {
+        unsigned int calls_count = it->second;
+        ASSERT_GT(calls_count, 0)
+            << "expected calls counter for timer"
+            << (std::distance(calls_count_.begin(), it) + 1)
+            << " greater than 0";
+    }
+
+    // Copy counters for all timers.
+    CallsCount calls_count(calls_count_);
+
+    // Let's unregister all timers.
+    ASSERT_NO_THROW(timer_mgr.unregisterTimers());
+
+    // Start worker thread again and wait for 500ms.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+    doWait(500);
+    ASSERT_NO_THROW(timer_mgr.stopThread(true));
+
+    // The calls counter shouldn't change because there are
+    // no timers registered.
+    EXPECT_TRUE(calls_count == calls_count_);
+}
+
+// This test checks that it is not possible to unregister timers
+// while the thread is running.
+TEST_F(TimerMgrTest, unregisterTimerWhileRunning) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register two timers.
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 1));
+
+    // Start the thread and make sure we can't unregister them.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+    EXPECT_THROW(timer_mgr.unregisterTimer("timer1"), InvalidOperation);
+    EXPECT_THROW(timer_mgr.unregisterTimers(), InvalidOperation);
+
+    // No need to stop the thread as it will be stopped by the
+    // test fixture destructor.
+}
+
+// This test verifies that the timer execution can be cancelled.
+TEST_F(TimerMgrTest, cancel) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register timer.
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+
+    // We can start the worker thread before we even kick in the timers.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Kick in the timer and wait for 500ms.
+    ASSERT_NO_THROW(timer_mgr.setup("timer1"));
+    doWait(500);
+
+    // Cancelling non-existing timer should fail.
+    EXPECT_THROW(timer_mgr.cancel("timer2"), BadValue);
+
+    // Cancelling the good one should pass, even when the worker
+    // thread is running.
+    ASSERT_NO_THROW(timer_mgr.cancel("timer1"));
+
+    // Remember how many calls have been invoked and wait for
+    // another 500ms.
+    unsigned int calls_count = calls_count_["timer1"];
+    doWait(500);
+
+    // The number of calls shouldn't change because the timer had been
+    // cancelled.
+    ASSERT_EQ(calls_count, calls_count_["timer1"]);
+
+    // Setup the timer again.
+    ASSERT_NO_THROW(timer_mgr.setup("timer1"));
+    doWait(500);
+
+    // New calls should be recorded.
+    EXPECT_GT(calls_count_["timer1"], calls_count);
+}
+
+// This test verifies that the callbacks for the scheduled timers are
+// actually called.
+TEST_F(TimerMgrTest, scheduleTimers) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register two timers: 'timer1' and 'timer2'. The first timer will
+    // be executed at the 1ms interval. The second one at the 5ms
+    // interval.
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 5));
+
+    // We can start the worker thread before we even kick in the timers.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Kick in the timers. The timers have been registered so there
+    // should be no exception.
+    ASSERT_NO_THROW(timer_mgr.setup("timer1"));
+    ASSERT_NO_THROW(timer_mgr.setup("timer2"));
+
+    // Run IfaceMgr::receive6() in the loop for 500ms. This function
+    // will read data from the watch sockets created when the timers
+    // were registered. The data is delivered to the watch sockets
+    // at the interval of the timers, which should break the blocking
+    // call to receive6(). As a result, the callbacks associated
+    // with the watch sockets should be called.
+    doWait(500);
+
+    // Stop the worker thread, which would halt the execution of
+    // the timers.
+    timer_mgr.stopThread(true);
+
+    // We have been running the timer for 500ms at the interval of
+    // 1 ms. The maximum number of callbacks is 500. However, the
+    // callback itself takes time. Stoping the thread takes time.
+    // So, the real number differs significantly. We don't know
+    // exactly how many have been executed. It should be more
+    // than 10 for sure. But we really made up the numbers here.
+    EXPECT_GT(calls_count_["timer1"], 25);
+    // For the second timer it should be more than 5.
+    EXPECT_GT(calls_count_["timer2"], 5);
+
+    // Because the interval of the 'timer1' is lower than the
+    // interval of the 'timer2' the number of calls should
+    // be higher for the 'timer1'.
+    EXPECT_GT(calls_count_["timer1"], calls_count_["timer2"]);
+
+    // Remember the number of calls from 'timer1' and 'timer2'.
+    unsigned int calls_count_timer1 = calls_count_["timer1"];
+    unsigned int calls_count_timer2 = calls_count_["timer2"];
+
+    // Unregister the 'timer1'.
+    ASSERT_NO_THROW(timer_mgr.unregisterTimer("timer1"));
+
+    // Restart the thread.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Wait another 500ms. The 'timer1' was unregistered so it
+    // should not make any more calls. The 'timer2' should still
+    // work as previously.
+    doWait(500);
+
+    // The number of calls shouldn't have changed.
+    EXPECT_EQ(calls_count_timer1, calls_count_["timer1"]);
+    // There should be some new calls registered for the 'timer2'.
+    EXPECT_GT(calls_count_["timer2"], calls_count_timer2);
+}
+
+// This test verifies that it is possible to force that the pending
+// timer callbacks are executed when the worker thread is stopped.
+TEST_F(TimerMgrTest, stopThreadWithRunningHandlers) {
+    TimerMgr& timer_mgr = TimerMgr::instance();
+
+    // Register 'timer1'.
+    ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+
+    // We can start the worker thread before we even kick in the timers.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Kick in the timer.
+    ASSERT_NO_THROW(timer_mgr.setup("timer1"));
+
+    // Run the thread for 100ms. This should run some timers. The 'false'
+    // value indicates that the IfaceMgr::receive6 is not called, so the
+    // watch socket is never cleared.
+    doWait(100, false);
+
+    // There should be no calls registered for the timer1.
+    EXPECT_EQ(0, calls_count_["timer1"]);
+
+    // Stop the worker thread without completing pending callbacks.
+    ASSERT_NO_THROW(timer_mgr.stopThread(false));
+
+    // There should be still not be any calls registered.
+    EXPECT_EQ(0, calls_count_["timer1"]);
+
+    // We can restart the worker thread before we even kick in the timers.
+    ASSERT_NO_THROW(timer_mgr.startThread());
+
+    // Run the thread for 100ms. This should run some timers. The 'false'
+    // value indicates that the IfaceMgr::receive6 is not called, so the
+    // watch socket is never cleared.
+    doWait(100, false);
+
+    // There should be no calls registered for the timer1.
+    EXPECT_EQ(0, calls_count_["timer1"]);
+
+    // Stop the worker thread with completing pending callbacks.
+    ASSERT_NO_THROW(timer_mgr.stopThread(true));
+
+    // There should be one call registered.
+    EXPECT_EQ(1, calls_count_["timer1"]);
+}
+
+} // end of anonymous namespace

+ 300 - 0
src/lib/dhcpsrv/timer_mgr.cc

@@ -0,0 +1,300 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asio.hpp>
+#include <asiolink/io_service.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <boost/bind.hpp>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace isc::util::thread;
+
+namespace isc {
+namespace dhcp {
+
+TimerMgr&
+TimerMgr::instance() {
+    static TimerMgr timer_mgr;
+    return (timer_mgr);
+}
+
+TimerMgr::TimerMgr()
+    : io_service_(new IOService()), thread_(),
+      registered_timers_() {
+}
+
+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();
+}
+
+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);
+
+    // 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 << "'");
+    }
+
+    // Must not register new timer when the worker thread is running. Note
+    // that worker thread is using IO service and trying to register a new
+    // timer while IO service is being used would result in hang.
+    if (thread_) {
+        isc_throw(InvalidOperation, "unable to register new timer when the"
+                  " timer manager's worker thread is running");
+    }
+
+    // Create a structure holding the configuration for the timer. It will
+    // create the instance if the IntervalTimer and WatchSocket. It will
+    // also hold the callback, interval and scheduling mode parameters.
+    // This may throw a WatchSocketError if the socket creation fails.
+    TimerInfoPtr timer_info(new TimerInfo(getIOService(), callback,
+                                          interval, scheduling_mode));
+
+    // Register the WatchSocket in the IfaceMgr and register our own callback
+    // to be executed when the data is received over this socket. The only time
+    // 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,
+                                                       this, timer_name));
+
+    // Actually register the timer.
+    registered_timers_.insert(std::pair<std::string, TimerInfoPtr>(timer_name,
+                                                                   timer_info));
+}
+
+void
+TimerMgr::unregisterTimer(const std::string& timer_name) {
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+              DHCPSRV_TIMERMGR_UNREGISTER_TIMER)
+        .arg(timer_name);
+
+    if (thread_) {
+        isc_throw(InvalidOperation, "unable to unregister timer "
+                  << timer_name << " while worker thread is running");
+    }
+
+    // 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.
+    cancel(timer_name);
+
+    const TimerInfoPtr& timer_info = timer_info_it->second;
+
+    // Unregister the watch socket from the IfaceMgr.
+    IfaceMgr::instance().deleteExternalSocket(timer_info->watch_socket_.getSelectFd());
+
+    // Remove the timer.
+    registered_timers_.erase(timer_info_it);
+}
+
+void
+TimerMgr::unregisterTimers() {
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+              DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS);
+
+    // 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) {
+        unregisterTimer(timer_info_it->first);
+    }
+}
+
+void
+TimerMgr::setup(const std::string& timer_name) {
+
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+              DHCPSRV_TIMERMGR_START_TIMER)
+        .arg(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;
+   timer_info->interval_timer_.setup(boost::bind(&TimerMgr::timerCallback, this, timer_name),
+                                     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);
+
+    // 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();
+    // Clear watch socket, if ready.
+    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())));
+    }
+}
+
+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();
+    }
+}
+IOService&
+TimerMgr::getIOService() const {
+    return (*io_service_);
+}
+
+void
+TimerMgr::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()) {
+        // We will mark watch socket ready - write data to a socket to
+        // 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();
+    }
+}
+
+void
+TimerMgr::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()) {
+        // We're executing a callback function from the Interface Manager.
+        // This callback function is executed when the call to select() is
+        // interrupted as a result of receiving some data over the watch
+        // socket. We have to clear the socket which has been marked as
+        // ready. Then execute the callback function supplied by the
+        // TimerMgr user to perform custom actions on the expiration of
+        // the given timer.
+        handleReadySocket(timer_info_it, true);
+    }
+}
+
+void
+TimerMgr::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);
+   }
+}
+
+template<typename Iterator>
+void
+TimerMgr::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
+        // such traces.
+        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+                  DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION)
+            .arg(timer_info_iterator->first);
+        timer_info_iterator->second->user_callback_();
+    }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 387 - 0
src/lib/dhcpsrv/timer_mgr.h

@@ -0,0 +1,387 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TIMER_MGR_H
+#define TIMER_MGR_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <util/threads/thread.h>
+#include <util/watch_socket.h>
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Manages a pool of asynchronous interval timers for DHCP server.
+///
+/// This class holds a pool of asynchronous interval timers which are
+/// capable of interrupting the blocking call to @c select() function in
+/// the main threads of the DHCP servers. The main thread can then
+/// timely execute the callback function associated with the particular
+/// timer.
+///
+/// This class is useful for performing periodic actions at the specified
+/// intervals, e.g. act upon expired leases (leases reclamation) or
+/// return declined leases back to the address pool. Other applications
+/// may be added in the future.
+///
+/// The @c TimerMgr is a singleton, thus its instance is available from
+/// different places in the server code. This is convenient because timers
+/// can be installed by different configuration parsers or they can be
+/// re-scheduled from the callback functions.
+///
+/// The timer is registered using the @c TimerMgr::registerTimer method.
+/// Each registered timer has a unique name. It is not possible to register
+/// multiple timers with the same name. Each registered timer is associated
+/// with the callback function supplied by the caller. This callback function
+/// performs the tasks to be executed periodically according to the timer's
+/// interval.
+///
+/// The registered timer's interval does not begin to elapse until the
+/// @c TimerMgr::setup method is called for it.
+///
+/// The @c TimerMgr uses worker thread to run the timers. The thread is
+/// started and stopped using the @c TimerMgr::startThread and
+/// @c TimerMgr::stopThread respectively. The thread calls the blocking
+/// @c IOService::run. All the registered timers are associated with
+/// this instance of the @c IOService that the thread is running.
+/// When the timer elapses a generic callback function is executed
+/// @c TimerMgr::timerCallback with the parameter giving the name
+/// of the timer for which it has been executed.
+///
+/// Every registered timer is associated with an instance of the
+/// @c util::WatchSocket object. The socket is registered in the
+/// @c IfaceMgr as an "external" socket. When the generic callback
+/// function is invoked for the timer, it obtains the instance of the
+/// @c util::WatchSocket and marks it "ready". This call effectively
+/// writes the data to a socket (pipe) which interrupts the call
+/// to the @c select() function in the main thread. When the
+/// @c IfaceMgr (in the main thread) detects data transmitted over
+/// the external socket it will invoke a callback function
+/// associated with this socket. This is the
+/// @c TimerMgr::ifaceMgrCallback associated with the socket when the
+/// timer is registered. This callback function is executed/ in the
+/// main thread. It clears the socket, which unblocks the worker
+/// thread. It also invokes the user callback function specified
+/// for a given timer.
+///
+/// The @c TimerMgr::timerCallback function searches for the
+/// registered timer for which it has been called. This may cause
+/// race conditions between the worker thread and the main thread
+/// if the latter is modifying the collection of the registered
+/// timers. Therefore, the @c TimerMgr does not allow for
+/// registering or unregistering the timers when the worker thread
+/// is running. The worker thread must be stopped first.
+///
+/// @warning The application (DHCP server) is responsible for
+///  unregistering the timers before it terminates:
+/// @code
+///     TimerMgr::instance().unregisterTimers();
+/// @endcode
+///
+/// to avoid the static deinitialization fiasco between the @c TimerMgr
+/// and @c IfaceMgr. Note that the @c TimerMgr destructor doesn't
+/// unregister the timers to avoid referencing the @c IfaceMgr
+/// instance which may not exist at this point. If the timers are
+/// not unregistered before the application terminates this will
+/// likely result in segmentation fault on some systems.
+///
+class TimerMgr : public boost::noncopyable {
+public:
+
+    /// @brief Returns sole instance of the @c TimerMgr singleton.
+    static TimerMgr& instance();
+
+    /// @name Registering, unregistering and scheduling the timers.
+    //@{
+
+    /// @brief Registers new timers in the @c TimerMgr.
+    ///
+    /// This method must not be called while the worker thread is running,
+    /// as it modifies the internal data structure holding registered
+    /// timers, which is also accessed from the worker thread via the
+    /// callback. Inserting new element to this data structure and
+    /// reading it at the same time would yield undefined behavior.
+    ///
+    /// In order to prevent race conditions between the worker thread and
+    /// this method a mutex could be introduced. However, locking the mutex
+    /// would be required for all callback invocations, which could have
+    /// negative impact on the performance.
+    ///
+    /// @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.
+    ///
+    /// This method must not be called while the worker thread is running,
+    /// as it modifies the internal data structure holding registered
+    /// timers, which is also accessed from the worker thread via the
+    /// callback. Removing element from this data structure and
+    /// reading it at the same time would yield undefined behavior.
+    ///
+    /// In order to prevent race conditions between the worker thread and
+    /// this method a mutex could be introduced. However, locking the mutex
+    /// would be required for all callback invocations which could have
+    /// negative impact on the performance.
+    ///
+    /// @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);
+
+    //@}
+
+    /// @name Starting and stopping the worker thread.
+    //@{
+
+    /// @brief Starts worker thread
+    ///
+    /// This method has no effect if the thread has already been started.
+    void startThread();
+
+    /// @brief Stops worker thread.
+    ///
+    /// When the thread is being stopped, it is possible that some of the
+    /// timers have elapsed and marked their respective watch sockets
+    /// as "ready", but the sockets haven't been yet cleared in the
+    /// main thread and the installed callbacks haven't been executed.
+    /// It is possible to control whether those pending callbacks should
+    /// be executed or not before the call to @c stopThread ends.
+    /// If the thread is being stopped as a result of the DHCP server
+    /// reconfiguration running pending callback may take significant
+    /// amount of time, e.g. when operations on the lease database are
+    /// involved. If this is a concern, the function parameter should
+    /// be left at its default value. In this case, however, it is
+    /// important to note that callbacks installed on ONE_SHOT timers
+    /// often reschedule the timer. If such callback is not executed
+    /// the timer will have to be setup by the application when the
+    /// thread is started again.
+    ///
+    /// Setting the @c run_pending_callbacks to true will guarantee
+    /// that all callbacks for which the timers have already elapsed
+    /// (and marked their watch sockets as ready) will be executed
+    /// prior to the return from @c stopThread method. However, this
+    /// should be avoided if the longer execution time of the
+    /// @c stopThread function is a concern.
+    ///
+    /// This method has no effect if the thread is not running.
+    ///
+    /// @param run_pending_callbacks Indicates if the pending callbacks
+    /// should be executed (if true).
+    void stopThread(const bool run_pending_callbacks = false);
+
+    //@}
+
+    /// @brief Returns a reference to IO service used by the @c TimerMgr.
+    asiolink::IOService& getIOService() const;
+
+private:
+
+    /// @name Constructor and destructor.
+    //@{
+    ///
+    /// @brief Private default constructor.
+    ///
+    /// The @c TimerMgr is a singleton class which instance must be created
+    /// using the @c TimerMgr::instance method. Private constructor enforces
+    /// construction via @c TimerMgr::instance.
+    TimerMgr();
+
+    /// @brief Private destructor.
+    ///
+    /// Stops the worker thread if it is running. It doesn't unregister any
+    /// timers to avoid static deinitialization fiasco with the @c IfaceMgr.
+    ~TimerMgr();
+
+    //@}
+
+    /// @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 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::scoped_ptr<util::thread::Thread> thread_;
+
+    /// @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) { };
+    };
+
+    /// @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 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_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // TIMER_MGR_H

+ 1 - 0
src/lib/util/Makefile.am

@@ -25,6 +25,7 @@ libkea_util_la_SOURCES += range_utilities.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_la_SOURCES += stopwatch.cc stopwatch.h
 libkea_util_la_SOURCES += stopwatch.cc stopwatch.h
 libkea_util_la_SOURCES += stopwatch_impl.cc stopwatch_impl.h
 libkea_util_la_SOURCES += stopwatch_impl.cc stopwatch_impl.h
+libkea_util_la_SOURCES += watch_socket.cc watch_socket.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h
 libkea_util_la_SOURCES += encode/base16_from_binary.h
 libkea_util_la_SOURCES += encode/base32hex.h encode/base64.h
 libkea_util_la_SOURCES += encode/base32hex.h encode/base64.h
 libkea_util_la_SOURCES += encode/base32hex_from_binary.h
 libkea_util_la_SOURCES += encode/base32hex_from_binary.h

+ 2 - 0
src/lib/util/tests/Makefile.am

@@ -50,6 +50,8 @@ run_unittests_SOURCES += time_utilities_unittest.cc
 run_unittests_SOURCES += range_utilities_unittest.cc
 run_unittests_SOURCES += range_utilities_unittest.cc
 run_unittests_SOURCES += signal_set_unittest.cc
 run_unittests_SOURCES += signal_set_unittest.cc
 run_unittests_SOURCES += stopwatch_unittest.cc
 run_unittests_SOURCES += stopwatch_unittest.cc
+run_unittests_SOURCES += watch_socket_unittests.cc
+
 
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 49 - 4
src/lib/dhcp_ddns/tests/watch_socket_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015  Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -12,8 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 #include <config.h>
 #include <config.h>
-#include <dhcp_ddns/watch_socket.h>
-#include <test_utils.h>
+#include <util/watch_socket.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -27,10 +26,33 @@
 
 
 using namespace std;
 using namespace std;
 using namespace isc;
 using namespace isc;
-using namespace isc::dhcp_ddns;
+using namespace isc::util;
 
 
 namespace {
 namespace {
 
 
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+///
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read. 
+int selectCheck(int fd_to_check) {
+    fd_set read_fds;
+    int maxfd = 0;
+
+    FD_ZERO(&read_fds);
+
+    // Add this socket to listening set
+    FD_SET(fd_to_check,  &read_fds);
+    maxfd = fd_to_check;
+
+    struct timeval select_timeout;
+    select_timeout.tv_sec = 0;
+    select_timeout.tv_usec = 0;
+
+    return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
 /// @brief Tests the basic functionality of WatchSocket.
 /// @brief Tests the basic functionality of WatchSocket.
 TEST(WatchSocketTest, basics) {
 TEST(WatchSocketTest, basics) {
     WatchSocketPtr watch;
     WatchSocketPtr watch;
@@ -209,4 +231,27 @@ TEST(WatchSocketTest, badReadOnClear) {
     ASSERT_THROW(watch->markReady(), WatchSocketError);
     ASSERT_THROW(watch->markReady(), WatchSocketError);
 }
 }
 
 
+/// @brief Checks if the socket can be explicitly closed.
+TEST(WatchSocketTest, explicitClose) {
+    WatchSocketPtr watch;
+
+    // Create new instance of the socket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    // Make sure it has been opened by checking that its descriptor
+    // is valid.
+    EXPECT_NE(watch->getSelectFd(), WatchSocket::SOCKET_NOT_VALID);
+
+    // Close the socket.
+    std::string error_string;
+    ASSERT_TRUE(watch->closeSocket(error_string));
+
+    // Make sure that the descriptor is now invalid which indicates
+    // that the socket has been closed.
+    EXPECT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+    // No errors should be reported.
+    EXPECT_TRUE(error_string.empty());
+}
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 27 - 13
src/lib/dhcp_ddns/watch_socket.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -14,15 +14,17 @@
 
 
 /// @file watch_socket.cc
 /// @file watch_socket.cc
 
 
-#include <dhcp_ddns/dhcp_ddns_log.h>
-#include <dhcp_ddns/watch_socket.h>
+//#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <util/watch_socket.h>
 
 
 #include <fcntl.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <errno.h>
+#include <sstream>
+#include <string.h>
 #include <sys/select.h>
 #include <sys/select.h>
 
 
 namespace isc {
 namespace isc {
-namespace dhcp_ddns {
+namespace util {
 
 
 
 
 const int WatchSocket::SOCKET_NOT_VALID;
 const int WatchSocket::SOCKET_NOT_VALID;
@@ -116,17 +118,17 @@ WatchSocket::clearReady() {
     }
     }
 }
 }
 
 
-void
-WatchSocket::closeSocket() {
+bool
+WatchSocket::closeSocket(std::string& error_string) {
+    std::ostringstream s;
     // Close the pipe fds.  Technically a close can fail (hugely unlikely)
     // Close the pipe fds.  Technically a close can fail (hugely unlikely)
     // but there's no recovery for it either.  If one does fail we log it
     // but there's no recovery for it either.  If one does fail we log it
     // and go on. Plus this is called by the destructor and no one likes
     // and go on. Plus this is called by the destructor and no one likes
     // destructors that throw.
     // destructors that throw.
     if (source_ != SOCKET_NOT_VALID) {
     if (source_ != SOCKET_NOT_VALID) {
         if (close(source_)) {
         if (close(source_)) {
-            const char* errstr = strerror(errno);
-            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR)
-                      .arg(errstr);
+            // An error occured.
+            s << "Could not close source: " << strerror(errno);
         }
         }
 
 
         source_ = SOCKET_NOT_VALID;
         source_ = SOCKET_NOT_VALID;
@@ -134,13 +136,25 @@ WatchSocket::closeSocket() {
 
 
     if (sink_ != SOCKET_NOT_VALID) {
     if (sink_ != SOCKET_NOT_VALID) {
         if (close(sink_)) {
         if (close(sink_)) {
-            const char* errstr = strerror(errno);
-            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SINK_CLOSE_ERROR)
-                      .arg(errstr);
+            // An error occured.
+            if (error_string.empty()) {
+                s << "could not close sink: " << strerror(errno);
+            }
         }
         }
 
 
         sink_ = SOCKET_NOT_VALID;
         sink_ = SOCKET_NOT_VALID;
     }
     }
+
+    error_string = s.str();
+
+    // If any errors have been reported, return false.
+    return (error_string.empty() ? true : false);
+}
+
+void
+WatchSocket::closeSocket() {
+    std::string error_string;
+    closeSocket(error_string);
 }
 }
 
 
 int
 int
@@ -148,5 +162,5 @@ WatchSocket::getSelectFd() {
     return (sink_);
     return (sink_);
 }
 }
 
 
-} // namespace isc::dhcp_ddns
+} // namespace isc::util
 } // namespace isc
 } // namespace isc

+ 20 - 7
src/lib/dhcp_ddns/watch_socket.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -18,13 +18,14 @@
 /// @file watch_socket.h Defines the class, WatchSocket.
 /// @file watch_socket.h Defines the class, WatchSocket.
 
 
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
-
+#include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
 #include <stdint.h>
 #include <stdint.h>
+#include <string>
 
 
 namespace isc {
 namespace isc {
-namespace dhcp_ddns {
+namespace util {
 
 
 /// @brief Exception thrown if an error occurs during IO source open.
 /// @brief Exception thrown if an error occurs during IO source open.
 class WatchSocketError : public isc::Exception {
 class WatchSocketError : public isc::Exception {
@@ -51,7 +52,7 @@ public:
 /// such as close, read, or altering behavior flags with fcntl or ioctl can have
 /// such as close, read, or altering behavior flags with fcntl or ioctl can have
 /// unpredictable results.  It is intended strictly use with functions such as select()
 /// unpredictable results.  It is intended strictly use with functions such as select()
 /// poll() or their variants.
 /// poll() or their variants.
-class WatchSocket {
+class WatchSocket : public boost::noncopyable {
 public:
 public:
     /// @brief Value used to signify an invalid descriptor.
     /// @brief Value used to signify an invalid descriptor.
     static const int SOCKET_NOT_VALID = -1;
     static const int SOCKET_NOT_VALID = -1;
@@ -114,11 +115,23 @@ public:
     /// pipe.
     /// pipe.
     int getSelectFd();
     int getSelectFd();
 
 
+    /// @brief Closes the descriptors associated with the socket.
+    ///
+    /// This method is used to close the socket and capture errors that
+    /// may occur during this operation.
+    ///
+    /// @param [out] error_string Holds the error string if closing
+    /// the socket failed. It will hold empty string otherwise.
+    ///
+    /// @return true if the operation was successful, false otherwise.
+    bool closeSocket(std::string& error_string);
+
 private:
 private:
+
     /// @brief Closes the descriptors associated with the socket.
     /// @brief Closes the descriptors associated with the socket.
     ///
     ///
-    /// Used internally in the destructor and if an error occurs marking or
-    /// clearing the socket.
+    /// This method is called by the class destructor and it ignores
+    /// any errors that may occur while closing the sockets.
     void closeSocket();
     void closeSocket();
 
 
     /// @brief The end of the pipe to which the marker is written
     /// @brief The end of the pipe to which the marker is written
@@ -132,7 +145,7 @@ private:
 /// @brief Defines a smart pointer to an instance of a WatchSocket.
 /// @brief Defines a smart pointer to an instance of a WatchSocket.
 typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
 typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
 
 
-} // namespace isc::dhcp_ddns
+} // namespace isc::util
 } // namespace isc
 } // namespace isc
 
 
 #endif
 #endif