2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[#1148] make NameChangeSender thread safe

This commit is contained in:
Razvan Becheriu
2020-03-17 22:50:04 +02:00
parent 02d802a50d
commit 6f00cb77c1
3 changed files with 757 additions and 52 deletions

View File

@@ -8,12 +8,18 @@
#include <asiolink/asio_wrapper.h>
#include <dhcp_ddns/dhcp_ddns_log.h>
#include <dhcp_ddns/ncr_io.h>
#include <util/multi_threading_mgr.h>
#include <boost/algorithm/string/predicate.hpp>
#include <mutex>
namespace isc {
namespace dhcp_ddns {
using namespace isc::util;
using namespace std;
NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
if (boost::iequals(protocol_str, "UDP")) {
return (NCR_UDP);
@@ -154,7 +160,7 @@ NameChangeListener::invokeRecvHandler(const Result result,
NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
size_t send_queue_max)
: sending_(false), send_handler_(send_handler),
send_queue_max_(send_queue_max), io_service_(NULL) {
send_queue_max_(send_queue_max), io_service_(NULL), mutex_(new mutex) {
// Queue size must be big enough to hold at least 1 entry.
setQueueMaxSize(send_queue_max);
@@ -167,18 +173,28 @@ NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
isc_throw(NcrSenderError, "NameChangeSender is already sending");
}
// Clear send marker.
ncr_to_send_.reset();
// Call implementation dependent open.
try {
// Remember io service we're given.
io_service_ = &io_service;
open(io_service);
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
startSendingInternal(io_service);
} else {
startSendingInternal(io_service);
}
} catch (const isc::Exception& ex) {
stopSending();
isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
}
}
void
NameChangeSender::startSendingInternal(isc::asiolink::IOService& io_service) {
// Clear send marker.
ncr_to_send_.reset();
// Remember io service we're given.
io_service_ = &io_service;
open(io_service);
// Set our status to sending.
setSending(true);
@@ -229,10 +245,20 @@ NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
isc_throw(NcrSenderError, "request to send is empty");
}
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
sendRequestInternal(ncr);
} else {
sendRequestInternal(ncr);
}
}
void
NameChangeSender::sendRequestInternal(NameChangeRequestPtr& ncr) {
if (send_queue_.size() >= send_queue_max_) {
isc_throw(NcrSenderQueueFull,
"send queue has reached maximum capacity: "
<< send_queue_max_ );
<< send_queue_max_);
}
// Put it on the queue.
@@ -267,6 +293,16 @@ NameChangeSender::sendNext() {
void
NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
invokeSendHandlerInternal(result);
} else {
invokeSendHandlerInternal(result);
}
}
void
NameChangeSender::invokeSendHandlerInternal(const NameChangeSender::Result result) {
// @todo reset defense timer
if (result == SUCCESS) {
// It shipped so pull it off the queue.
@@ -318,6 +354,16 @@ NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
void
NameChangeSender::skipNext() {
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
skipNextInternal();
} else {
skipNextInternal();
}
}
void
NameChangeSender::skipNextInternal() {
if (!send_queue_.empty()) {
// Discards the request at the front of the queue.
send_queue_.pop_front();
@@ -330,7 +376,12 @@ NameChangeSender::clearSendQueue() {
isc_throw(NcrSenderError, "Cannot clear queue while sending");
}
send_queue_.clear();
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
send_queue_.clear();
} else {
send_queue_.clear();
}
}
void
@@ -341,19 +392,54 @@ NameChangeSender::setQueueMaxSize(const size_t new_max) {
}
send_queue_max_ = new_max;
}
size_t
NameChangeSender::getQueueSize() const {
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
return (getQueueSizeInternal());
} else {
return (getQueueSizeInternal());
}
}
size_t
NameChangeSender::getQueueSizeInternal() const {
return (send_queue_.size());
}
const NameChangeRequestPtr&
NameChangeSender::peekAt(const size_t index) const {
if (index >= getQueueSize()) {
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
return (peekAtInternal(index));
} else {
return (peekAtInternal(index));
}
}
const NameChangeRequestPtr&
NameChangeSender::peekAtInternal(const size_t index) const {
auto size = getQueueSizeInternal();
if (index >= size) {
isc_throw(NcrSenderError,
"NameChangeSender::peekAt peek beyond end of queue attempted"
<< " index: " << index << " queue size: " << getQueueSize());
<< " index: " << index << " queue size: " << size);
}
return (send_queue_.at(index));
}
bool
NameChangeSender::isSendInProgress() const {
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
return ((ncr_to_send_) ? true : false);
} else {
return ((ncr_to_send_) ? true : false);
}
}
void
NameChangeSender::assumeQueue(NameChangeSender& source_sender) {
@@ -372,6 +458,16 @@ NameChangeSender::assumeQueue(NameChangeSender& source_sender) {
" source queue count exceeds target queue max");
}
if (MultiThreadingMgr::instance().getMode()) {
lock_guard<mutex> lock(*mutex_);
assumeQueueInternal(source_sender);
} else {
assumeQueueInternal(source_sender);
}
}
void
NameChangeSender::assumeQueueInternal(NameChangeSender& source_sender) {
if (!send_queue_.empty()) {
isc_throw(NcrSenderError, "Cannot assume queue:"
" target queue is not empty");
@@ -399,6 +495,5 @@ NameChangeSender::runReadyIO() {
io_service_->get_io_service().poll_one();
}
} // namespace isc::dhcp_ddns
} // namespace isc
} // namespace dhcp_ddns
} // namespace isc

View File

@@ -46,14 +46,16 @@
/// communications that is independent of the IO layer mechanisms. While the
/// type and details of the IO mechanism are not relevant to either class, it
/// is presumed to use isc::asiolink library for asynchronous event processing.
///
#include <asiolink/io_address.h>
#include <asiolink/io_service.h>
#include <dhcp_ddns/ncr_msg.h>
#include <exceptions/exceptions.h>
#include <boost/scoped_ptr.hpp>
#include <deque>
#include <mutex>
namespace isc {
namespace dhcp_ddns {
@@ -68,7 +70,7 @@ enum NameChangeProtocol {
NCR_TCP
};
/// @brief Function which converts labels to NameChangeProtocol enum values.
/// @brief Function which converts text labels to @ref NameChangeProtocol enums.
///
/// @param protocol_str text to convert to an enum.
/// Valid string values: "UDP", "TCP"
@@ -79,7 +81,7 @@ enum NameChangeProtocol {
/// enum value.
extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
/// @brief Function which converts NameChangeProtocol enums to text labels.
/// @brief Function which converts @ref NameChangeProtocol enums to text labels.
///
/// @param protocol enum value to convert to label
///
@@ -167,10 +169,10 @@ public:
/// @brief Defines the outcome of an asynchronous NCR receive
enum Result {
SUCCESS,
TIME_OUT,
STOPPED,
ERROR
SUCCESS,
TIME_OUT,
STOPPED,
ERROR
};
/// @brief Abstract class for defining application layer receive callbacks.
@@ -179,7 +181,8 @@ public:
/// derivation of this class to the listener constructor in order to
/// receive NameChangeRequests.
class RequestReceiveHandler {
public:
public:
/// @brief Function operator implementing a NCR receive callback.
///
/// This method allows the application to receive the inbound
@@ -190,6 +193,7 @@ public:
/// @param ncr is a pointer to the newly received NameChangeRequest if
/// result is NameChangeListener::SUCCESS. It is indeterminate other
/// wise.
///
/// @throw This method MUST NOT throw.
virtual void operator ()(const Result result,
NameChangeRequestPtr& ncr) = 0;
@@ -296,12 +300,15 @@ protected:
virtual void doReceive() = 0;
public:
/// @brief Returns true if the listener is listening, false otherwise.
///
/// A true value indicates that the IO source has been opened successfully,
/// and that receive loop logic is active. This implies that closing the
/// IO source will interrupt that operation, resulting in a callback
/// invocation.
///
/// @return The listening mode.
bool amListening() const {
return (listening_);
}
@@ -315,6 +322,8 @@ public:
/// deleted while there is an IO call pending. This can result in the
/// IO service attempting to invoke methods on objects that are no longer
/// valid.
///
/// @return The pending flag.
bool isIoPending() const {
return (io_pending_);
}
@@ -477,7 +486,8 @@ public:
/// derivation of this class to the sender constructor in order to
/// receive send outcome notifications.
class RequestSendHandler {
public:
public:
/// @brief Function operator implementing a NCR send callback.
///
/// This method allows the application to receive the outcome of
@@ -504,7 +514,7 @@ public:
/// send queue. Once the maximum number is reached, all calls to
/// sendRequest will fail with an exception.
NameChangeSender(RequestSendHandler& send_handler,
size_t send_queue_max = MAX_QUEUE_DEFAULT);
size_t send_queue_max = MAX_QUEUE_DEFAULT);
/// @brief Destructor
virtual ~NameChangeSender() {
@@ -573,13 +583,61 @@ public:
/// @return true if the sender has at IO ready, false otherwise.
virtual bool ioReady() = 0;
private:
/// @brief Prepares the IO for transmission in a thread safe context.
///
/// @param io_service is the IOService that will handle IO event processing.
void startSendingInternal(isc::asiolink::IOService & io_service);
/// @brief Queues the given request to be sent in a thread safe context.
///
/// @param ncr is the NameChangeRequest to send.
///
/// @throw NcrSenderQueueFull if the send queue has reached capacity.
void sendRequestInternal(NameChangeRequestPtr& ncr);
/// @brief Move all queued requests from a given sender into the send queue
/// in a thread safe context.
///
/// @param source_sender from whom the queued messages will be taken
///
/// @throw NcrSenderError if this sender's queue is not empty.
void assumeQueueInternal(NameChangeSender& source_sender);
/// @brief Calls the NCR send completion handler registered with the
/// sender in a thread safe context.
///
/// @param result contains that send outcome status.
void invokeSendHandlerInternal(const NameChangeSender::Result result);
/// @brief Removes the request at the front of the send queue in a thread
/// safe context.
void skipNextInternal();
/// @brief Returns the number of entries currently in the send queue in a
/// thread safe context.
///
/// @return the queue size.
size_t getQueueSizeInternal() const;
/// @brief Returns the entry at a given position in the queue in a thread
/// safe context.
///
/// @return Pointer reference to the queue entry.
///
/// @throw NcrSenderError if the given index is beyond the
/// end of the queue.
const NameChangeRequestPtr& peekAtInternal(const size_t index) const;
protected:
/// @brief Dequeues and sends the next request on the send queue.
/// @brief Dequeues and sends the next request on the send queue in a thread
/// safe context.
///
/// If there is already a send in progress just return. If there is not
/// a send in progress and the send queue is not empty the grab the next
/// message on the front of the queue and call doSend().
///
void sendNext();
/// @brief Calls the NCR send completion handler registered with the
@@ -587,8 +645,8 @@ protected:
///
/// This is the hook by which the sender's caller's NCR send completion
/// handler is called. This method MUST be invoked by the derivation's
/// implementation of doSend. Note that if the send was a success,
/// the entry at the front of the queue is removed from the queue.
/// implementation of doSend. Note that if the send was a success, the
/// entry at the front of the queue is removed from the queue.
/// If not we leave it there so we can retry it. After we invoke the
/// handler we clear the pending ncr value and queue up the next send.
///
@@ -643,6 +701,7 @@ protected:
virtual void doSend(NameChangeRequestPtr& ncr) = 0;
public:
/// @brief Removes the request at the front of the send queue
///
/// This method can be used to avoid further retries of a failed
@@ -653,7 +712,7 @@ public:
/// It is presumed that sends will only fail due to some sort of
/// communications issue. In the unlikely event that a request is
/// somehow tainted and causes an send failure based on its content,
/// this method provides a means to remove th message.
/// this method provides a means to remove the message.
void skipNext();
/// @brief Flushes all entries in the send queue
@@ -661,6 +720,7 @@ public:
/// This method can be used to discard all of the NCRs currently in the
/// the send queue. Note it may not be called while the sender is in
/// the sending state.
///
/// @throw NcrSenderError if called and sender is in sending state.
void clearSendQueue();
@@ -668,6 +728,8 @@ public:
///
/// A true value indicates that the IO sink has been opened successfully,
/// and that send loop logic is active.
///
/// @return The send mode.
bool amSending() const {
return (sending_);
}
@@ -676,11 +738,13 @@ public:
///
/// A true value indicates that a request is actively in the process of
/// being delivered.
bool isSendInProgress() const {
return ((ncr_to_send_) ? true : false);
}
///
/// @return The send in progress flag.
bool isSendInProgress() const;
/// @brief Returns the maximum number of entries allowed in the send queue.
///
/// @return The queue maximum size.
size_t getQueueMaxSize() const {
return (send_queue_max_);
}
@@ -696,13 +760,14 @@ public:
void setQueueMaxSize(const size_t new_max);
/// @brief Returns the number of entries currently in the send queue.
size_t getQueueSize() const {
return (send_queue_.size());
}
///
/// @return The queue size.
size_t getQueueSize() const;
/// @brief Returns the entry at a given position in the queue.
///
/// Note that the entry is not removed from the queue.
///
/// @param index the index of the entry in the queue to fetch.
/// Valid values are 0 (front of the queue) to (queue size - 1).
///
@@ -731,16 +796,19 @@ public:
/// By running only one handler at time, we ensure that NCR IO activity
/// doesn't starve other processing. It is unclear how much of a real
/// threat this poses but for now it is best to err on the side of caution.
///
virtual void runReadyIO();
protected:
/// @brief Returns a reference to the send queue.
///
/// @return The send queue.
SendQueue& getSendQueue() {
return (send_queue_);
}
private:
/// @brief Sets the sending indicator to the given value.
///
/// Note, this method is private as it is used the base class is solely
@@ -748,7 +816,7 @@ private:
///
/// @param value is the new value to assign to the indicator.
void setSending(bool value) {
sending_ = value;
sending_ = value;
}
/// @brief Boolean indicator which tracks sending status.
@@ -771,12 +839,15 @@ private:
/// reference. Use a raw pointer to store it. This value should never be
/// exposed and is only valid while in send mode.
asiolink::IOService* io_service_;
/// @brief The mutex used to protect internal state.
const boost::scoped_ptr<std::mutex> mutex_;
};
/// @brief Defines a smart pointer to an instance of a sender.
typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
} // namespace isc::dhcp_ddns
} // namespace isc
} // namespace dhcp_ddns
} // namespace isc
#endif

View File

@@ -6,22 +6,26 @@
#include <config.h>
#include <gtest/gtest.h>
#include <asiolink/interval_timer.h>
#include <dhcp_ddns/ncr_io.h>
#include <dhcp_ddns/ncr_udp.h>
#include <util/multi_threading_mgr.h>
#include <util/time_utilities.h>
#include <test_utils.h>
#include <boost/asio/ip/udp.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <gtest/gtest.h>
#include <algorithm>
#include <sys/select.h>
using namespace std;
using namespace isc;
using namespace isc::util;
using namespace isc::dhcp_ddns;
namespace {
@@ -231,7 +235,7 @@ public:
/// This test verifies that a listener can enter listening mode and
/// receive NCRs in wire format on its UDP socket; reconstruct the
/// NCRs and delivery them to the "application" layer.
TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
TEST_F(NameChangeUDPListenerTest, basicReceiveTests) {
// Verify we can enter listening mode.
ASSERT_FALSE(listener_->amListening());
ASSERT_NO_THROW(listener_->startListening(io_service_));
@@ -283,13 +287,64 @@ public:
int error_count_;
};
/// @brief Text fixture for testing NameChangeUDPListener
class NameChangeUDPSenderBasicTest : public virtual ::testing::Test {
public:
NameChangeUDPSenderBasicTest() {
// Disable multi-threading
MultiThreadingMgr::instance().setMode(false);
}
~NameChangeUDPSenderBasicTest() {
// Disable multi-threading
MultiThreadingMgr::instance().setMode(false);
}
};
/// @brief Tests the NameChangeUDPSender constructors.
/// This test verifies that:
/// 1. Constructing with a max queue size of 0 is not allowed
/// 2. Given valid parameters, the sender constructor works
/// 3. Default construction provides default max queue size
/// 4. Construction with a custom max queue size works
TEST(NameChangeUDPSenderBasicTest, constructionTests) {
TEST_F(NameChangeUDPSenderBasicTest, constructionTests) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
uint32_t port = SENDER_PORT;
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Verify that constructing with an queue size of zero is not allowed.
EXPECT_THROW(NameChangeUDPSender(ip_address, port,
ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
NameChangeSenderPtr sender;
// Verify that valid constructor works.
EXPECT_NO_THROW(sender.reset(
new NameChangeUDPSender(ip_address, port, ip_address, port,
FMT_JSON, ncr_handler)));
// Verify that send queue default max is correct.
size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
EXPECT_EQ(expected, sender->getQueueMaxSize());
// Verify that constructor with a valid custom queue size works.
EXPECT_NO_THROW(sender.reset(
new NameChangeUDPSender(ip_address, port, ip_address, port,
FMT_JSON, ncr_handler, 100)));
EXPECT_EQ(100, sender->getQueueMaxSize());
}
/// @brief Tests the NameChangeUDPSender constructors.
/// This test verifies that:
/// 1. Constructing with a max queue size of 0 is not allowed
/// 2. Given valid parameters, the sender constructor works
/// 3. Default construction provides default max queue size
/// 4. Construction with a custom max queue size works
TEST_F(NameChangeUDPSenderBasicTest, constructionTestsMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
uint32_t port = SENDER_PORT;
isc::asiolink::IOService io_service;
@@ -318,8 +373,135 @@ TEST(NameChangeUDPSenderBasicTest, constructionTests) {
}
/// @brief Tests NameChangeUDPSender basic send functionality
/// This test verifies that:
TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
TEST_F(NameChangeUDPSenderBasicTest, basicSendTests) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Tests are based on a list of messages, get the count now.
int num_msgs = sizeof(valid_msgs)/sizeof(char*);
// Create the sender, setting the queue max equal to the number of
// messages we will have in the list.
NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
LISTENER_PORT, FMT_JSON, ncr_handler,
num_msgs, true);
// Verify that we can start sending.
EXPECT_NO_THROW(sender.startSending(io_service));
EXPECT_TRUE(sender.amSending());
// Verify that attempting to send when we already are is an error.
EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
// Verify that we can stop sending.
EXPECT_NO_THROW(sender.stopSending());
EXPECT_FALSE(sender.amSending());
// Verify that attempting to stop sending when we are not is ok.
EXPECT_NO_THROW(sender.stopSending());
// Verify that we can re-enter sending after stopping.
EXPECT_NO_THROW(sender.startSending(io_service));
EXPECT_TRUE(sender.amSending());
// Fetch the sender's select-fd.
int select_fd = sender.getSelectFd();
// Verify select_fd is valid and currently shows no ready to read.
ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd);
// Make sure select_fd does evaluates to not ready via select and
// that ioReady() method agrees.
ASSERT_EQ(0, selectCheck(select_fd));
ASSERT_FALSE(sender.ioReady());
// Iterate over a series of messages, sending each one. Since we
// do not invoke IOService::run, then the messages should accumulate
// in the queue.
NameChangeRequestPtr ncr;
NameChangeRequestPtr ncr2;
for (int i = 0; i < num_msgs; i++) {
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
EXPECT_NO_THROW(sender.sendRequest(ncr));
// Verify that the queue count increments in step with each send.
EXPECT_EQ(i+1, sender.getQueueSize());
// Verify that peekAt(i) returns the NCR we just added.
ASSERT_NO_THROW(ncr2 = sender.peekAt(i));
ASSERT_TRUE(ncr2);
EXPECT_TRUE(*ncr == *ncr2);
}
// Verify that attempting to peek beyond the end of the queue, throws.
ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError);
// Verify that attempting to send an additional message results in a
// queue full exception.
EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
// Loop for the number of valid messages. So long as there is at least
// on NCR in the queue, select-fd indicate ready to read. Invoke
// IOService::run_one. This should complete the send of exactly one
// message and the queue count should decrement accordingly.
for (int i = num_msgs; i > 0; i--) {
// Make sure select_fd does evaluates to ready via select and
// that ioReady() method agrees.
ASSERT_TRUE(selectCheck(select_fd) > 0);
ASSERT_TRUE(sender.ioReady());
// Execute at one ready handler.
ASSERT_NO_THROW(sender.runReadyIO());
// Verify that the queue count decrements in step with each run.
EXPECT_EQ(i-1, sender.getQueueSize());
}
// Make sure select_fd does evaluates to not ready via select and
// that ioReady() method agrees.
ASSERT_EQ(0, selectCheck(select_fd));
ASSERT_FALSE(sender.ioReady());
// Verify that the queue is empty.
EXPECT_EQ(0, sender.getQueueSize());
// Verify that we can add back to the queue
EXPECT_NO_THROW(sender.sendRequest(ncr));
EXPECT_EQ(1, sender.getQueueSize());
// Verify that we can remove the current entry at the front of the queue.
EXPECT_NO_THROW(sender.skipNext());
EXPECT_EQ(0, sender.getQueueSize());
// Verify that flushing the queue is not allowed in sending state.
EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
// Put num_msgs messages on the queue.
for (int i = 0; i < num_msgs; i++) {
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
EXPECT_NO_THROW(sender.sendRequest(ncr));
}
// Make sure we have number of messages expected.
EXPECT_EQ(num_msgs, sender.getQueueSize());
// Verify that we can gracefully stop sending.
EXPECT_NO_THROW(sender.stopSending());
EXPECT_FALSE(sender.amSending());
// Verify that the queue is preserved after leaving sending state.
EXPECT_EQ(num_msgs - 1, sender.getQueueSize());
// Verify that flushing the queue works when not sending.
EXPECT_NO_THROW(sender.clearSendQueue());
EXPECT_EQ(0, sender.getQueueSize());
}
/// @brief Tests NameChangeUDPSender basic send functionality
TEST_F(NameChangeUDPSenderBasicTest, basicSendTestsMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
@@ -445,7 +627,62 @@ TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
/// @brief Tests that sending gets kick-started if the queue isn't empty
/// when startSending is called.
TEST(NameChangeUDPSenderBasicTest, autoStart) {
TEST_F(NameChangeUDPSenderBasicTest, autoStart) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Tests are based on a list of messages, get the count now.
int num_msgs = sizeof(valid_msgs)/sizeof(char*);
// Create the sender, setting the queue max equal to the number of
// messages we will have in the list.
NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
LISTENER_PORT, FMT_JSON, ncr_handler,
num_msgs, true);
// Verify that we can start sending.
EXPECT_NO_THROW(sender.startSending(io_service));
EXPECT_TRUE(sender.amSending());
// Queue up messages.
NameChangeRequestPtr ncr;
for (int i = 0; i < num_msgs; i++) {
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
EXPECT_NO_THROW(sender.sendRequest(ncr));
}
// Make sure queue count is what we expect.
EXPECT_EQ(num_msgs, sender.getQueueSize());
// Stop sending.
ASSERT_NO_THROW(sender.stopSending());
ASSERT_FALSE(sender.amSending());
// We should have completed the first message only.
EXPECT_EQ(--num_msgs, sender.getQueueSize());
// Restart sending.
EXPECT_NO_THROW(sender.startSending(io_service));
// We should be able to loop through remaining messages and send them.
for (int i = num_msgs; i > 0; i--) {
// ioReady() should evaluate to true.
ASSERT_TRUE(sender.ioReady());
// Execute at one ready handler.
ASSERT_NO_THROW(sender.runReadyIO());
}
// Verify that the queue is empty.
EXPECT_EQ(0, sender.getQueueSize());
}
/// @brief Tests that sending gets kick-started if the queue isn't empty
/// when startSending is called.
TEST_F(NameChangeUDPSenderBasicTest, autoStartMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
@@ -496,7 +733,45 @@ TEST(NameChangeUDPSenderBasicTest, autoStart) {
}
/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
TEST(NameChangeUDPSenderBasicTest, anyAddressSend) {
TEST_F(NameChangeUDPSenderBasicTest, anyAddressSend) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOAddress any_address("0.0.0.0");
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Tests are based on a list of messages, get the count now.
int num_msgs = sizeof(valid_msgs)/sizeof(char*);
// Create the sender, setting the queue max equal to the number of
// messages we will have in the list.
NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT,
FMT_JSON, ncr_handler, num_msgs);
// Enter send mode.
ASSERT_NO_THROW(sender.startSending(io_service));
EXPECT_TRUE(sender.amSending());
// Create and queue up a message.
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
EXPECT_NO_THROW(sender.sendRequest(ncr));
EXPECT_EQ(1, sender.getQueueSize());
// Verify we have a ready IO, then execute at one ready handler.
ASSERT_TRUE(sender.ioReady());
ASSERT_NO_THROW(sender.runReadyIO());
// Verify that sender shows no IO ready.
// and that the queue is empty.
ASSERT_FALSE(sender.ioReady());
EXPECT_EQ(0, sender.getQueueSize());
}
/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
TEST_F(NameChangeUDPSenderBasicTest, anyAddressSendMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOAddress any_address("0.0.0.0");
isc::asiolink::IOService io_service;
@@ -531,7 +806,79 @@ TEST(NameChangeUDPSenderBasicTest, anyAddressSend) {
}
/// @brief Test the NameChangeSender::assumeQueue method.
TEST(NameChangeSender, assumeQueue) {
TEST_F(NameChangeUDPSenderBasicTest, assumeQueue) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
uint32_t port = SENDER_PORT;
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
NameChangeRequestPtr ncr;
// Tests are based on a list of messages, get the count now.
int num_msgs = sizeof(valid_msgs)/sizeof(char*);
// Create two senders with queue max equal to the number of
// messages we will have in the list.
NameChangeUDPSender sender1(ip_address, port, ip_address, port,
FMT_JSON, ncr_handler, num_msgs);
NameChangeUDPSender sender2(ip_address, port+1, ip_address, port,
FMT_JSON, ncr_handler, num_msgs);
// Place sender1 into send mode and queue up messages.
ASSERT_NO_THROW(sender1.startSending(io_service));
for (int i = 0; i < num_msgs; i++) {
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
ASSERT_NO_THROW(sender1.sendRequest(ncr));
}
// Make sure sender1's queue count is as expected.
ASSERT_EQ(num_msgs, sender1.getQueueSize());
// Verify sender1 is sending, sender2 is not.
ASSERT_TRUE(sender1.amSending());
ASSERT_FALSE(sender2.amSending());
// Transfer from sender1 to sender2 should fail because
// sender1 is in send mode.
ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
// Take sender1 out of send mode.
ASSERT_NO_THROW(sender1.stopSending());
ASSERT_FALSE(sender1.amSending());
// Stopping should have completed the first message.
--num_msgs;
EXPECT_EQ(num_msgs, sender1.getQueueSize());
// Transfer should succeed. Verify sender1 has none,
// and sender2 has num_msgs queued.
EXPECT_NO_THROW(sender2.assumeQueue(sender1));
EXPECT_EQ(0, sender1.getQueueSize());
EXPECT_EQ(num_msgs, sender2.getQueueSize());
// Reduce sender1's max queue size.
ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1));
// Transfer should fail as sender1's queue is not large enough.
ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError);
// Place sender1 into send mode and queue up a message.
ASSERT_NO_THROW(sender1.startSending(io_service));
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
ASSERT_NO_THROW(sender1.sendRequest(ncr));
// Take sender1 out of send mode.
ASSERT_NO_THROW(sender1.stopSending());
// Try to transfer from sender1 to sender2. This should fail
// as sender2's queue is not empty.
ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
}
/// @brief Test the NameChangeSender::assumeQueue method.
TEST_F(NameChangeUDPSenderBasicTest, assumeQueueMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
uint32_t port = SENDER_PORT;
isc::asiolink::IOService io_service;
@@ -634,6 +981,13 @@ public:
test_timer_.setup(boost::bind(&NameChangeUDPTest::testTimeoutHandler,
this),
TEST_TIMEOUT);
// Disble multi-threading
MultiThreadingMgr::instance().setMode(false);
}
~NameChangeUDPTest() {
// Disble multi-threading
MultiThreadingMgr::instance().setMode(false);
}
void reset_results() {
@@ -670,7 +1024,63 @@ public:
/// Conducts a "round-trip" test using a sender to transmit a set of valid
/// NCRs to a listener. The test verifies that what was sent matches what
/// was received both in quantity and in content.
TEST_F (NameChangeUDPTest, roundTripTest) {
TEST_F(NameChangeUDPTest, roundTripTest) {
// Place the listener into listening state.
ASSERT_NO_THROW(listener_->startListening(io_service_));
EXPECT_TRUE(listener_->amListening());
// Get the number of messages in the list of test messages.
int num_msgs = sizeof(valid_msgs)/sizeof(char*);
// Place the sender into sending state.
ASSERT_NO_THROW(sender_->startSending(io_service_));
EXPECT_TRUE(sender_->amSending());
for (int i = 0; i < num_msgs; i++) {
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
sender_->sendRequest(ncr);
EXPECT_EQ(i+1, sender_->getQueueSize());
}
// Execute callbacks until we have sent and received all of messages.
while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
EXPECT_NO_THROW(io_service_.run_one());
}
// Send queue should be empty.
EXPECT_EQ(0, sender_->getQueueSize());
// We should have the same number of sends and receives as we do messages.
ASSERT_EQ(num_msgs, sent_ncrs_.size());
ASSERT_EQ(num_msgs, received_ncrs_.size());
// Verify that what we sent matches what we received.
for (int i = 0; i < num_msgs; i++) {
EXPECT_TRUE (checkSendVsReceived(sent_ncrs_[i], received_ncrs_[i]));
}
// Verify that we can gracefully stop listening.
EXPECT_NO_THROW(listener_->stopListening());
EXPECT_FALSE(listener_->amListening());
// Verify that IO pending is false, after cancel event occurs.
EXPECT_NO_THROW(io_service_.run_one());
EXPECT_FALSE(listener_->isIoPending());
// Verify that we can gracefully stop sending.
EXPECT_NO_THROW(sender_->stopSending());
EXPECT_FALSE(sender_->amSending());
}
/// @brief Uses a sender and listener to test UDP-based NCR delivery
/// Conducts a "round-trip" test using a sender to transmit a set of valid
/// NCRs to a listener. The test verifies that what was sent matches what
/// was received both in quantity and in content.
TEST_F(NameChangeUDPTest, roundTripTestMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
// Place the listener into listening state.
ASSERT_NO_THROW(listener_->startListening(io_service_));
EXPECT_TRUE(listener_->amListening());
@@ -721,7 +1131,42 @@ TEST_F (NameChangeUDPTest, roundTripTest) {
// Tests error handling of a failure to mark the watch socket ready, when
// sendRequest() is called.
TEST(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Create the sender and put into send mode.
NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
FMT_JSON, ncr_handler, 100, true);
ASSERT_NO_THROW(sender.startSending(io_service));
ASSERT_TRUE(sender.amSending());
// Create an NCR.
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
// Tamper with the watch socket by closing the select-fd.
close(sender.getSelectFd());
// Send should fail as we interfered by closing the select-fd.
ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
// Verify we didn't invoke the handler.
EXPECT_EQ(0, ncr_handler.pass_count_);
EXPECT_EQ(0, ncr_handler.error_count_);
// Request remains in the queue. Technically it was sent but its
// completion handler won't get called.
EXPECT_EQ(1, sender.getQueueSize());
}
// Tests error handling of a failure to mark the watch socket ready, when
// sendRequest() is called.
TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequestMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
@@ -753,7 +1198,7 @@ TEST(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
// Tests error handling of a failure to mark the watch socket ready, when
// sendNext() is called during completion handling.
TEST(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
@@ -790,9 +1235,103 @@ TEST(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
EXPECT_EQ(1, sender.getQueueSize());
}
// Tests error handling of a failure to mark the watch socket ready, when
// sendNext() is called during completion handling.
TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequestMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Create the sender and put into send mode.
NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
FMT_JSON, ncr_handler, 100, true);
ASSERT_NO_THROW(sender.startSending(io_service));
ASSERT_TRUE(sender.amSending());
// Build and queue up 2 messages. No handlers will get called yet.
for (int i = 0; i < 2; i++) {
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
sender.sendRequest(ncr);
EXPECT_EQ(i+1, sender.getQueueSize());
}
// Tamper with the watch socket by closing the select-fd.
close (sender.getSelectFd());
// Run one handler. This should execute the send completion handler
// after sending the first message. Doing completion handling, we will
// attempt to queue the second message which should fail.
ASSERT_NO_THROW(sender.runReadyIO());
// Verify handler got called twice. First request should have be sent
// without error, second call should have failed to send due to watch
// socket markReady failure.
EXPECT_EQ(1, ncr_handler.pass_count_);
EXPECT_EQ(1, ncr_handler.error_count_);
// The second request should still be in the queue.
EXPECT_EQ(1, sender.getQueueSize());
}
// Tests error handling of a failure to clear the watch socket during
// completion handling.
TEST(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;
// Create the sender and put into send mode.
NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
FMT_JSON, ncr_handler, 100, true);
ASSERT_NO_THROW(sender.startSending(io_service));
ASSERT_TRUE(sender.amSending());
// Build and queue up 2 messages. No handlers will get called yet.
for (int i = 0; i < 2; i++) {
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
sender.sendRequest(ncr);
EXPECT_EQ(i+1, sender.getQueueSize());
}
// Fetch the sender's select-fd.
int select_fd = sender.getSelectFd();
// Verify that select_fd appears ready.
ASSERT_TRUE(selectCheck(select_fd) > 0);
// Interfere by reading part of the marker from the select-fd.
uint32_t buf = 0;
ASSERT_EQ((read (select_fd, &buf, 1)), 1);
ASSERT_NE(util::WatchSocket::MARKER, buf);
// Run one handler. This should execute the send completion handler
// after sending the message. Doing completion handling clearing the
// watch socket should fail, which will close the socket, but not
// result in a throw.
ASSERT_NO_THROW(sender.runReadyIO());
// Verify handler got called twice. First request should have be sent
// without error, second call should have failed to send due to watch
// socket markReady failure.
EXPECT_EQ(1, ncr_handler.pass_count_);
EXPECT_EQ(1, ncr_handler.error_count_);
// The second request should still be in the queue.
EXPECT_EQ(1, sender.getQueueSize());
}
// Tests error handling of a failure to clear the watch socket during
// completion handling.
TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadReadMultiThreading) {
// Enable multi-threading
MultiThreadingMgr::instance().setMode(true);
isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
isc::asiolink::IOService io_service;
SimpleSendHandler ncr_handler;