Selaa lähdekoodia

add tests for ReadWriteLock

Kolja Strohm 1 viikko sitten
vanhempi
commit
a10776849c

+ 42 - 1
Critical.cpp

@@ -432,4 +432,45 @@ Lock& Framework::ReadWriteLock::getReadLock() const
 Lock& Framework::ReadWriteLock::getWriteLock() const
 {
     return *writeLock;
-}
+}
+
+int Framework::ReadWriteLock::getReadThreadCount() const
+{
+    return readerThreadCount;
+}
+
+bool Framework::ReadWriteLock::isWriteLocked() const
+{
+    return writerCount > 0;
+}
+
+int Framework::ReadWriteLock::getReadThread(int index) const
+{
+    if (index < 0 || index >= readerThreadCount)
+    {
+        throw "Index out of bounds";
+    }
+    return readerThreads[index];
+}
+
+int Framework::ReadWriteLock::getWriteLockThread() const
+{
+    return writerThread;
+}
+
+int Framework::ReadWriteLock::getWriteLockCount() const
+{
+    return writerCount;
+}
+
+int Framework::ReadWriteLock::getReadLockCount(int threadId) const
+{
+    for (int i = 0; i < readerThreadCount; i++)
+    {
+        if (readerThreads[i] == threadId)
+        {
+            return readCounters[i];
+        }
+    }
+    return 0;
+}

+ 6 - 0
Critical.h

@@ -105,5 +105,11 @@ namespace Framework
         DLLEXPORT bool tryLockWrite();
         DLLEXPORT Lock& getReadLock() const;
         DLLEXPORT Lock& getWriteLock() const;
+        DLLEXPORT int getReadThreadCount() const;
+        DLLEXPORT bool isWriteLocked() const;
+        DLLEXPORT int getWriteLockThread() const;
+        DLLEXPORT int getReadThread(int index) const;
+        DLLEXPORT int getWriteLockCount() const;
+        DLLEXPORT int getReadLockCount(int threadId) const;
     };
 } // namespace Framework

+ 297 - 0
Framework Tests/Critical.cpp

@@ -0,0 +1,297 @@
+#include "pch.h"
+
+#define NO_MAIN
+#include <AsynchronCall.h>
+#include <Critical.h>
+#include <Globals.h>
+
+#include "CppUnitTest.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+namespace FrameworkTests
+{
+    TEST_CLASS (ReadWriteLockTest)
+    {
+    public:
+        ReadWriteLockTest()
+        {
+            Framework::initFramework(0);
+        }
+
+        ~ReadWriteLockTest()
+        {
+            Framework::releaseFramework();
+        }
+
+        TEST_METHOD (testLockRead)
+        {
+            Framework::ReadWriteLock lock;
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 0,
+                L"The current threads lock holding count should be 0 at the "
+                L"beginning");
+            Assert::IsTrue(lock.getReadThreadCount() == 0,
+                L"Initialized ReadWriteLock should not have any readers.");
+            lock.lockRead();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 1,
+                L"The current threads lock holding count should be 1 after "
+                L"lockRead() was called");
+            Assert::IsTrue(lock.getReadThreadCount() == 1,
+                L"ReadWriteLock should have exactly one reader after "
+                L"lockRead() was called.");
+            Assert::IsTrue(
+                lock.getReadThread(0) == Framework::getCurrentThreadId(),
+                L"Current thread id is not present in reader threads after "
+                L"lockRead() was called");
+            Assert::IsTrue(
+                lock.getReadLockCount(Framework::getCurrentThreadId()) == 1,
+                L"Lock count should be 1 after lockRead() was called once");
+            lock.lockRead();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 2,
+                L"The current threads lock holding count should be 2 after "
+                L"lockRead() was called twice");
+            Assert::IsTrue(lock.getReadThreadCount() == 1,
+                L"ReadWriteLock should have exactly one reader after "
+                L"lockRead() was called twice by the same thread.");
+            Assert::IsTrue(
+                lock.getReadThread(0) == Framework::getCurrentThreadId(),
+                L"Current thread id is not present in reader threads after "
+                L"lockRead() was called twice by the same thread");
+            Assert::IsTrue(
+                lock.getReadLockCount(Framework::getCurrentThreadId()) == 2,
+                L"Lock count should be 1 after lockRead() was called twice");
+        }
+
+        TEST_METHOD (testUnlockRead)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockRead();
+            lock.lockRead();
+            lock.unlockRead();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 1,
+                L"The current threads lock holding count should be 1 after "
+                L"lockRead() was called twice and unlockRead() once");
+            Assert::IsTrue(lock.getReadThreadCount() == 1,
+                L"ReadWriteLock should have exactly one reader after "
+                L"lockRead() was called twice and unlockRead() once by the "
+                L"same thread.");
+            Assert::IsTrue(
+                lock.getReadThread(0) == Framework::getCurrentThreadId(),
+                L"Current thread id is not present in reader threads after "
+                L"lockRead() was called twice and unlockRead() once by the "
+                L"same thread.");
+            Assert::IsTrue(
+                lock.getReadLockCount(Framework::getCurrentThreadId()) == 1,
+                L"Lock count should be 1 after lockRead() was called twice and "
+                L"unlockRead() once by the same thread.");
+            lock.unlockRead();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 0,
+                L"The current threads lock holding count should be 0 after "
+                L"lockRead() was called twice and unlockRead() twice");
+            Assert::IsTrue(lock.getReadThreadCount() == 0,
+                L"ReadWriteLock should have 0 readers after "
+                L"lockRead() was called twice and unlockRead() twice by the "
+                L"same thread.");
+            lock.lockRead();
+            Assert::IsTrue(lock.getReadThreadCount() == 1,
+                L"ReadWriteLock should have exactly one reader after "
+                L"lockRead() was called 3 times and unlockRead() twice by the "
+                L"same thread.");
+            lock.unlockRead();
+            Assert::IsTrue(lock.getReadThreadCount() == 0,
+                L"ReadWriteLock should have 0 readers after "
+                L"lockRead() was called 3 times and unlockRead() 3 times by "
+                L"the "
+                L"same thread.");
+        }
+
+        TEST_METHOD (testLockWrite)
+        {
+            Framework::ReadWriteLock lock;
+            Assert::IsTrue(lock.getWriteLockCount() == 0,
+                L"The write lock count should be 0 at the beginning");
+            lock.lockWrite();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 1,
+                L"The current threads lock holding count should be 1 after "
+                L"lockWrite() was called");
+            Assert::IsTrue(lock.getWriteLockCount() == 1,
+                L"write lock count should be 1 after "
+                L"lockWrite() was called.");
+            Assert::IsTrue(
+                lock.getWriteLockThread() == Framework::getCurrentThreadId(),
+                L"Current thread should be the writer thread after "
+                L"lockWrite() was called");
+            lock.lockWrite();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 2,
+                L"The current threads lock holding count should be 2 after "
+                L"lockWrite() was called twice");
+            Assert::IsTrue(lock.getWriteLockCount() == 2,
+                L"write lock count should be 2 after "
+                L"lockWrite() was called twice");
+            Assert::IsTrue(
+                lock.getWriteLockThread() == Framework::getCurrentThreadId(),
+                L"Current thread should be the writer thread after "
+                L"lockWrite() was called twice");
+        }
+
+        TEST_METHOD (testUnlockWrite)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockWrite();
+            lock.lockWrite();
+            lock.unlockWrite();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 1,
+                L"The current threads lock holding count should be 1 after "
+                L"lockWrite() was called twice and unlockWrite() once");
+            Assert::IsTrue(lock.getWriteLockCount() == 1,
+                L"write lock count should be one reader after "
+                L"lockWrite() was called twice and unlockWrite() once by the "
+                L"same thread.");
+            Assert::IsTrue(
+                lock.getWriteLockThread() == Framework::getCurrentThreadId(),
+                L"Current thread id should be the writer thread after "
+                L"lockWrite() was called twice and unlockWrite() once by the "
+                L"same thread.");
+            lock.unlockWrite();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 0,
+                L"The current threads lock holding count should be 0 after "
+                L"lockWrite() was called twice and unlockWrite() twice");
+            Assert::IsTrue(lock.getWriteLockCount() == 0,
+                L"write lock count should be 0 after "
+                L"lockWrite() was called twice and unlockWrite() twice by the "
+                L"same thread.");
+            lock.lockWrite();
+            Assert::IsTrue(lock.getWriteLockCount() == 1,
+                L"write lock count should be exactly one after "
+                L"lockWrite() was called 3 times and unlockWrite() twice by "
+                L"the "
+                L"same thread.");
+            lock.unlockWrite();
+            Assert::IsTrue(lock.getWriteLockCount() == 0,
+                L"write lock count should be 0 after "
+                L"lockWrite() was called 3 times and unlockWrite() 3 times by "
+                L"the "
+                L"same thread.");
+        }
+
+        TEST_METHOD (testUpgrade)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockRead();
+            lock.lockWrite();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 2,
+                L"The current threads lock holding count should be 2 after "
+                L"lockRead() and lockWrite() was called");
+            Assert::IsTrue(lock.getWriteLockCount() == 1,
+                L"write lock count should be 1 after "
+                L"lockWrite() was called.");
+            Assert::IsTrue(
+                lock.getWriteLockThread() == Framework::getCurrentThreadId(),
+                L"Current thread should be the writer thread after "
+                L"lockWrite() was called");
+            Assert::IsTrue(lock.getReadThreadCount() == 1,
+                L"ReadWriteLock should have exactly one reader after "
+                L"lockRead() was called.");
+            Assert::IsTrue(
+                lock.getReadThread(0) == Framework::getCurrentThreadId(),
+                L"Current thread id is not present in reader threads after "
+                L"lockRead() was called");
+            Assert::IsTrue(
+                lock.getReadLockCount(Framework::getCurrentThreadId()) == 1,
+                L"Lock count should be 1 after lockRead() was called once");
+            lock.unlockWrite();
+            lock.unlockRead();
+            Assert::IsTrue(Framework::getCurrentThreadLockCount() == 0,
+                L"The current threads lock holding count should be 0 after "
+                L"lockRead() and lockWrite() and unlockWrite() and "
+                L"unlockRead() was called");
+            Assert::IsTrue(lock.getWriteLockCount() == 0,
+                L"write lock count should be 0 after "
+                L"lockWrite() and unlockWrite() was called");
+            Assert::IsTrue(lock.getReadThreadCount() == 0,
+                L"write lock count should be 0 after "
+                L"lockRead() and unlockRead() was called");
+        }
+
+        TEST_METHOD (testReadMultithreading)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockRead();
+            bool finished = 0;
+            new Framework::AsynchronCall(
+                [&lock]() {
+                    lock.lockRead();
+                    lock.unlockRead();
+                },
+                &finished);
+            while (!finished)
+            {
+                Sleep(10);
+            }
+            lock.unlockRead();
+        }
+
+        TEST_METHOD (testWriteMultithreading)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockWrite();
+            bool finished = 0;
+            bool lockAvailable = 0;
+            new Framework::AsynchronCall(
+                [&lock, &lockAvailable]() {
+                    lockAvailable = lock.tryLockWrite();
+                },
+                &finished);
+            while (!finished)
+            {
+                Sleep(10);
+            }
+            lock.unlockWrite();
+            Assert::IsFalse(lockAvailable,
+                L"Write lock should not be available when another thread is "
+                L"using it");
+        }
+
+        TEST_METHOD (testWriteReadMultithreading)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockWrite();
+            bool finished = 0;
+            bool lockAvailable = 0;
+            new Framework::AsynchronCall(
+                [&lock, &lockAvailable]() {
+                    lockAvailable = lock.tryLockRead();
+                },
+                &finished);
+            while (!finished)
+            {
+                Sleep(10);
+            }
+            lock.unlockWrite();
+            Assert::IsFalse(lockAvailable,
+                L"Read lock should not be available when another thread is "
+                L"writing");
+        }
+
+        TEST_METHOD (testReadWriteMultithreading)
+        {
+            Framework::ReadWriteLock lock;
+            lock.lockRead();
+            bool finished = 0;
+            bool lockAvailable = 0;
+            new Framework::AsynchronCall(
+                [&lock, &lockAvailable]() {
+                    lockAvailable = lock.tryLockWrite();
+                },
+                &finished);
+            while (!finished)
+            {
+                Sleep(10);
+            }
+            lock.unlockRead();
+            Assert::IsFalse(lockAvailable,
+                L"Write lock should not be available when another thread is "
+                L"reading");
+        }
+    };
+} // namespace FrameworkTests

+ 1 - 0
Framework Tests/Framework Tests.vcxproj

@@ -177,6 +177,7 @@
     <ClCompile Include="Assembly.cpp" />
     <ClCompile Include="Base64.cpp" />
     <ClCompile Include="Cache.cpp" />
+    <ClCompile Include="Critical.cpp" />
     <ClCompile Include="Json.cpp" />
     <ClCompile Include="pch.cpp">
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>

+ 3 - 0
Framework Tests/Framework Tests.vcxproj.filters

@@ -48,6 +48,9 @@
     <ClCompile Include="Assembly.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Critical.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="pch.h">

+ 9 - 0
Global.cpp

@@ -131,4 +131,13 @@ void Framework::setShowCursor(bool visible)
     cursorVisible = visible;
 }
 
+int Framework::getCurrentThreadId()
+{
+    return currentThreadId;
+}
+
+int Framework::getCurrentThreadLockCount()
+{
+    return currentThreadLockCount;
+}
 #endif

+ 4 - 0
Globals.h

@@ -85,6 +85,10 @@ namespace Framework
     //! Sets the mouse cursor visibility
     DLLEXPORT void setShowCursor(bool visible);
 #endif
+    //! returns the id of the current thread
+    DLLEXPORT int getCurrentThreadId();
+    //! returns the amount of locks that the current thread holds
+    DLLEXPORT int getCurrentThreadLockCount();
 } // namespace Framework
 
 #endif

+ 7 - 16
Screen.cpp

@@ -65,9 +65,9 @@ Screen::~Screen()
 
 void Screen::postAction(std::function<void()> action)
 {
-    rwLock.lockWrite();
+    actionsLock.lockWrite();
     actions.push(action);
-    rwLock.unlockWrite();
+    actionsLock.unlockWrite();
 }
 
 void Screen::setHandleUserInputsOnTick(bool handleOnTick)
@@ -75,17 +75,6 @@ void Screen::setHandleUserInputsOnTick(bool handleOnTick)
     handleUserInputsOnTick = handleOnTick;
 }
 
-// non-constant
-Lock& Screen::readLock()
-{
-    return rwLock.getReadLock();
-}
-
-Lock& Screen::writeLock()
-{
-    return rwLock.getWriteLock();
-}
-
 void Screen::setFill(bool f)
 {
     fill = f;
@@ -170,14 +159,16 @@ void Screen::setFullscreen(bool vollbild) // sets fullscreen
 
 void Screen::tick(double tickval)
 {
-    rwLock.lockRead();
+    actionsLock.lockRead();
     while (!actions.empty())
     {
         actions.front()();
-        rwLock.lockWrite();
+        actionsLock.lockWrite();
         actions.pop();
-        rwLock.unlockWrite();
+        actionsLock.unlockWrite();
     }
+    actionsLock.unlockRead();
+    rwLock.lockRead();
     if (!renderOnTop)
     {
         for (ArrayIterator<ToolTip*> i = tips->begin(); i; i++)

+ 1 - 4
Screen.h

@@ -85,6 +85,7 @@ namespace Framework
         bool rendering;
         Timer* renderTime;
         ReadWriteLock rwLock;
+        ReadWriteLock actionsLock;
         RCArray<ToolTip>* tips;
         bool testRend;
         bool fill;
@@ -110,10 +111,6 @@ namespace Framework
         //! rendering and ticks. This can lead to longer waiting times since the
         //! lock can not allways be aquired betwean two frames
         DLLEXPORT void setHandleUserInputsOnTick(bool handleOnTick);
-        //! returns a lock for reading.
-        DLLEXPORT virtual Lock& readLock();
-        //! returns a lock for writing.
-        DLLEXPORT virtual Lock& writeLock();
         //! Specifies whether the screen is refilled with a color after each
         //! frame (set by default) \param f 1 if the image should be reset
         //! before drawing