Update the MessageQueue and add unit test for it. (#1089)

This commit makes the following changes:

- It adds a unit test for testing different `MessageQueue` operations.

- It modifies how the `MessageQueue` determines the lists (the `kListAll`
  is maintained by the message pool and the `kListInterface` is accessed
  from the `MessageQueue` associated with the list).

- It modifies the underlying list implementation to use a circular doubly
  linked-list.
This commit is contained in:
Abtin Keshavarzian
2016-12-20 08:46:58 -08:00
committed by Jonathan Hui
parent 97fbca2063
commit 9d3bd7802b
8 changed files with 303 additions and 112 deletions
+1
View File
@@ -69,6 +69,7 @@
<ClCompile Include="..\..\tests\unit\test_lowpan.cpp" />
<ClCompile Include="..\..\tests\unit\test_mac_frame.cpp" />
<ClCompile Include="..\..\tests\unit\test_message.cpp" />
<ClCompile Include="..\..\tests\unit\test_message_queue.cpp" />
<ClCompile Include="..\..\tests\unit\test_ncp_buffer.cpp" />
<ClCompile Include="..\..\tests\unit\test_platform.cpp" />
<ClCompile Include="..\..\tests\unit\test_timer.cpp" />
@@ -36,6 +36,9 @@
<ClCompile Include="..\..\tests\unit\test_message.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\tests\unit\test_message_queue.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\tests\unit\test_timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+69 -61
View File
@@ -44,7 +44,7 @@ namespace Thread {
MessagePool::MessagePool(void)
{
memset(mBuffers, 0, sizeof(mBuffers));
memset(&mAll, 0, sizeof(mAll));
mAllListTail = NULL;
mFreeBuffers = mBuffers;
@@ -87,11 +87,21 @@ exit:
ThreadError MessagePool::Free(Message *aMessage)
{
assert(aMessage->GetMessageList(MessageInfo::kListAll).mList == NULL &&
aMessage->GetMessageList(MessageInfo::kListInterface).mList == NULL);
assert(aMessage->Next(MessageInfo::kListAll) == NULL &&
aMessage->Prev(MessageInfo::kListAll) == NULL);
assert(aMessage->Next(MessageInfo::kListInterface) == NULL &&
aMessage->Prev(MessageInfo::kListInterface) == NULL);
return FreeBuffers(static_cast<Buffer *>(aMessage));
}
Message *MessagePool::GetAllMessagesListHead(void) const
{
return (mAllListTail == NULL) ? NULL : mAllListTail->Next(MessageInfo::kListAll);
}
Buffer *MessagePool::NewBuffer(void)
{
Buffer *buffer = NULL;
@@ -186,7 +196,9 @@ ThreadError Message::Free(void)
Message *Message::GetNext(void) const
{
return GetMessageList(MessageInfo::kListInterface).mNext;
assert(GetMessageQueue() != NULL);
return (this == GetMessageQueue()->mTail) ? NULL : Next(MessageInfo::kListInterface);
}
uint16_t Message::GetLength(void) const
@@ -740,94 +752,90 @@ void Message::SetReserved(uint16_t aReserved)
mInfo.mReserved = aReserved;
}
MessageQueue::MessageQueue(void)
MessageQueue::MessageQueue(void) :
mTail(NULL)
{
mInterface.mHead = NULL;
mInterface.mTail = NULL;
}
ThreadError MessageQueue::AddToList(uint8_t aList, Message &aMessage)
void MessageQueue::AddToList(Message *&aListTail, uint8_t aList, Message &aMessage)
{
MessageList *list;
Message *head;
assert(aMessage.GetMessageList(aList).mNext == NULL &&
aMessage.GetMessageList(aList).mPrev == NULL &&
aMessage.GetMessageList(aList).mList != NULL);
assert((aMessage.Next(aList) == NULL) && (aMessage.Prev(aList) == NULL));
list = aMessage.GetMessageList(aList).mList;
if (list->mHead == NULL)
if (aListTail == NULL)
{
list->mHead = &aMessage;
list->mTail = &aMessage;
aMessage.Next(aList) = &aMessage;
aMessage.Prev(aList) = &aMessage;
}
else
{
list->mTail->GetMessageList(aList).mNext = &aMessage;
aMessage.GetMessageList(aList).mPrev = list->mTail;
list->mTail = &aMessage;
head = aListTail->Next(aList);
aMessage.Next(aList) = head;
aMessage.Prev(aList) = aListTail;
head->Prev(aList) = &aMessage;
aListTail->Next(aList) = &aMessage;
}
return kThreadError_None;
aListTail = &aMessage;
}
ThreadError MessageQueue::RemoveFromList(uint8_t aList, Message &aMessage)
void MessageQueue::RemoveFromList(Message *&aListTail, uint8_t aList, Message &aMessage)
{
MessageList *list;
assert((aMessage.Next(aList) != NULL) && (aMessage.Prev(aList) != NULL));
assert(aMessage.GetMessageList(aList).mList != NULL);
list = aMessage.GetMessageList(aList).mList;
assert(list->mHead == &aMessage ||
aMessage.GetMessageList(aList).mNext != NULL ||
aMessage.GetMessageList(aList).mPrev != NULL);
if (aMessage.GetMessageList(aList).mPrev)
if (&aMessage == aListTail)
{
aMessage.GetMessageList(aList).mPrev->GetMessageList(aList).mNext = aMessage.GetMessageList(aList).mNext;
}
else
{
list->mHead = aMessage.GetMessageList(aList).mNext;
aListTail = aListTail->Prev(aList);
if (&aMessage == aListTail)
{
aListTail = NULL;
}
}
if (aMessage.GetMessageList(aList).mNext)
{
aMessage.GetMessageList(aList).mNext->GetMessageList(aList).mPrev = aMessage.GetMessageList(aList).mPrev;
}
else
{
list->mTail = aMessage.GetMessageList(aList).mPrev;
}
aMessage.Prev(aList)->Next(aList) = aMessage.Next(aList);
aMessage.Next(aList)->Prev(aList) = aMessage.Prev(aList);
aMessage.GetMessageList(aList).mPrev = NULL;
aMessage.GetMessageList(aList).mNext = NULL;
return kThreadError_None;
aMessage.Prev(aList) = NULL;
aMessage.Next(aList) = NULL;
}
Message *MessageQueue::GetHead(void) const
{
return mInterface.mHead;
return (mTail == NULL) ? NULL : mTail->Next(MessageInfo::kListInterface);
}
ThreadError MessageQueue::Enqueue(Message &aMessage)
{
aMessage.GetMessageList(MessageInfo::kListAll).mList = &aMessage.GetMessagePool()->mAll;
aMessage.GetMessageList(MessageInfo::kListInterface).mList = &mInterface;
AddToList(MessageInfo::kListAll, aMessage);
AddToList(MessageInfo::kListInterface, aMessage);
return kThreadError_None;
ThreadError error = kThreadError_None;
VerifyOrExit(aMessage.GetMessageQueue() == NULL, error = kThreadError_Already);
aMessage.SetMessageQueue(this);
AddToList(aMessage.GetMessagePool()->mAllListTail, MessageInfo::kListAll, aMessage);
AddToList(mTail, MessageInfo::kListInterface, aMessage);
exit:
return error;
}
ThreadError MessageQueue::Dequeue(Message &aMessage)
{
RemoveFromList(MessageInfo::kListAll, aMessage);
RemoveFromList(MessageInfo::kListInterface, aMessage);
aMessage.GetMessageList(MessageInfo::kListAll).mList = NULL;
aMessage.GetMessageList(MessageInfo::kListInterface).mList = NULL;
return kThreadError_None;
ThreadError error = kThreadError_None;
VerifyOrExit(aMessage.GetMessageQueue() == this, error = kThreadError_NotFound);
RemoveFromList(aMessage.GetMessagePool()->mAllListTail, MessageInfo::kListAll, aMessage);
RemoveFromList(mTail, MessageInfo::kListInterface, aMessage);
aMessage.SetMessageQueue(NULL);
exit:
return error;
}
void MessageQueue::GetInfo(uint16_t &aMessageCount, uint16_t &aBufferCount) const
@@ -835,7 +843,7 @@ void MessageQueue::GetInfo(uint16_t &aMessageCount, uint16_t &aBufferCount) cons
aMessageCount = 0;
aBufferCount = 0;
for (const Message *message = mInterface.mHead; message; message = message->GetNext())
for (const Message *message = GetHead(); message != NULL; message = message->GetNext())
{
aMessageCount++;
aBufferCount += message->GetBufferCount();
+75 -47
View File
@@ -69,29 +69,7 @@ enum
class Message;
class MessagePool;
/**
* This structure contains pointers to the head and tail of a Message list.
*
*/
struct MessageList
{
Message *mHead; ///< A pointer to the first Message in the list.
Message *mTail; ///< A pointer to the last Message in the list.
};
/**
* This structure contains pointers to the MessageList structure, the next Message, and previous Message.
*
*/
struct MessageListEntry
{
struct MessageList *mList; ///< A pointer to the MessageList structure for the list.
Message *mNext; ///< A pointer to the next Message in the list.
Message *mPrev; ///< A pointer to the previous Message in the list.
};
class MessageQueue;
/**
* This structure contains metdata about a Message.
@@ -99,13 +77,18 @@ struct MessageListEntry
*/
struct MessageInfo
{
MessagePool *mMessagePool; ///< Identifies the message pool for this message.
enum
{
kListAll = 0, ///< Identifies the all messages list.
kListInterface = 1, ///< Identifies the per-interface message list.
kListAll = 0, ///< Identifies the all messages list (maintained by the MessagePool).
kListInterface = 1, ///< Identifies the list for per-interface message queue.
kNumLists = 2, ///< Number of lists.
};
MessageListEntry mList[2]; ///< Message lists.
Message *mNext[kNumLists]; ///< A pointer to the next Message in a doubly linked list.
Message *mPrev[kNumLists]; ///< A pointer to the previous Message in a doubly linked list.
MessagePool *mMessagePool; ///< Identifies the message pool for this message.
MessageQueue *mMessageQueue; ///< Identifies the message queue (if any) where this message is queued.
uint16_t mReserved; ///< Number of header bytes reserved for the message.
uint16_t mLength; ///< Number of bytes within the message.
uint16_t mOffset; ///< A byte offset within the message.
@@ -586,29 +569,67 @@ public:
uint16_t UpdateChecksum(uint16_t aChecksum, uint16_t aOffset, uint16_t aLength) const;
private:
/**
* This method returns a pointer to the message pool to which this message belongs
*
* @returns A pointer to the message pool.
*
*/
MessagePool *GetMessagePool(void) const { return mInfo.mMessagePool; }
/**
* This method sets the message pool this message to which this message belongs.
*
* @param[in] aMessagePool A pointer to the message pool
*
*/
void SetMessagePool(MessagePool *aMessagePool) { mInfo.mMessagePool = aMessagePool; }
/**
* This method returns a reference to a message list.
* This method returns a pointer to the message queue (if any) where this message is queued.
*
* @param[in] aList The message list.
*
* @returns A reference to a message list.
*
*/
MessageListEntry &GetMessageList(uint8_t aList) { return mInfo.mList[aList]; }
MessageQueue *GetMessageQueue(void) const { return mInfo.mMessageQueue; }
/**
* This method returns a reference to a message list.
* This method sets the message queue information for the message.
*
* @param[in] aList The message list.
*
* @returns A reference to a message list.
* @param[in] aMessageQueue A pointer to the message queue where this message is queued.
*
*/
const MessageListEntry &GetMessageList(uint8_t aList) const { return mInfo.mList[aList]; }
void SetMessageQueue(MessageQueue *aMessageQueue) { mInfo.mMessageQueue = aMessageQueue; }
/**
* This method returns a reference to the `mNext` pointer for a given list.
*
* @param[in] aList The index to the message list.
*
* @returns A reference to the mNext pointer for the specified list.
*
*/
Message *&Next(uint8_t aList) { return mInfo.mNext[aList]; }
/**
* This method returns a const reference to the `mNext` pointer for a given list.
*
* @param[in] aList The index to the message list.
*
* @returns A const reference to the mNext pointer for the specified list.
*
*/
Message *const &Next(uint8_t aList) const { return mInfo.mNext[aList]; }
/**
* This method returns a reference to the `mPrev` pointer for a given list.
*
* @param[in] aList The index to the message list.
*
* @returns A reference to the mPrev pointer for the specified list.
*
*/
Message *&Prev(uint8_t aList) { return mInfo.mPrev[aList]; }
/**
* This method returns the number of reserved header bytes.
@@ -652,6 +673,8 @@ private:
*/
class MessageQueue
{
friend class Message;
public:
/**
* This constructor initializes the message queue.
@@ -699,31 +722,28 @@ public:
void GetInfo(uint16_t &aMessageCount, uint16_t &aBufferCount) const;
private:
/**
* This static method adds a message to a list.
*
* @param[in] aListTail A reference to the list tail pointer.
* @param[in] aListId The list to add @p aMessage to.
* @param[in] aMessage The message to add to @p aListId.
*
* @retval kThreadError_None Successfully added the message to the list.
* @retval kThreadError_Already The message is already enqueued in a list.
*
*/
static ThreadError AddToList(uint8_t aListId, Message &aMessage);
static void AddToList(Message *&aListTail, uint8_t aListId, Message &aMessage);
/**
* This static method removes a message from a list.
*
* @param[in] aListTail A reference to the list tale pointer.
* @param[in] aListId The list to add @p aMessage to.
* @param[in] aMessage The message to add to @p aListId.
*
* @retval kThreadError_None Successfully added the message to the list.
* @retval kThreadError_NotFound The message is not enqueued in the list.
*
*/
static ThreadError RemoveFromList(uint8_t aListId, Message &aMessage);
static void RemoveFromList(Message *&aListTail, uint8_t aListId, Message &aMessage);
MessageList mInterface; ///< The instance-specific message list.
Message *mTail; ///< A pointer to the last Message in the list.
};
class MessagePool
@@ -760,6 +780,14 @@ public:
*/
ThreadError Free(Message *aMessage);
/**
* This method returns a pointer to the first message in the all-messages list.
*
* @returns A pointer to the first message.
*
*/
Message *GetAllMessagesListHead(void) const;
/**
* This method returns the number of free buffers.
*
@@ -776,7 +804,7 @@ private:
int mNumFreeBuffers;
Buffer mBuffers[kNumBuffers];
Buffer *mFreeBuffers;
MessageList mAll;
Message *mAllListTail;
};
/**
+1 -1
View File
@@ -1059,7 +1059,7 @@ exit:
return rval;
}
otInstance *Ip6::GetInstance()
otInstance *Ip6::GetInstance(void)
{
return otInstanceFromIp6(this);
}
+4
View File
@@ -80,6 +80,7 @@ check_PROGRAMS = \
test-link-quality \
test-mac-frame \
test-message \
test-message-queue \
test-timer \
test-toolchain \
$(NULL)
@@ -138,6 +139,9 @@ test_mac_frame_SOURCES = test_platform.cpp test_mac_frame.cpp
test_message_LDADD = $(COMMON_LDADD)
test_message_SOURCES = test_platform.cpp test_message.cpp
test_message_queue_LDADD = $(COMMON_LDADD)
test_message_queue_SOURCES = test_platform.cpp test_message_queue.cpp
test_ncp_buffer_LDADD = $(COMMON_LDADD)
test_ncp_buffer_SOURCES = test_platform.cpp test_ncp_buffer.cpp
+141
View File
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2016, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "test_util.h"
#include <openthread.h>
#include <common/debug.hpp>
#include <common/message.hpp>
#include <string.h>
#include <stdarg.h>
#define kNumMessages 5
// This function verifies the content of the message to matches the passed in messages
void VerifyMessageQueueContent(Thread::MessageQueue &aMessageQueue, int aExpectedLength, ...)
{
va_list args;
Thread::Message *message;
Thread::Message *msgArg;
va_start(args, aExpectedLength);
if (aExpectedLength == 0)
{
message = aMessageQueue.GetHead();
VerifyOrQuit(message == NULL, "MessageQueue is not empty when expected len is zero.");
}
else
{
for (message = aMessageQueue.GetHead(); message != NULL; message = message->GetNext())
{
VerifyOrQuit(aExpectedLength != 0, "MessageQueue contains more entries than expected");
msgArg = va_arg(args, Thread::Message *);
VerifyOrQuit(msgArg == message, "MessageQueue content does not match what is expected.");
aExpectedLength--;
}
VerifyOrQuit(aExpectedLength == 0, "MessageQueue contains less entries than expected");
}
va_end(args);
}
void TestMessageQueue(void)
{
Thread::MessagePool messagePool;
Thread::MessageQueue messageQueue;
Thread::Message *msg[kNumMessages];
for (int i = 0; i < kNumMessages; i++)
{
msg[i] = messagePool.New(Thread::Message::kTypeIp6, 0);
VerifyOrQuit(msg[i] != NULL, "Message::New failed\n");
}
VerifyMessageQueueContent(messageQueue, 0);
//printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
//printf("\nEnqueue/Dequeue one message");
// Enqueue 1 message and remove it
SuccessOrQuit(messageQueue.Enqueue(*msg[0]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 1, msg[0]);
SuccessOrQuit(messageQueue.Dequeue(*msg[0]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 0);
// Enqueue 5 messages
SuccessOrQuit(messageQueue.Enqueue(*msg[0]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 1, msg[0]);
SuccessOrQuit(messageQueue.Enqueue(*msg[1]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 2, msg[0], msg[1]);
SuccessOrQuit(messageQueue.Enqueue(*msg[2]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 3, msg[0], msg[1], msg[2]);
SuccessOrQuit(messageQueue.Enqueue(*msg[3]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 4, msg[0], msg[1], msg[2], msg[3]);
SuccessOrQuit(messageQueue.Enqueue(*msg[4]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 5, msg[0], msg[1], msg[2], msg[3], msg[4]);
// Remove from head
SuccessOrQuit(messageQueue.Dequeue(*msg[0]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 4, msg[1], msg[2], msg[3], msg[4]);
// Remove a message in middle
SuccessOrQuit(messageQueue.Dequeue(*msg[3]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 3, msg[1], msg[2], msg[4]);
// Remove from tail
SuccessOrQuit(messageQueue.Dequeue(*msg[4]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 2, msg[1], msg[2]);
// Add after removes
SuccessOrQuit(messageQueue.Enqueue(*msg[0]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 3, msg[1], msg[2], msg[0]);
SuccessOrQuit(messageQueue.Enqueue(*msg[3]), "MessageQueue::Enqueue() failed.");
VerifyMessageQueueContent(messageQueue, 4, msg[1], msg[2], msg[0], msg[3]);
// Remove all messages
SuccessOrQuit(messageQueue.Dequeue(*msg[2]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 3, msg[1], msg[0], msg[3]);
SuccessOrQuit(messageQueue.Dequeue(*msg[1]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 2, msg[0], msg[3]);
SuccessOrQuit(messageQueue.Dequeue(*msg[3]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 1, msg[0]);
SuccessOrQuit(messageQueue.Dequeue(*msg[0]), "MessageQueue::Dequeue() failed.");
VerifyMessageQueueContent(messageQueue, 0);
}
#ifdef ENABLE_TEST_MAIN
int main(void)
{
TestMessageQueue();
printf("All tests passed\n");
return 0;
}
#endif
+9 -3
View File
@@ -65,6 +65,9 @@ namespace Thread
// test_message.cpp
void TestMessage();
// test_message_queue.cpp
void TestMessageQueue();
// test_ncp_buffer.cpp
namespace Thread
{
@@ -92,7 +95,7 @@ utAssertTrue s_AssertTrue;
utLogMessage s_LogMessage;
namespace Thread
{
{
TEST_CLASS(UnitTests)
{
public:
@@ -114,7 +117,7 @@ namespace Thread
va_start(args, format);
vsnprintf(message, sizeof(message), format, args);
va_end(args);
Logger::WriteMessage(message);
}
@@ -145,7 +148,10 @@ namespace Thread
// test_message.cpp
TEST_METHOD(TestMessage) { ::TestMessage(); }
// test_message.cpp
// test_message_queue.cpp
TEST_METHOD(TestMessageQueue) { ::TestMessageQueue(); }
// test_timer.cpp
TEST_METHOD(TestOneTimer) { ::TestOneTimer(); }
TEST_METHOD(TestTenTimers) { ::TestTenTimers(); }