Browse Source

[3221] Add new class dhcp_ddns::WatchSocket

Added a new to libdhcp_ddns, WatchSocket. This class provides an open
file descriptor whose read-readiness can be set or cleared and checked
via select() or poll() variants.
Thomas Markwalder 11 years ago
parent
commit
1ebd0b7870

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

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

+ 12 - 0
src/lib/dhcp_ddns/dhcp_ddns_messages.mes

@@ -74,3 +74,15 @@ This is an error message that indicates that an exception was thrown but not
 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
 client may or may not continue operating normally.
+
+% 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.
+
+% DHCP_DDNS_WATCH_SINK_CLOSE_ERROR Sink-side watch socket failed to close: %1
+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.

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

@@ -29,6 +29,7 @@ TESTS += libdhcp_ddns_unittests
 libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+libdhcp_ddns_unittests_SOURCES += watch_socket_unittests.cc
 
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 

+ 96 - 0
src/lib/dhcp_ddns/tests/watch_socket_unittests.cc

@@ -0,0 +1,96 @@
+// Copyright (C) 2013  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 <dhcp_ddns/watch_socket.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+
+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). 
+
+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.
+TEST(WatchSocketTest, basics) {
+    WatchSocketPtr watch;
+
+    /// Verify that we can construct a WatchSocket.
+    ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+    ASSERT_TRUE(watch);
+
+    /// Verify that post-construction the state the select-fd is valid.
+    int select_fd = watch->getSelectFd();
+    EXPECT_NE(select_fd, WatchSocket::INVALID_SOCKET);
+   
+    /// Verify that isReady() is false and that a call to select agrees. 
+    EXPECT_FALSE(watch->isReady());
+    EXPECT_EQ(0, selectCheck(select_fd));
+
+    /// Verify that the socket can be marked ready.
+    ASSERT_NO_THROW(watch->markReady());
+
+    /// Verify that we have exactly one marker waiting to be read.
+    int count = 0;
+    EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+    EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+    /// Verify that we can call markReady again without error.
+    ASSERT_NO_THROW(watch->markReady());
+
+    /// Verify that we STILL have exactly one marker waiting to be read.
+    EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+    EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+    /// Verify that isReady() is true and that a call to select agrees. 
+    EXPECT_TRUE(watch->isReady());
+    EXPECT_EQ(1, selectCheck(select_fd));
+
+    /// Verify that the socket can be cleared.
+    ASSERT_NO_THROW(watch->clearReady());
+
+    /// Verify that isReady() is false and that a call to select agrees. 
+    EXPECT_FALSE(watch->isReady());
+    EXPECT_EQ(0, selectCheck(select_fd));
+}
+
+} // end of anonymous namespace

+ 100 - 0
src/lib/dhcp_ddns/watch_socket.cc

@@ -0,0 +1,100 @@
+// Copyright (C) 2014 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.
+
+/// @file watch_socket.cc
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/watch_socket.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+const int WatchSocket::INVALID_SOCKET;
+const uint32_t WatchSocket::MARKER;
+
+WatchSocket::WatchSocket() 
+    : source_(INVALID_SOCKET), sink_(INVALID_SOCKET), ready_flag_(false) {
+    // Open the pipe.
+    int fds[2];
+    if (pipe(fds)) {
+        const char* errstr = strerror(errno);
+        isc_throw(WatchSocketError, "Cannot construct pipe: " << errstr);
+    }
+
+    source_ = fds[1];
+    sink_ = fds[0];
+}
+
+WatchSocket::~WatchSocket() {
+    // 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 
+    // and go on. Plus no likes destructors that throw.
+    if (source_ != INVALID_SOCKET) {
+        if (close(source_)) {
+            const char* errstr = strerror(errno);
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SOURCE_CLOSE_ERROR)
+                      .arg(errstr);
+        }
+    }
+
+    if (sink_ != INVALID_SOCKET) {
+        if (close(sink_)) {
+            const char* errstr = strerror(errno);
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_WATCH_SINK_CLOSE_ERROR)
+                      .arg(errstr);
+        }
+    }
+}
+
+void 
+WatchSocket::markReady() {
+    if (!isReady()) {
+        int nbytes = write (source_, &MARKER, sizeof(MARKER));
+        if (nbytes != sizeof(MARKER)) {
+            const char* errstr = strerror(errno);
+            isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+                      << " bytes written: " << nbytes << " : " << errstr);
+        }
+
+        ready_flag_ = true;
+    } 
+}
+
+bool 
+WatchSocket::isReady() {
+    return (ready_flag_);
+}
+
+void 
+WatchSocket::clearReady() {
+    if (isReady()) {
+        uint32_t buf;
+        int nbytes = read (sink_, &buf, sizeof(buf));
+        if (nbytes != sizeof(MARKER)) { 
+            const char* errstr = strerror(errno);
+            isc_throw(WatchSocketError, "WatchSocket clearReady failed:"
+                      << " bytes read: " << nbytes << " : " << errstr);
+        }
+
+        ready_flag_ = false;
+    }
+}
+
+int 
+WatchSocket::getSelectFd() {
+    return (sink_);
+}
+
+} // namespace isc::dhcp_ddns
+} // namespace isc

+ 108 - 0
src/lib/dhcp_ddns/watch_socket.h

@@ -0,0 +1,108 @@
+// Copyright (C) 2014 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 WATCH_SOCKET_H
+#define WATCH_SOCKET_H
+
+/// @file watch_socket.h Defines the class, WatchSocket.
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class WatchSocketError : public isc::Exception {
+public:
+    WatchSocketError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Provides an IO "ready" semaphore for use with select() or poll()
+/// WatchSocket exposes a single open file descriptor, the "select-fd" which
+/// can be marked as being ready to read (i.e. !EWOULDBLOCK) and cleared
+/// (i.e. EWOULDBLOCK).  The select-fd can be used with select(), poll(), or
+/// their variants alongside other file descriptors.
+///
+/// Internally, WatchSocket uses a pipe.  The select-fd is the "read" end of
+/// pipe.  To mark the socket as ready to read, an integer marker is written
+/// to the pipe.  To clear the socket, the marker is read from the pipe.  Note
+/// that WatchSocket will only write the marker if it is not already marked.
+/// This prevents the socket's pipe from filling endlessly.
+class WatchSocket {
+public:
+    /// @brief Value used to signify an invalid descriptor.
+    static const int INVALID_SOCKET = -1;
+    /// @brief Value written to the source when marking the socket as ready.
+    static const uint32_t MARKER = 0xDEADBEEF;
+
+    /// @brief Constructor
+    ///
+    /// Constructs an instance of the WatchSocket in the cleared (EWOULDBLOCK)
+    /// state.
+    WatchSocket();
+
+    /// @brief Destructor
+    ///
+    /// Closes all internal resources, including the select-fd.
+    virtual ~WatchSocket();
+
+    /// @brief Marks the select-fd as ready to read.
+    ///
+    /// Marks the socket as ready to read, if is not already so marked.
+    ///
+    /// @throw WatchSocketError if an error occurs marking the socket.
+    void markReady();
+
+    /// @brief Returns true the if socket is marked as ready.
+    bool isReady();
+
+    /// @brief Clears the socket's ready to read marker.
+    ///
+    /// Clears the socket if it is currently marked as ready to read.
+    ///
+    /// @throw WatchSocketError if an error occurs clearing the socket
+    /// marker.
+    void clearReady();
+
+    /// @brief Returns the file descriptor to use to monitor the socket.
+    ///
+    /// @note Using this file descriptor as anything other than an argument
+    /// to select() or similar methods can have unpredictable results.
+    ///
+    /// @return The file descriptor associated with read end of the socket's
+    /// pipe.
+    int getSelectFd();
+
+private:
+    /// @brief The end of the pipe to which the marker is written
+    int source_;
+
+    /// @brief The end of the pipe from which the marker is read.
+    /// This is the value returned as the select-fd.
+    int sink_;
+
+    /// @brief True the socket is currently marked ready to read.
+    bool ready_flag_;
+};
+
+/// @brief Defines a smart pointer to an instance of a WatchSocket.
+typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif