summaryrefslogtreecommitdiffstats
path: root/xbmc/threads
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/threads
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/threads')
-rw-r--r--xbmc/threads/CMakeLists.txt17
-rw-r--r--xbmc/threads/Condition.h108
-rw-r--r--xbmc/threads/CriticalSection.h27
-rw-r--r--xbmc/threads/Event.cpp95
-rw-r--r--xbmc/threads/Event.h239
-rw-r--r--xbmc/threads/IRunnable.h17
-rw-r--r--xbmc/threads/IThreadImpl.h43
-rw-r--r--xbmc/threads/Lockables.h107
-rw-r--r--xbmc/threads/SharedSection.h54
-rw-r--r--xbmc/threads/SingleLock.h32
-rw-r--r--xbmc/threads/SystemClock.h96
-rw-r--r--xbmc/threads/Thread.cpp282
-rw-r--r--xbmc/threads/Thread.h125
-rw-r--r--xbmc/threads/Timer.cpp109
-rw-r--r--xbmc/threads/Timer.h51
-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
20 files changed, 2404 insertions, 0 deletions
diff --git a/xbmc/threads/CMakeLists.txt b/xbmc/threads/CMakeLists.txt
new file mode 100644
index 0000000..02e2016
--- /dev/null
+++ b/xbmc/threads/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(SOURCES Event.cpp
+ Thread.cpp
+ Timer.cpp)
+
+set(HEADERS Condition.h
+ CriticalSection.h
+ Event.h
+ Lockables.h
+ SharedSection.h
+ SingleLock.h
+ SystemClock.h
+ Thread.h
+ Timer.h
+ IThreadImpl.h
+ IRunnable.h)
+
+core_add_library(threads)
diff --git a/xbmc/threads/Condition.h b/xbmc/threads/Condition.h
new file mode 100644
index 0000000..3738352
--- /dev/null
+++ b/xbmc/threads/Condition.h
@@ -0,0 +1,108 @@
+/*
+ * 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/CriticalSection.h"
+
+#include <chrono>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <utility>
+
+namespace XbmcThreads
+{
+
+ /**
+ * This is a thin wrapper around std::condition_variable_any. It is subject
+ * to "spurious returns"
+ */
+ class ConditionVariable
+ {
+ private:
+ std::condition_variable_any cond;
+ ConditionVariable(const ConditionVariable&) = delete;
+ ConditionVariable& operator=(const ConditionVariable&) = delete;
+
+ public:
+ ConditionVariable() = default;
+
+ inline void wait(CCriticalSection& lock, std::function<bool()> predicate)
+ {
+ int count = lock.count;
+ lock.count = 0;
+ cond.wait(lock.get_underlying(), std::move(predicate));
+ lock.count = count;
+ }
+
+ inline void wait(CCriticalSection& lock)
+ {
+ int count = lock.count;
+ lock.count = 0;
+ cond.wait(lock.get_underlying());
+ lock.count = count;
+ }
+
+ template<typename Rep, typename Period>
+ inline bool wait(CCriticalSection& lock,
+ std::chrono::duration<Rep, Period> duration,
+ std::function<bool()> predicate)
+ {
+ int count = lock.count;
+ lock.count = 0;
+ bool ret = cond.wait_for(lock.get_underlying(), duration, predicate);
+ lock.count = count;
+ return ret;
+ }
+
+ template<typename Rep, typename Period>
+ inline bool wait(CCriticalSection& lock, std::chrono::duration<Rep, Period> duration)
+ {
+ int count = lock.count;
+ lock.count = 0;
+ std::cv_status res = cond.wait_for(lock.get_underlying(), duration);
+ lock.count = count;
+ return res == std::cv_status::no_timeout;
+ }
+
+ inline void wait(std::unique_lock<CCriticalSection>& lock, std::function<bool()> predicate)
+ {
+ cond.wait(*lock.mutex(), std::move(predicate));
+ }
+
+ inline void wait(std::unique_lock<CCriticalSection>& lock) { wait(*lock.mutex()); }
+
+ template<typename Rep, typename Period>
+ inline bool wait(std::unique_lock<CCriticalSection>& lock,
+ std::chrono::duration<Rep, Period> duration,
+ std::function<bool()> predicate)
+ {
+ return wait(*lock.mutex(), duration, predicate);
+ }
+
+ template<typename Rep, typename Period>
+ inline bool wait(std::unique_lock<CCriticalSection>& lock,
+ std::chrono::duration<Rep, Period> duration)
+ {
+ return wait(*lock.mutex(), duration);
+ }
+
+ inline void notifyAll()
+ {
+ cond.notify_all();
+ }
+
+ inline void notify()
+ {
+ cond.notify_one();
+ }
+ };
+
+}
+
diff --git a/xbmc/threads/CriticalSection.h b/xbmc/threads/CriticalSection.h
new file mode 100644
index 0000000..5aaf8aa
--- /dev/null
+++ b/xbmc/threads/CriticalSection.h
@@ -0,0 +1,27 @@
+/*
+ * 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/Lockables.h"
+
+#if defined(TARGET_POSIX)
+#include "platform/posix/threads/RecursiveMutex.h"
+
+class CCriticalSection : public XbmcThreads::CountingLockable<XbmcThreads::CRecursiveMutex>
+{
+};
+
+#elif defined(TARGET_WINDOWS)
+#include <mutex>
+
+class CCriticalSection : public XbmcThreads::CountingLockable<std::recursive_mutex>
+{
+};
+
+#endif
diff --git a/xbmc/threads/Event.cpp b/xbmc/threads/Event.cpp
new file mode 100644
index 0000000..1c67f4e
--- /dev/null
+++ b/xbmc/threads/Event.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-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 "Event.h"
+
+#include <algorithm>
+#include <limits>
+#include <mutex>
+
+using namespace std::chrono_literals;
+
+void CEvent::addGroup(XbmcThreads::CEventGroup* group)
+{
+ std::unique_lock<CCriticalSection> lock(groupListMutex);
+ if (!groups)
+ groups.reset(new std::vector<XbmcThreads::CEventGroup*>);
+
+ groups->push_back(group);
+}
+
+void CEvent::removeGroup(XbmcThreads::CEventGroup* group)
+{
+ std::unique_lock<CCriticalSection> lock(groupListMutex);
+ if (groups)
+ {
+ groups->erase(std::remove(groups->begin(), groups->end(), group), groups->end());
+ if (groups->empty())
+ {
+ groups.reset();
+ }
+ }
+}
+
+// locking is ALWAYS done in this order:
+// CEvent::groupListMutex -> CEventGroup::mutex -> CEvent::mutex
+void CEvent::Set()
+{
+ // Originally I had this without locking. Thanks to FernetMenta who
+ // pointed out that this creates a race condition between setting
+ // checking the signal and calling wait() on the Wait call in the
+ // CEvent class. This now perfectly matches the boost example here:
+ // http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref
+ {
+ std::unique_lock<CCriticalSection> slock(mutex);
+ signaled = true;
+ }
+
+ actualCv.notifyAll();
+
+ std::unique_lock<CCriticalSection> l(groupListMutex);
+ if (groups)
+ {
+ for (auto* group : *groups)
+ group->Set(this);
+ }
+}
+
+namespace XbmcThreads
+{
+ /**
+ * This will block until any one of the CEvents in the group are
+ * signaled at which point a pointer to that CEvents will be
+ * returned.
+ */
+ CEvent* CEventGroup::wait()
+ {
+ return wait(std::chrono::milliseconds::max());
+ }
+
+ CEventGroup::CEventGroup(std::initializer_list<CEvent*> eventsList)
+ : events{eventsList}
+ {
+ // we preping for a wait, so we need to set the group value on
+ // all of the CEvents.
+ for (auto* event : events)
+ {
+ event->addGroup(this);
+ }
+ }
+
+ CEventGroup::~CEventGroup()
+ {
+ for (auto* event : events)
+ {
+ event->removeGroup(this);
+ }
+ }
+}
diff --git a/xbmc/threads/Event.h b/xbmc/threads/Event.h
new file mode 100644
index 0000000..72ce754
--- /dev/null
+++ b/xbmc/threads/Event.h
@@ -0,0 +1,239 @@
+/*
+ * 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/Condition.h"
+
+#include <initializer_list>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+// forward declare the CEventGroup
+namespace XbmcThreads
+{
+class CEventGroup;
+}
+
+/**
+ * @brief This is an Event class built from a ConditionVariable. The Event adds the state
+ * that the condition is gating as well as the mutex/lock.
+ *
+ * This Event can be 'interruptible' (even though there is only a single place
+ * in the code that uses this behavior).
+ *
+ * This class manages 'spurious returns' from the condition variable.
+ *
+ */
+
+class CEvent
+{
+ bool manualReset;
+ volatile bool signaled;
+ unsigned int numWaits = 0;
+
+ CCriticalSection groupListMutex; // lock for the groups list
+ std::unique_ptr<std::vector<XbmcThreads::CEventGroup*>> groups;
+
+ XbmcThreads::ConditionVariable actualCv;
+ CCriticalSection mutex;
+
+ friend class XbmcThreads::CEventGroup;
+
+ void addGroup(XbmcThreads::CEventGroup* group);
+ void removeGroup(XbmcThreads::CEventGroup* group);
+
+ // helper for the two wait methods
+ inline bool prepReturn()
+ {
+ bool ret = signaled;
+ if (!manualReset && numWaits == 0)
+ signaled = false;
+ return ret;
+ }
+
+ CEvent(const CEvent&) = delete;
+ CEvent& operator=(const CEvent&) = delete;
+
+public:
+ inline CEvent(bool manual = false, bool signaled_ = false)
+ : manualReset(manual), signaled(signaled_)
+ {
+ }
+
+ inline void Reset()
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ signaled = false;
+ }
+ void Set();
+
+ /**
+ * @brief Returns true if Event has been triggered and not reset, false otherwise.
+ *
+ */
+ inline bool Signaled()
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ return signaled;
+ }
+
+ /**
+ * @brief This will wait up to 'duration' for the Event to be
+ * triggered. The method will return 'true' if the Event
+ * was triggered. Otherwise it will return false.
+ *
+ */
+ template<typename Rep, typename Period>
+ inline bool Wait(std::chrono::duration<Rep, Period> duration)
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ numWaits++;
+ actualCv.wait(mutex, duration, std::bind(&CEvent::Signaled, this));
+ numWaits--;
+ return prepReturn();
+ }
+
+ /**
+ * @brief This will wait for the Event to be triggered. The method will return
+ * 'true' if the Event was triggered. If it was either interrupted
+ * it will return false. Otherwise it will return false.
+ *
+ */
+ inline bool Wait()
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ numWaits++;
+ actualCv.wait(mutex, std::bind(&CEvent::Signaled, this));
+ numWaits--;
+ return prepReturn();
+ }
+
+ /**
+ * @brief This is mostly for testing. It allows a thread to make sure there are
+ * the right amount of other threads waiting.
+ *
+ */
+ inline int getNumWaits()
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ return numWaits;
+ }
+};
+
+namespace XbmcThreads
+{
+/**
+ * @brief CEventGroup is a means of grouping CEvents to wait on them together.
+ * It is equivalent to WaitOnMultipleObject that returns when "any" Event
+ * in the group signaled.
+ *
+ */
+class CEventGroup
+{
+ std::vector<CEvent*> events;
+ CEvent* signaled{};
+ XbmcThreads::ConditionVariable actualCv;
+ CCriticalSection mutex;
+
+ unsigned int numWaits{0};
+
+ // This is ONLY called from CEvent::Set.
+ inline void Set(CEvent* child)
+ {
+ std::unique_lock<CCriticalSection> l(mutex);
+ signaled = child;
+ actualCv.notifyAll();
+ }
+
+ friend class ::CEvent;
+
+ CEventGroup(const CEventGroup&) = delete;
+ CEventGroup& operator=(const CEventGroup&) = delete;
+
+public:
+ /**
+ * @brief Create a CEventGroup from a number of CEvents.
+ *
+ */
+ CEventGroup(std::initializer_list<CEvent*> events);
+
+ ~CEventGroup();
+
+ /**
+ * @brief This will block until any one of the CEvents in the group are
+ * signaled at which point a pointer to that CEvents will be
+ * returned.
+ *
+ */
+ CEvent* wait();
+
+ /**
+ * @brief locking is ALWAYS done in this order:
+ * CEvent::groupListMutex -> CEventGroup::mutex -> CEvent::mutex
+ *
+ * Notice that this method doesn't grab the CEvent::groupListMutex at all. This
+ * is fine. It just grabs the CEventGroup::mutex and THEN the individual
+ *
+ */
+ template<typename Rep, typename Period>
+ CEvent* wait(std::chrono::duration<Rep, Period> duration)
+ {
+ std::unique_lock<CCriticalSection> lock(mutex); // grab CEventGroup::mutex
+ numWaits++;
+
+ // ==================================================
+ // This block checks to see if any child events are
+ // signaled and sets 'signaled' to the first one it
+ // finds.
+ // ==================================================
+ signaled = nullptr;
+ for (auto* cur : events)
+ {
+ std::unique_lock<CCriticalSection> lock2(cur->mutex);
+ if (cur->signaled)
+ signaled = cur;
+ }
+ // ==================================================
+
+ if (!signaled)
+ {
+ // both of these release the CEventGroup::mutex
+ if (duration == std::chrono::duration<Rep, Period>::max())
+ actualCv.wait(mutex, [this]() { return signaled != nullptr; });
+ else
+ actualCv.wait(mutex, duration, [this]() { return signaled != nullptr; });
+ } // at this point the CEventGroup::mutex is reacquired
+ numWaits--;
+
+ // signaled should have been set by a call to CEventGroup::Set
+ CEvent* ret = signaled;
+ if (numWaits == 0)
+ {
+ if (signaled)
+ // This acquires and releases the CEvent::mutex. This is fine since the
+ // CEventGroup::mutex is already being held
+ signaled->Wait(std::chrono::duration<Rep, Period>::zero()); // reset the event if needed
+ signaled = nullptr; // clear the signaled if all the waiters are gone
+ }
+ return ret;
+ }
+
+ /**
+ * @brief This is mostly for testing. It allows a thread to make sure there are
+ * the right amount of other threads waiting.
+ *
+ */
+ inline int getNumWaits()
+ {
+ std::unique_lock<CCriticalSection> lock(mutex);
+ return numWaits;
+ }
+};
+} // namespace XbmcThreads
diff --git a/xbmc/threads/IRunnable.h b/xbmc/threads/IRunnable.h
new file mode 100644
index 0000000..32c5b8f
--- /dev/null
+++ b/xbmc/threads/IRunnable.h
@@ -0,0 +1,17 @@
+/*
+ * 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
+
+class IRunnable
+{
+public:
+ virtual void Run()=0;
+ virtual void Cancel() {}
+ virtual ~IRunnable() = default;
+};
diff --git a/xbmc/threads/IThreadImpl.h b/xbmc/threads/IThreadImpl.h
new file mode 100644
index 0000000..f631c79
--- /dev/null
+++ b/xbmc/threads/IThreadImpl.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2005-2022 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 <string>
+#include <thread>
+
+class IThreadImpl
+{
+public:
+ virtual ~IThreadImpl() = default;
+
+ static std::unique_ptr<IThreadImpl> CreateThreadImpl(std::thread::native_handle_type handle);
+
+ /*!
+ * \brief Set the thread name and other info (platform dependent)
+ *
+ */
+ virtual void SetThreadInfo(const std::string& name) = 0;
+
+ /*!
+ * \brief Set the thread priority via the native threading library
+ *
+ */
+ virtual bool SetPriority(const ThreadPriority& priority) = 0;
+
+protected:
+ IThreadImpl(std::thread::native_handle_type handle) : m_handle(handle) {}
+
+ std::thread::native_handle_type m_handle;
+
+private:
+ IThreadImpl() = delete;
+};
diff --git a/xbmc/threads/Lockables.h b/xbmc/threads/Lockables.h
new file mode 100644
index 0000000..18509b1
--- /dev/null
+++ b/xbmc/threads/Lockables.h
@@ -0,0 +1,107 @@
+/*
+ * 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
+
+namespace XbmcThreads
+{
+
+ /**
+ * This template will take any implementation of the "Lockable" concept
+ * and allow it to be used as an "Exitable Lockable."
+ *
+ * Something that implements the "Lockable concept" simply means that
+ * it has the three methods:
+ *
+ * lock();
+ * try_lock();
+ * unlock();
+ * IsLocked();
+ *
+ * "Exitable" specifically means that, no matter how deep the recursion
+ * on the mutex/critical section, we can exit from it and then restore
+ * the state.
+ *
+ * This requires us to extend the Lockable so that we can keep track of the
+ * number of locks that have been recursively acquired so that we can
+ * undo it, and then restore that (See class CSingleExit).
+ *
+ * All xbmc code expects Lockables to be recursive.
+ */
+ template<class L> class CountingLockable
+ {
+ friend class ConditionVariable;
+
+ CountingLockable(const CountingLockable&) = delete;
+ CountingLockable& operator=(const CountingLockable&) = delete;
+ protected:
+ L mutex;
+ unsigned int count = 0;
+
+ public:
+ inline CountingLockable() = default;
+
+ // STL Lockable concept
+ inline void lock() { mutex.lock(); count++; }
+ inline bool try_lock() { return mutex.try_lock() ? count++, true : false; }
+ inline void unlock() { count--; mutex.unlock(); }
+
+ /*!
+ * \brief Check if have a lock owned
+ * \return True if have a lock, otherwise false
+ */
+ inline bool IsLocked() const { return count > 0; }
+
+ /**
+ * This implements the "exitable" behavior mentioned above.
+ */
+ inline unsigned int exit(unsigned int leave = 0)
+ {
+ // it's possible we don't actually own the lock
+ // so we will try it.
+ unsigned int ret = 0;
+ if (try_lock())
+ {
+ if (leave < (count - 1))
+ {
+ ret = count - 1 - leave; // The -1 is because we don't want
+ // to count the try_lock increment.
+ // We must NOT compare "count" in this loop since
+ // as soon as the last unlock is called another thread
+ // can modify it.
+ for (unsigned int i = 0; i < ret; i++)
+ unlock();
+ }
+ unlock(); // undo the try_lock before returning
+ }
+
+ return ret;
+ }
+
+ /**
+ * Restore a previous exit to the provided level.
+ */
+ inline void restore(unsigned int restoreCount)
+ {
+ for (unsigned int i = 0; i < restoreCount; i++)
+ lock();
+ }
+
+ /**
+ * Some implementations (see pthreads) require access to the underlying
+ * CCriticalSection, which is also implementation specific. This
+ * provides access to it through the same method on the guard classes
+ * UniqueLock, and SharedLock.
+ *
+ * There really should be no need for the users of the threading library
+ * to call this method.
+ */
+ inline L& get_underlying() { return mutex; }
+ };
+
+}
diff --git a/xbmc/threads/SharedSection.h b/xbmc/threads/SharedSection.h
new file mode 100644
index 0000000..dce371d
--- /dev/null
+++ b/xbmc/threads/SharedSection.h
@@ -0,0 +1,54 @@
+/*
+ * 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/Condition.h"
+
+#include <mutex>
+#include <shared_mutex>
+
+/**
+ * A CSharedSection is a mutex that satisfies the Shared Lockable concept (see Lockables.h).
+ */
+class CSharedSection
+{
+ CCriticalSection sec;
+ XbmcThreads::ConditionVariable actualCv;
+
+ unsigned int sharedCount = 0;
+
+public:
+ inline CSharedSection() = default;
+
+ inline void lock()
+ {
+ std::unique_lock<CCriticalSection> l(sec);
+ while (sharedCount)
+ actualCv.wait(l, [this]() { return sharedCount == 0; });
+ sec.lock();
+ }
+ inline bool try_lock() { return (sec.try_lock() ? ((sharedCount == 0) ? true : (sec.unlock(), false)) : false); }
+ inline void unlock() { sec.unlock(); }
+
+ inline void lock_shared()
+ {
+ std::unique_lock<CCriticalSection> l(sec);
+ sharedCount++;
+ }
+ inline bool try_lock_shared() { return (sec.try_lock() ? sharedCount++, sec.unlock(), true : false); }
+ inline void unlock_shared()
+ {
+ std::unique_lock<CCriticalSection> l(sec);
+ sharedCount--;
+ if (!sharedCount)
+ {
+ actualCv.notifyAll();
+ }
+ }
+};
diff --git a/xbmc/threads/SingleLock.h b/xbmc/threads/SingleLock.h
new file mode 100644
index 0000000..cacb691
--- /dev/null
+++ b/xbmc/threads/SingleLock.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-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/CriticalSection.h"
+
+#include <mutex>
+
+/**
+ * This implements a "guard" pattern for exiting all locks
+ * currently being held by the current thread and restoring
+ * those locks on destruction.
+ *
+ * This class can be used on a CCriticalSection that isn't owned
+ * by this thread in which case it will do nothing.
+ */
+class CSingleExit
+{
+ CCriticalSection& sec;
+ unsigned int count;
+public:
+ inline explicit CSingleExit(CCriticalSection& cs) : sec(cs), count(cs.exit()) { }
+ inline ~CSingleExit() { sec.restore(count); }
+};
diff --git a/xbmc/threads/SystemClock.h b/xbmc/threads/SystemClock.h
new file mode 100644
index 0000000..92c4901
--- /dev/null
+++ b/xbmc/threads/SystemClock.h
@@ -0,0 +1,96 @@
+/*
+ * 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 "utils/log.h"
+
+#include <chrono>
+#include <limits>
+#include <thread>
+
+namespace XbmcThreads
+{
+
+template<typename>
+struct is_chrono_duration : std::false_type
+{
+};
+
+template<typename Rep, typename Period>
+struct is_chrono_duration<std::chrono::duration<Rep, Period>> : std::true_type
+{
+};
+
+template<typename T = std::chrono::milliseconds, bool = is_chrono_duration<T>::value>
+class EndTime;
+
+template<typename T>
+class EndTime<T, true>
+{
+public:
+ explicit EndTime(const T duration) { Set(duration); }
+
+ EndTime() = default;
+ EndTime(const EndTime& right) = delete;
+ ~EndTime() = default;
+
+ static constexpr T Max() { return m_max; }
+
+ void Set(const T duration)
+ {
+ m_startTime = std::chrono::steady_clock::now();
+
+ if (duration > m_max)
+ {
+ m_totalWaitTime = m_max;
+ CLog::Log(LOGWARNING, "duration ({}) greater than max ({}) - duration will be truncated!",
+ duration.count(), m_max.count());
+ }
+ else
+ {
+ m_totalWaitTime = duration;
+ }
+ }
+
+ bool IsTimePast() const
+ {
+ const auto now = std::chrono::steady_clock::now();
+
+ return ((now - m_startTime) >= m_totalWaitTime);
+ }
+
+ T GetTimeLeft() const
+ {
+ const auto now = std::chrono::steady_clock::now();
+
+ const auto left = ((m_startTime + m_totalWaitTime) - now);
+
+ if (left < T::zero())
+ return T::zero();
+
+ return std::chrono::duration_cast<T>(left);
+ }
+
+ void SetExpired() { m_totalWaitTime = T::zero(); }
+
+ void SetInfinite() { m_totalWaitTime = m_max; }
+
+ T GetInitialTimeoutValue() const { return m_totalWaitTime; }
+
+ std::chrono::steady_clock::time_point GetStartTime() const { return m_startTime; }
+
+private:
+ std::chrono::steady_clock::time_point m_startTime;
+ T m_totalWaitTime = T::zero();
+
+ static constexpr T m_max =
+ std::chrono::duration_cast<T>(std::chrono::steady_clock::duration::max());
+};
+
+} // namespace XbmcThreads
diff --git a/xbmc/threads/Thread.cpp b/xbmc/threads/Thread.cpp
new file mode 100644
index 0000000..e23baa7
--- /dev/null
+++ b/xbmc/threads/Thread.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2002 Frodo
+ * Portions Copyright (c) by the authors of ffmpeg and xvid
+ * Copyright (C) 2002-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 "Thread.h"
+
+#include "IRunnable.h"
+#include "commons/Exception.h"
+#include "threads/IThreadImpl.h"
+#include "threads/SingleLock.h"
+#include "utils/log.h"
+
+#include <atomic>
+#include <inttypes.h>
+#include <iostream>
+#include <mutex>
+#include <stdlib.h>
+
+static thread_local CThread* currentThread;
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+CThread::CThread(const char* ThreadName)
+:
+ m_bStop(false), m_StopEvent(true, true), m_StartEvent(true, true), m_pRunnable(nullptr)
+{
+ if (ThreadName)
+ m_ThreadName = ThreadName;
+}
+
+CThread::CThread(IRunnable* pRunnable, const char* ThreadName)
+:
+ m_bStop(false), m_StopEvent(true, true), m_StartEvent(true, true), m_pRunnable(pRunnable)
+{
+ if (ThreadName)
+ m_ThreadName = ThreadName;
+}
+
+CThread::~CThread()
+{
+ StopThread();
+ if (m_thread != nullptr)
+ {
+ m_thread->detach();
+ delete m_thread;
+ }
+}
+
+void CThread::Create(bool bAutoDelete)
+{
+ if (m_thread != nullptr)
+ {
+ // if the thread exited on it's own, without a call to StopThread, then we can get here
+ // incorrectly. We should be able to determine this by checking the promise.
+ std::future_status stat = m_future.wait_for(std::chrono::milliseconds(0));
+ // a status of 'ready' means the future contains the value so the thread has exited
+ // since the thread can't exit without setting the future.
+ if (stat == std::future_status::ready) // this is an indication the thread has exited.
+ StopThread(true); // so let's just clean up
+ else
+ { // otherwise we have a problem.
+ CLog::Log(LOGERROR, "{} - fatal error creating thread {} - old thread id not null",
+ __FUNCTION__, m_ThreadName);
+ exit(1);
+ }
+ }
+
+ m_bAutoDelete = bAutoDelete;
+ m_bStop = false;
+ m_StopEvent.Reset();
+ m_StartEvent.Reset();
+
+ // lock?
+ //std::unique_lock<CCriticalSection> l(m_CriticalSection);
+
+ std::promise<bool> prom;
+ m_future = prom.get_future();
+
+ {
+ // The std::thread internals must be set prior to the lambda doing
+ // any work. This will cause the lambda to wait until m_thread
+ // is fully initialized. Interestingly, using a std::atomic doesn't
+ // have the appropriate memory barrier behavior to accomplish the
+ // same thing so a full system mutex needs to be used.
+ std::unique_lock<CCriticalSection> blockLambdaTillDone(m_CriticalSection);
+ m_thread = new std::thread([](CThread* pThread, std::promise<bool> promise)
+ {
+ try
+ {
+
+ {
+ // Wait for the pThread->m_thread internals to be set. Otherwise we could
+ // get to a place where we're reading, say, the thread id inside this
+ // lambda's call stack prior to the thread that kicked off this lambda
+ // having it set. Once this lock is released, the CThread::Create function
+ // that kicked this off is done so everything should be set.
+ std::unique_lock<CCriticalSection> waitForThreadInternalsToBeSet(
+ pThread->m_CriticalSection);
+ }
+
+ // This is used in various helper methods like GetCurrentThread so it needs
+ // to be set before anything else is done.
+ currentThread = pThread;
+
+ std::string name;
+ bool autodelete;
+
+ if (pThread == nullptr)
+ {
+ CLog::Log(LOGERROR, "{}, sanity failed. thread is NULL.", __FUNCTION__);
+ promise.set_value(false);
+ return;
+ }
+
+ name = pThread->m_ThreadName;
+
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ std::string id = ss.str();
+ autodelete = pThread->m_bAutoDelete;
+
+ pThread->m_impl = IThreadImpl::CreateThreadImpl(pThread->m_thread->native_handle());
+ pThread->m_impl->SetThreadInfo(name);
+
+ CLog::Log(LOGDEBUG, "Thread {} start, auto delete: {}", name,
+ (autodelete ? "true" : "false"));
+
+ pThread->m_StartEvent.Set();
+
+ pThread->Action();
+
+ if (autodelete)
+ {
+ CLog::Log(LOGDEBUG, "Thread {} {} terminating (autodelete)", name, id);
+ delete pThread;
+ pThread = NULL;
+ }
+ else
+ CLog::Log(LOGDEBUG, "Thread {} {} terminating", name, id);
+ }
+ catch (const std::exception& e)
+ {
+ CLog::Log(LOGDEBUG, "Thread Terminating with Exception: {}", e.what());
+ }
+ catch (...)
+ {
+ CLog::Log(LOGDEBUG,"Thread Terminating with Exception");
+ }
+
+ promise.set_value(true);
+ }, this, std::move(prom));
+ } // let the lambda proceed
+
+ m_StartEvent.Wait(); // wait for the thread just spawned to set its internals
+}
+
+bool CThread::IsRunning() const
+{
+ if (m_thread != nullptr) {
+ // it's possible that the thread exited on it's own without a call to StopThread. If so then
+ // the promise should be fulfilled.
+ std::future_status stat = m_future.wait_for(std::chrono::milliseconds(0));
+ // a status of 'ready' means the future contains the value so the thread has exited
+ // since the thread can't exit without setting the future.
+ if (stat == std::future_status::ready) // this is an indication the thread has exited.
+ return false;
+ return true; // otherwise the thread is still active.
+ } else
+ return false;
+}
+
+bool CThread::SetPriority(const ThreadPriority& priority)
+{
+ return m_impl->SetPriority(priority);
+}
+
+bool CThread::IsAutoDelete() const
+{
+ return m_bAutoDelete;
+}
+
+void CThread::StopThread(bool bWait /*= true*/)
+{
+ m_StartEvent.Wait();
+
+ m_bStop = true;
+ m_StopEvent.Set();
+ std::unique_lock<CCriticalSection> lock(m_CriticalSection);
+ std::thread* lthread = m_thread;
+ if (lthread != nullptr && bWait && !IsCurrentThread())
+ {
+ lock.unlock();
+ if (!Join(std::chrono::milliseconds::max())) // eh?
+ lthread->join();
+ m_thread = nullptr;
+ }
+}
+
+void CThread::Process()
+{
+ if (m_pRunnable)
+ m_pRunnable->Run();
+}
+
+bool CThread::IsCurrentThread() const
+{
+ CThread* pThread = currentThread;
+ if (pThread != nullptr)
+ return pThread == this;
+ else
+ return false;
+}
+
+CThread* CThread::GetCurrentThread()
+{
+ return currentThread;
+}
+
+bool CThread::Join(std::chrono::milliseconds duration)
+{
+ std::unique_lock<CCriticalSection> l(m_CriticalSection);
+ std::thread* lthread = m_thread;
+ if (lthread != nullptr)
+ {
+ if (IsCurrentThread())
+ return false;
+
+ {
+ CSingleExit exit(m_CriticalSection); // don't hold the thread lock while we're waiting
+ std::future_status stat = m_future.wait_for(duration);
+ if (stat != std::future_status::ready)
+ return false;
+ }
+
+ // it's possible it's already joined since we released the lock above.
+ if (lthread->joinable())
+ m_thread->join();
+ return true;
+ }
+ else
+ return false;
+}
+
+void CThread::Action()
+{
+ try
+ {
+ OnStartup();
+ }
+ catch (const XbmcCommons::UncheckedException &e)
+ {
+ e.LogThrowMessage("OnStartup");
+ if (IsAutoDelete())
+ return;
+ }
+
+ try
+ {
+ Process();
+ }
+ catch (const XbmcCommons::UncheckedException &e)
+ {
+ e.LogThrowMessage("Process");
+ }
+
+ try
+ {
+ OnExit();
+ }
+ catch (const XbmcCommons::UncheckedException &e)
+ {
+ e.LogThrowMessage("OnExit");
+ }
+}
diff --git a/xbmc/threads/Thread.h b/xbmc/threads/Thread.h
new file mode 100644
index 0000000..c657d1b
--- /dev/null
+++ b/xbmc/threads/Thread.h
@@ -0,0 +1,125 @@
+/*
+ * 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
+
+// Thread.h: interface for the CThread class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "Event.h"
+
+#include <atomic>
+#include <future>
+#ifdef TARGET_DARWIN
+#include <mach/mach.h>
+#endif
+#include <stdint.h>
+#include <string>
+#include <thread>
+
+enum class ThreadPriority
+{
+ LOWEST,
+ BELOW_NORMAL,
+ NORMAL,
+ ABOVE_NORMAL,
+ HIGHEST,
+
+ /*!
+ * \brief Do not use this for priority. It is only needed to count the
+ * amount of values in the ThreadPriority enum.
+ *
+ */
+ PRIORITY_COUNT,
+};
+
+class IRunnable;
+class IThreadImpl;
+class CThread
+{
+protected:
+ explicit CThread(const char* ThreadName);
+
+public:
+ CThread(IRunnable* pRunnable, const char* ThreadName);
+ virtual ~CThread();
+ void Create(bool bAutoDelete = false);
+
+ template<typename Rep, typename Period>
+ void Sleep(std::chrono::duration<Rep, Period> duration)
+ {
+ if (duration > std::chrono::milliseconds(10) && IsCurrentThread())
+ m_StopEvent.Wait(duration);
+ else
+ std::this_thread::sleep_for(duration);
+ }
+
+ bool IsAutoDelete() const;
+ virtual void StopThread(bool bWait = true);
+ bool IsRunning() const;
+
+ bool IsCurrentThread() const;
+ bool Join(std::chrono::milliseconds duration);
+
+ inline static const std::thread::id GetCurrentThreadId()
+ {
+ return std::this_thread::get_id();
+ }
+
+ /*!
+ * \brief Set the threads priority. This uses the platforms
+ * native threading library to do so.
+ *
+ */
+ bool SetPriority(const ThreadPriority& priority);
+
+ static CThread* GetCurrentThread();
+
+ virtual void OnException(){} // signal termination handler
+
+protected:
+ virtual void OnStartup() {}
+ virtual void OnExit() {}
+ virtual void Process();
+
+ std::atomic<bool> m_bStop;
+
+ enum WaitResponse { WAIT_INTERRUPTED = -1, WAIT_SIGNALED = 0, WAIT_TIMEDOUT = 1 };
+
+ /**
+ * This call will wait on a CEvent in an interruptible way such that if
+ * stop is called on the thread the wait will return with a response
+ * indicating what happened.
+ */
+ inline WaitResponse AbortableWait(CEvent& event,
+ std::chrono::milliseconds duration =
+ std::chrono::milliseconds(-1) /* indicates wait forever*/)
+ {
+ XbmcThreads::CEventGroup group{&event, &m_StopEvent};
+ const CEvent* result =
+ duration < std::chrono::milliseconds::zero() ? group.wait() : group.wait(duration);
+ return result == &event ? WAIT_SIGNALED :
+ (result == NULL ? WAIT_TIMEDOUT : WAIT_INTERRUPTED);
+ }
+
+private:
+ void Action();
+
+ bool m_bAutoDelete = false;
+ CEvent m_StopEvent;
+ CEvent m_StartEvent;
+ CCriticalSection m_CriticalSection;
+ IRunnable* m_pRunnable;
+
+ std::string m_ThreadName;
+ std::thread* m_thread = nullptr;
+ std::future<bool> m_future;
+
+ std::unique_ptr<IThreadImpl> m_impl;
+};
diff --git a/xbmc/threads/Timer.cpp b/xbmc/threads/Timer.cpp
new file mode 100644
index 0000000..d06ba40
--- /dev/null
+++ b/xbmc/threads/Timer.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012-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 "Timer.h"
+
+#include <algorithm>
+
+using namespace std::chrono_literals;
+
+CTimer::CTimer(std::function<void()> const& callback)
+ : CThread("Timer"), m_callback(callback), m_timeout(0ms), m_interval(false)
+{ }
+
+CTimer::CTimer(ITimerCallback *callback)
+ : CTimer(std::bind(&ITimerCallback::OnTimeout, callback))
+{ }
+
+CTimer::~CTimer()
+{
+ Stop(true);
+}
+
+bool CTimer::Start(std::chrono::milliseconds timeout, bool interval /* = false */)
+{
+ if (m_callback == NULL || timeout == 0ms || IsRunning())
+ return false;
+
+ m_timeout = timeout;
+ m_interval = interval;
+
+ Create();
+ return true;
+}
+
+bool CTimer::Stop(bool wait /* = false */)
+{
+ if (!IsRunning())
+ return false;
+
+ m_bStop = true;
+ m_eventTimeout.Set();
+ StopThread(wait);
+
+ return true;
+}
+
+void CTimer::RestartAsync(std::chrono::milliseconds timeout)
+{
+ m_timeout = timeout;
+ m_endTime = std::chrono::steady_clock::now() + timeout;
+ m_eventTimeout.Set();
+}
+
+bool CTimer::Restart()
+{
+ if (!IsRunning())
+ return false;
+
+ Stop(true);
+
+ return Start(m_timeout, m_interval);
+}
+
+float CTimer::GetElapsedSeconds() const
+{
+ return GetElapsedMilliseconds() / 1000.0f;
+}
+
+float CTimer::GetElapsedMilliseconds() const
+{
+ if (!IsRunning())
+ return 0.0f;
+
+ auto now = std::chrono::steady_clock::now();
+ std::chrono::duration<float, std::milli> duration = (now - (m_endTime - m_timeout));
+
+ return duration.count();
+}
+
+void CTimer::Process()
+{
+ while (!m_bStop)
+ {
+ auto currentTime = std::chrono::steady_clock::now();
+ m_endTime = currentTime + m_timeout;
+
+ // wait the necessary time
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(m_endTime - currentTime);
+
+ if (!m_eventTimeout.Wait(duration))
+ {
+ currentTime = std::chrono::steady_clock::now();
+ if (m_endTime <= currentTime)
+ {
+ // execute OnTimeout() callback
+ m_callback();
+
+ // continue if this is an interval timer, or if it was restarted during callback
+ if (!m_interval && m_endTime <= currentTime)
+ break;
+ }
+ }
+ }
+}
diff --git a/xbmc/threads/Timer.h b/xbmc/threads/Timer.h
new file mode 100644
index 0000000..f434a38
--- /dev/null
+++ b/xbmc/threads/Timer.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012-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 "Event.h"
+#include "Thread.h"
+
+#include <chrono>
+#include <functional>
+
+class ITimerCallback
+{
+public:
+ virtual ~ITimerCallback() = default;
+
+ virtual void OnTimeout() = 0;
+};
+
+class CTimer : protected CThread
+{
+public:
+ explicit CTimer(ITimerCallback *callback);
+ explicit CTimer(std::function<void()> const& callback);
+ ~CTimer() override;
+
+ bool Start(std::chrono::milliseconds timeout, bool interval = false);
+ bool Stop(bool wait = false);
+ bool Restart();
+ void RestartAsync(std::chrono::milliseconds timeout);
+
+ bool IsRunning() const { return CThread::IsRunning(); }
+
+ float GetElapsedSeconds() const;
+ float GetElapsedMilliseconds() const;
+
+protected:
+ void Process() override;
+
+private:
+ std::function<void()> m_callback;
+ std::chrono::milliseconds m_timeout;
+ bool m_interval;
+ std::chrono::time_point<std::chrono::steady_clock> m_endTime;
+ CEvent m_eventTimeout;
+};
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));
+ }
+}
+