diff options
Diffstat (limited to 'xbmc/threads/test')
-rw-r--r-- | xbmc/threads/test/CMakeLists.txt | 7 | ||||
-rw-r--r-- | xbmc/threads/test/TestEndTime.cpp | 77 | ||||
-rw-r--r-- | xbmc/threads/test/TestEvent.cpp | 628 | ||||
-rw-r--r-- | xbmc/threads/test/TestHelpers.h | 75 | ||||
-rw-r--r-- | xbmc/threads/test/TestSharedSection.cpp | 215 |
5 files changed, 1002 insertions, 0 deletions
diff --git a/xbmc/threads/test/CMakeLists.txt b/xbmc/threads/test/CMakeLists.txt new file mode 100644 index 0000000..136e972 --- /dev/null +++ b/xbmc/threads/test/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES TestEvent.cpp + TestSharedSection.cpp + TestEndTime.cpp) + +set(HEADERS TestHelpers.h) + +core_add_test_library(threads_test) diff --git a/xbmc/threads/test/TestEndTime.cpp b/xbmc/threads/test/TestEndTime.cpp new file mode 100644 index 0000000..fb1c6d8 --- /dev/null +++ b/xbmc/threads/test/TestEndTime.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "threads/SystemClock.h" + +#include <gtest/gtest.h> + +using namespace std::chrono_literals; + +namespace +{ + +template<typename T = std::chrono::milliseconds> +void CommonTests(XbmcThreads::EndTime<T>& endTime) +{ + EXPECT_EQ(100ms, endTime.GetInitialTimeoutValue()); + EXPECT_LT(T::zero(), endTime.GetStartTime().time_since_epoch()); + + EXPECT_FALSE(endTime.IsTimePast()); + EXPECT_LT(T::zero(), endTime.GetTimeLeft()); + + std::this_thread::sleep_for(100ms); + + EXPECT_TRUE(endTime.IsTimePast()); + EXPECT_EQ(T::zero(), endTime.GetTimeLeft()); + + endTime.SetInfinite(); + EXPECT_GE(T::max(), endTime.GetInitialTimeoutValue()); + EXPECT_EQ(XbmcThreads::EndTime<T>::Max(), endTime.GetInitialTimeoutValue()); + endTime.SetExpired(); + EXPECT_EQ(T::zero(), endTime.GetInitialTimeoutValue()); +} + +} // namespace + +TEST(TestEndTime, DefaultConstructor) +{ + XbmcThreads::EndTime<> endTime; + endTime.Set(100ms); + + CommonTests(endTime); +} + +TEST(TestEndTime, ExplicitConstructor) +{ + XbmcThreads::EndTime<> endTime(100ms); + + CommonTests(endTime); +} + +TEST(TestEndTime, DoubleMicroSeconds) +{ + XbmcThreads::EndTime<std::chrono::duration<double, std::micro>> endTime(100ms); + + CommonTests(endTime); +} + +TEST(TestEndTime, SteadyClockDuration) +{ + XbmcThreads::EndTime<std::chrono::steady_clock::duration> endTime(100ms); + + CommonTests(endTime); + + endTime.SetInfinite(); + EXPECT_EQ(std::chrono::steady_clock::duration::max(), endTime.GetInitialTimeoutValue()); + + endTime.SetExpired(); + EXPECT_EQ(std::chrono::steady_clock::duration::zero(), endTime.GetInitialTimeoutValue()); + + endTime.Set(endTime.Max()); + EXPECT_EQ(std::chrono::steady_clock::duration::max(), endTime.GetInitialTimeoutValue()); +} diff --git a/xbmc/threads/test/TestEvent.cpp b/xbmc/threads/test/TestEvent.cpp new file mode 100644 index 0000000..c4526b3 --- /dev/null +++ b/xbmc/threads/test/TestEvent.cpp @@ -0,0 +1,628 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "threads/Event.h" +#include "threads/IRunnable.h" +#include "threads/test/TestHelpers.h" + +#include <memory> +#include <stdio.h> + +using namespace XbmcThreads; +using namespace std::chrono_literals; + +//============================================================================= +// Helper classes +//============================================================================= + +class waiter : public IRunnable +{ + CEvent& event; +public: + bool& result; + + volatile bool waiting; + + waiter(CEvent& o, bool& flag) : event(o), result(flag), waiting(false) {} + + void Run() override + { + waiting = true; + result = event.Wait(); + waiting = false; + } +}; + +class timed_waiter : public IRunnable +{ + CEvent& event; + std::chrono::milliseconds waitTime; + +public: + int& result; + + volatile bool waiting; + + timed_waiter(CEvent& o, int& flag, std::chrono::milliseconds waitTimeMillis) + : event(o), waitTime(waitTimeMillis), result(flag), waiting(false) + { + } + + void Run() override + { + waiting = true; + result = 0; + result = event.Wait(waitTime) ? 1 : -1; + waiting = false; + } +}; + +class group_wait : public IRunnable +{ + CEventGroup& event; + std::chrono::milliseconds timeout; + +public: + CEvent* result; + bool waiting; + + group_wait(CEventGroup& o) : event(o), timeout(-1ms), result(NULL), waiting(false) {} + + group_wait(CEventGroup& o, std::chrono::milliseconds timeout_) + : event(o), timeout(timeout_), result(NULL), waiting(false) + { + } + + void Run() override + { + waiting = true; + if (timeout == -1ms) + result = event.wait(); + else + result = event.wait(timeout); + waiting = false; + } +}; + +//============================================================================= + +TEST(TestEvent, General) +{ + CEvent event; + bool result = false; + waiter w1(event,result); + thread waitThread(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + + EXPECT_TRUE(!result); + + event.Set(); + + EXPECT_TRUE(waitThread.timed_join(10000ms)); + + EXPECT_TRUE(result); +} + +TEST(TestEvent, TwoWaits) +{ + CEvent event; + bool result1 = false; + bool result2 = false; + waiter w1(event,result1); + waiter w2(event,result2); + thread waitThread1(w1); + thread waitThread2(w2); + + EXPECT_TRUE(waitForWaiters(event, 2, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread2.timed_join(10000ms)); + + EXPECT_TRUE(result1); + EXPECT_TRUE(result2); + +} + +TEST(TestEvent, TimedWaits) +{ + CEvent event; + int result1 = 10; + timed_waiter w1(event, result1, 100ms); + thread waitThread1(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + + EXPECT_TRUE(result1 == 0); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + + EXPECT_TRUE(result1 == 1); +} + +TEST(TestEvent, TimedWaitsTimeout) +{ + CEvent event; + int result1 = 10; + timed_waiter w1(event, result1, 50ms); + thread waitThread1(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 100ms)); + + EXPECT_TRUE(result1 == 0); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + + EXPECT_TRUE(result1 == -1); +} + +TEST(TestEvent, Group) +{ + CEvent event1; + CEvent event2; + + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +/* Test disabled for now, because it deadlocks +TEST(TestEvent, GroupLimitedGroupScope) +{ + CEvent event1; + CEvent event2; + + { + CEventGroup group(&event1,&event2,NULL); + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event1,1,10000ms)); + EXPECT_TRUE(waitForWaiters(event2,1,10000ms)); + EXPECT_TRUE(waitForWaiters(group,1,10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + } + + event2.Set(); + + std::this_thread::sleep_for(50ms); // give thread 2 a chance to exit +}*/ + +TEST(TestEvent, TwoGroups) +{ + CEvent event1; + CEvent event2; + + CEventGroup group1{&event1,&event2}; + CEventGroup group2{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group1); + group_wait w4(group2); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + thread waitThread4(w4); + + EXPECT_TRUE(waitForWaiters(event1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(group1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(group2, 1, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_EQ(w3.result,(void*)NULL); + EXPECT_TRUE(w4.waiting); + EXPECT_EQ(w4.result,(void*)NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + EXPECT_TRUE(waitThread4.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!w1.waiting); + EXPECT_TRUE(!result2); + EXPECT_TRUE(w2.waiting); + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + EXPECT_TRUE(!w4.waiting); + EXPECT_TRUE(w4.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +TEST(TestEvent, AutoResetBehavior) +{ + CEvent event; + + EXPECT_TRUE(!event.Wait(1ms)); + + event.Set(); // event will remain signaled if there are no waits + + EXPECT_TRUE(event.Wait(1ms)); +} + +TEST(TestEvent, ManualReset) +{ + CEvent event(true); + bool result = false; + waiter w1(event,result); + thread waitThread(w1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + + EXPECT_TRUE(!result); + + event.Set(); + + EXPECT_TRUE(waitThread.timed_join(10000ms)); + + EXPECT_TRUE(result); + + // with manual reset, the state should remain signaled + EXPECT_TRUE(event.Wait(1ms)); + + event.Reset(); + + EXPECT_TRUE(!event.Wait(1ms)); +} + +TEST(TestEvent, InitVal) +{ + CEvent event(false,true); + EXPECT_TRUE(event.Wait(50ms)); +} + +TEST(TestEvent, SimpleTimeout) +{ + CEvent event; + EXPECT_TRUE(!event.Wait(50ms)); +} + +TEST(TestEvent, GroupChildSet) +{ + CEvent event1(true); + CEvent event2; + + event1.Set(); + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +TEST(TestEvent, GroupChildSet2) +{ + CEvent event1(true,true); + CEvent event2; + + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + group_wait w3(group); + + thread waitThread1(w1); + thread waitThread2(w2); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +TEST(TestEvent, GroupWaitResetsChild) +{ + CEvent event1; + CEvent event2; + + CEventGroup group{&event1,&event2}; + + group_wait w3(group); + + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + event2.Set(); + + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == &event2); + // event2 should have been reset. + EXPECT_TRUE(event2.Wait(1ms) == false); +} + +TEST(TestEvent, GroupTimedWait) +{ + CEvent event1; + CEvent event2; + CEventGroup group{&event1,&event2}; + + bool result1 = false; + bool result2 = false; + + waiter w1(event1,result1); + waiter w2(event2,result2); + + thread waitThread1(w1); + thread waitThread2(w2); + + EXPECT_TRUE(waitForWaiters(event1, 1, 10000ms)); + EXPECT_TRUE(waitForWaiters(event2, 1, 10000ms)); + + EXPECT_TRUE(group.wait(20ms) == NULL); // waited ... got nothing + + group_wait w3(group, 50ms); + thread waitThread3(w3); + + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(!result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + // this should end given the wait is for only 50 millis + EXPECT_TRUE(waitThread3.timed_join(200ms)); + + EXPECT_TRUE(!w3.waiting); + EXPECT_TRUE(w3.result == NULL); + + group_wait w4(group, 50ms); + thread waitThread4(w4); + + EXPECT_TRUE(waitForWaiters(group, 1, 10000ms)); + + EXPECT_TRUE(w4.waiting); + EXPECT_TRUE(w4.result == NULL); + + event1.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread4.timed_join(10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(result1); + EXPECT_TRUE(!result2); + + EXPECT_TRUE(!w4.waiting); + EXPECT_TRUE(w4.result == &event1); + + event2.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); +} + +#define TESTNUM 100000l +#define NUMTHREADS 100l + +CEvent* g_event = NULL; +std::atomic<long> g_mutex; + +class mass_waiter : public IRunnable +{ +public: + CEvent& event; + bool result; + + volatile bool waiting = false; + + mass_waiter() : event(*g_event) {} + + void Run() override + { + waiting = true; + AtomicGuard g(&g_mutex); + result = event.Wait(); + waiting = false; + } +}; + +class poll_mass_waiter : public IRunnable +{ +public: + CEvent& event; + bool result; + + volatile bool waiting = false; + + poll_mass_waiter() : event(*g_event) {} + + void Run() override + { + waiting = true; + AtomicGuard g(&g_mutex); + while ((result = event.Wait(0ms)) == false) + ; + waiting = false; + } +}; + +template <class W> void RunMassEventTest(std::vector<std::shared_ptr<W>>& m, bool canWaitOnEvent) +{ + std::vector<std::shared_ptr<thread>> t(NUMTHREADS); + for(size_t i=0; i<NUMTHREADS; i++) + t[i].reset(new thread(*m[i])); + + EXPECT_TRUE(waitForThread(g_mutex, NUMTHREADS, 10000ms)); + if (canWaitOnEvent) + { + EXPECT_TRUE(waitForWaiters(*g_event, NUMTHREADS, 10000ms)); + } + + std::this_thread::sleep_for(100ms); // give them a little more time + + for(size_t i=0; i<NUMTHREADS; i++) + { + EXPECT_TRUE(m[i]->waiting); + } + + g_event->Set(); + + for(size_t i=0; i<NUMTHREADS; i++) + { + EXPECT_TRUE(t[i]->timed_join(10000ms)); + } + + for(size_t i=0; i<NUMTHREADS; i++) + { + EXPECT_TRUE(!m[i]->waiting); + EXPECT_TRUE(m[i]->result); + } +} + + +TEST(TestMassEvent, General) +{ + g_event = new CEvent(); + + std::vector<std::shared_ptr<mass_waiter>> m(NUMTHREADS); + for(size_t i=0; i<NUMTHREADS; i++) + m[i].reset(new mass_waiter()); + + RunMassEventTest(m,true); + delete g_event; +} + +TEST(TestMassEvent, Polling) +{ + g_event = new CEvent(true); // polling needs to avoid the auto-reset + + std::vector<std::shared_ptr<poll_mass_waiter>> m(NUMTHREADS); + for(size_t i=0; i<NUMTHREADS; i++) + m[i].reset(new poll_mass_waiter()); + + RunMassEventTest(m,false); + delete g_event; +} + diff --git a/xbmc/threads/test/TestHelpers.h b/xbmc/threads/test/TestHelpers.h new file mode 100644 index 0000000..4d8752c --- /dev/null +++ b/xbmc/threads/test/TestHelpers.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/Thread.h" + +#include <memory> +#include <mutex> + +#include <gtest/gtest.h> + +template<class E> +inline static bool waitForWaiters(E& event, int numWaiters, std::chrono::milliseconds duration) +{ + for (auto i = std::chrono::milliseconds::zero(); i < duration; i++) + { + if (event.getNumWaits() == numWaiters) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return false; +} + +inline static bool waitForThread(std::atomic<long>& mutex, + int numWaiters, + std::chrono::milliseconds duration) +{ + CCriticalSection sec; + for (auto i = std::chrono::milliseconds::zero(); i < duration; i++) + { + if (mutex == (long)numWaiters) + return true; + + { + std::unique_lock<CCriticalSection> tmplock(sec); // kick any memory syncs + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return false; +} + +class AtomicGuard +{ + std::atomic<long>* val; +public: + inline AtomicGuard(std::atomic<long>* val_) : val(val_) { if (val) ++(*val); } + inline ~AtomicGuard() { if (val) --(*val); } +}; + +class thread +{ + std::unique_ptr<CThread> cthread; + +public: + inline explicit thread(IRunnable& runnable) + : cthread(std::make_unique<CThread>(&runnable, "DumbThread")) + { + cthread->Create(); + } + + void join() { cthread->Join(std::chrono::milliseconds::max()); } + + bool timed_join(std::chrono::milliseconds duration) { return cthread->Join(duration); } +}; + diff --git a/xbmc/threads/test/TestSharedSection.cpp b/xbmc/threads/test/TestSharedSection.cpp new file mode 100644 index 0000000..fc2ca08 --- /dev/null +++ b/xbmc/threads/test/TestSharedSection.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "threads/Event.h" +#include "threads/IRunnable.h" +#include "threads/SharedSection.h" +#include "threads/test/TestHelpers.h" + +#include <mutex> +#include <shared_mutex> +#include <stdio.h> + +using namespace std::chrono_literals; + +//============================================================================= +// Helper classes +//============================================================================= + +template<class L> +class locker : public IRunnable +{ + CSharedSection& sec; + CEvent* wait; + + std::atomic<long>* mutex; +public: + volatile bool haslock; + volatile bool obtainedlock; + + inline locker(CSharedSection& o, std::atomic<long>* mutex_ = NULL, CEvent* wait_ = NULL) : + sec(o), wait(wait_), mutex(mutex_), haslock(false), obtainedlock(false) {} + + inline locker(CSharedSection& o, CEvent* wait_ = NULL) : + sec(o), wait(wait_), mutex(NULL), haslock(false), obtainedlock(false) {} + + void Run() override + { + AtomicGuard g(mutex); + L lock(sec); + haslock = true; + obtainedlock = true; + if (wait) + wait->Wait(); + haslock = false; + } +}; + +TEST(TestCritSection, General) +{ + CCriticalSection sec; + + std::unique_lock<CCriticalSection> l1(sec); + std::unique_lock<CCriticalSection> l2(sec); +} + +TEST(TestSharedSection, General) +{ + CSharedSection sec; + + std::shared_lock<CSharedSection> l1(sec); + std::shared_lock<CSharedSection> l2(sec); +} + +TEST(TestSharedSection, GetSharedLockWhileTryingExclusiveLock) +{ + std::atomic<long> mutex(0L); + CEvent event; + + CSharedSection sec; + + std::shared_lock<CSharedSection> l1(sec); // get a shared lock + + locker<std::unique_lock<CSharedSection>> l2(sec, &mutex); + thread waitThread1(l2); // try to get an exclusive lock + + EXPECT_TRUE(waitForThread(mutex, 1, 10000ms)); + std::this_thread::sleep_for(10ms); // still need to give it a chance to move ahead + + EXPECT_TRUE(!l2.haslock); // this thread is waiting ... + EXPECT_TRUE(!l2.obtainedlock); // this thread is waiting ... + + // now try and get a SharedLock + locker<std::shared_lock<CSharedSection>> l3(sec, &mutex, &event); + thread waitThread3(l3); // try to get a shared lock + EXPECT_TRUE(waitForThread(mutex, 2, 10000ms)); + std::this_thread::sleep_for(10ms); + EXPECT_TRUE(l3.haslock); + + event.Set(); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + + // l3 should have released. + EXPECT_TRUE(!l3.haslock); + + // but the exclusive lock should still not have happened + EXPECT_TRUE(!l2.haslock); // this thread is waiting ... + EXPECT_TRUE(!l2.obtainedlock); // this thread is waiting ... + + // let it go + l1.unlock(); // the last shared lock leaves. + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + + EXPECT_TRUE(l2.obtainedlock); // the exclusive lock was captured + EXPECT_TRUE(!l2.haslock); // ... but it doesn't have it anymore +} + +TEST(TestSharedSection, TwoCase) +{ + CSharedSection sec; + + CEvent event; + std::atomic<long> mutex(0L); + + locker<std::shared_lock<CSharedSection>> l1(sec, &mutex, &event); + + { + std::shared_lock<CSharedSection> lock(sec); + thread waitThread1(l1); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + EXPECT_TRUE(l1.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + } + + locker<std::shared_lock<CSharedSection>> l2(sec, &mutex, &event); + { + std::unique_lock<CSharedSection> lock(sec); // get exclusive lock + thread waitThread2(l2); // thread should block + + EXPECT_TRUE(waitForThread(mutex, 1, 10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(!l2.haslock); + + lock.unlock(); + + EXPECT_TRUE(waitForWaiters(event, 1, 10000ms)); + std::this_thread::sleep_for(10ms); + EXPECT_TRUE(l2.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread2.timed_join(10000ms)); + } +} + +TEST(TestMultipleSharedSection, General) +{ + CSharedSection sec; + + CEvent event; + std::atomic<long> mutex(0L); + + locker<std::shared_lock<CSharedSection>> l1(sec, &mutex, &event); + + { + std::shared_lock<CSharedSection> lock(sec); + thread waitThread1(l1); + + EXPECT_TRUE(waitForThread(mutex, 1, 10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(l1.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + } + + locker<std::shared_lock<CSharedSection>> l2(sec, &mutex, &event); + locker<std::shared_lock<CSharedSection>> l3(sec, &mutex, &event); + locker<std::shared_lock<CSharedSection>> l4(sec, &mutex, &event); + locker<std::shared_lock<CSharedSection>> l5(sec, &mutex, &event); + { + std::unique_lock<CSharedSection> lock(sec); + thread waitThread1(l2); + thread waitThread2(l3); + thread waitThread3(l4); + thread waitThread4(l5); + + EXPECT_TRUE(waitForThread(mutex, 4, 10000ms)); + std::this_thread::sleep_for(10ms); + + EXPECT_TRUE(!l2.haslock); + EXPECT_TRUE(!l3.haslock); + EXPECT_TRUE(!l4.haslock); + EXPECT_TRUE(!l5.haslock); + + lock.unlock(); + + EXPECT_TRUE(waitForWaiters(event, 4, 10000ms)); + + EXPECT_TRUE(l2.haslock); + EXPECT_TRUE(l3.haslock); + EXPECT_TRUE(l4.haslock); + EXPECT_TRUE(l5.haslock); + + event.Set(); + + EXPECT_TRUE(waitThread1.timed_join(10000ms)); + EXPECT_TRUE(waitThread2.timed_join(10000ms)); + EXPECT_TRUE(waitThread3.timed_join(10000ms)); + EXPECT_TRUE(waitThread4.timed_join(10000ms)); + } +} + |