Commit 1ebd0b78 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[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.
parent 5ff46cb0
......@@ -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
......
......@@ -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.
......@@ -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)
......
// 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
// 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
// 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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment