summaryrefslogtreecommitdiffstats
path: root/xbmc/threads/test
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/threads/test')
-rw-r--r--xbmc/threads/test/CMakeLists.txt7
-rw-r--r--xbmc/threads/test/TestEndTime.cpp77
-rw-r--r--xbmc/threads/test/TestEvent.cpp628
-rw-r--r--xbmc/threads/test/TestHelpers.h75
-rw-r--r--xbmc/threads/test/TestSharedSection.cpp215
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));
+ }
+}
+