From be933ef2241d79558f91796cc5b3a161f72ebf9c Mon Sep 17 00:00:00 2001 From: manuel Date: Mon, 19 Oct 2020 00:52:24 +0200 Subject: sync with upstream --- .../kodi-dev-kit/include/kodi/tools/Thread.h | 399 +++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h (limited to 'xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h') diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h b/xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h new file mode 100644 index 0000000..4cae13e --- /dev/null +++ b/xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h @@ -0,0 +1,399 @@ +/* + * 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. + */ + +#pragma once + +#ifdef __cplusplus + +#include "../General.h" + +#include +#include +#include +#include +#include + +namespace kodi +{ +namespace tools +{ + +//============================================================================== +/// @defgroup cpp_kodi_tools_CThread class CThread +/// @ingroup cpp_kodi_tools +/// @brief **Helper class to represent threads of execution**\n +/// An execution thread is a sequence of instructions that can run concurrently +/// with other such sequences in multithreaded environments while sharing the +/// same address space. +/// +/// Is intended to reduce any code work of C++ on addons and to have them faster +/// to use. +/// +/// His code uses the support of platform-independent thread system introduced +/// with C++11. +/// +/// ---------------------------------------------------------------------------- +/// +/// **Example:** +/// ~~~~~~~~~~~~~{.cpp} +/// #include +/// #include +/// +/// class ATTRIBUTE_HIDDEN CTestAddon +/// : public kodi::addon::CAddonBase, +/// public kodi::tools::CThread +/// { +/// public: +/// CTestAddon() = default; +/// +/// ADDON_STATUS Create() override; +/// +/// void Process() override; +/// }; +/// +/// ADDON_STATUS CTestAddon::Create() +/// { +/// kodi::Log(ADDON_LOG_INFO, "Starting thread"); +/// CreateThread(); +/// +/// Sleep(4000); +/// +/// kodi::Log(ADDON_LOG_INFO, "Stopping thread"); +/// // This added as example and also becomes stopped by class destructor +/// StopThread(); +/// +/// return ADDON_STATUS_OK; +/// } +/// +/// void CTestAddon::Process() +/// { +/// kodi::Log(ADDON_LOG_INFO, "Thread started"); +/// +/// while (!m_threadStop) +/// { +/// kodi::Log(ADDON_LOG_INFO, "Hello World"); +/// Sleep(1000); +/// } +/// +/// kodi::Log(ADDON_LOG_INFO, "Thread ended"); +/// } +/// +/// ADDONCREATOR(CTestAddon) +/// ~~~~~~~~~~~~~ +/// +///@{ +class CThread +{ +public: + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Class constructor. + /// + CThread() : m_threadStop(false) {} + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Class destructor. + /// + virtual ~CThread() + { + StopThread(); + if (m_thread != nullptr) + { + m_thread->detach(); + delete m_thread; + } + } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Check auto delete is enabled on this thread class. + /// + /// @return true if auto delete is used, false otherwise + /// + bool IsAutoDelete() const { return m_autoDelete; } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Check caller is on this running thread. + /// + /// @return true if called from thread inside the class, false if from another + /// thread + /// + bool IsCurrentThread() const { return m_threadId == std::this_thread::get_id(); } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Check thread inside this class is running and active. + /// + /// @note This function should be used from outside and not within process to + /// check thread is active. Use use atomic bool @ref m_threadStop for this. + /// + /// @return true if running, false if not + /// + bool 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; + } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Create a new thread defined by this class on child. + /// + /// This starts then @ref Process() where is available on the child by addon. + /// + /// @param[in] autoDelete To set thread to delete itself after end, default is + /// false + /// + void CreateThread(bool autoDelete = false) + { + 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. + kodi::Log(ADDON_LOG_FATAL, "%s - fatal error creating thread - old thread id not null", + __func__); + exit(1); + } + } + + m_autoDelete = autoDelete; + m_threadStop = false; + m_startEvent.notify_all(); + m_stopEvent.notify_all(); + + std::promise 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 lock(m_threadMutex); + m_thread = new std::thread( + [](CThread* thread, std::promise 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 lock(thread->m_threadMutex); + } + + thread->m_threadId = std::this_thread::get_id(); + std::stringstream ss; + ss << thread->m_threadId; + std::string id = ss.str(); + bool autodelete = thread->m_autoDelete; + + kodi::Log(ADDON_LOG_DEBUG, "Thread %s start, auto delete: %s", id.c_str(), + (autodelete ? "true" : "false")); + + thread->m_running = true; + thread->m_startEvent.notify_one(); + + thread->Process(); + + if (autodelete) + { + kodi::Log(ADDON_LOG_DEBUG, "Thread %s terminating (autodelete)", id.c_str()); + delete thread; + thread = nullptr; + } + else + kodi::Log(ADDON_LOG_DEBUG, "Thread %s terminating", id.c_str()); + } + catch (const std::exception& e) + { + kodi::Log(ADDON_LOG_DEBUG, "Thread Terminating with Exception: %s", e.what()); + } + catch (...) + { + kodi::Log(ADDON_LOG_DEBUG, "Thread Terminating with Exception"); + } + + promise.set_value(true); + }, + this, std::move(prom)); + + m_startEvent.wait(lock); + } + } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Stop a running thread. + /// + /// @param[in] wait As true (default) to wait until thread is finished and + /// stopped, as false the function return directly and thread + /// becomes independently stopped. + /// + void StopThread(bool wait = true) + { + std::unique_lock lock(m_threadMutex); + + if (m_threadStop) + return; + + if (m_thread && !m_running) + m_startEvent.wait(lock); + m_running = false; + m_threadStop = true; + m_stopEvent.notify_one(); + + std::thread* lthread = m_thread; + if (lthread != nullptr && wait && !IsCurrentThread()) + { + lock.unlock(); + if (lthread->joinable()) + lthread->join(); + delete m_thread; + m_thread = nullptr; + m_threadId = std::thread::id(); + } + } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Thread sleep with given amount of milliseconds. + /// + /// This makes a sleep in the thread with a given time value. If it is called + /// within the process itself, it is also checked whether the thread is + /// terminated and the sleep process is thereby interrupted. + /// + /// If the external point calls this, only a regular sleep is used, which runs + /// through completely. + /// + /// @param[in] milliseconds Time to sleep + /// + void Sleep(uint32_t milliseconds) + { + if (milliseconds > 10 && IsCurrentThread()) + { + std::unique_lock lock(m_threadMutex); + m_stopEvent.wait_for(lock, std::chrono::milliseconds(milliseconds)); + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); + } + } + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief The function returns when the thread execution has completed or + /// timing is reached in milliseconds beforehand + /// + /// This synchronizes the moment this function returns with the completion of + /// all operations on the thread. + /// + /// @param[in] milliseconds Time to wait for join + /// + bool Join(unsigned int milliseconds) + { + std::unique_lock lock(m_threadMutex); + std::thread* lthread = m_thread; + if (lthread != nullptr) + { + if (IsCurrentThread()) + return false; + + { + m_threadMutex.unlock(); // don't hold the thread lock while we're waiting + std::future_status stat = m_future.wait_for(std::chrono::milliseconds(milliseconds)); + if (stat != std::future_status::ready) + return false; + m_threadMutex.lock(); + } + + // it's possible it's already joined since we released the lock above. + if (lthread->joinable()) + m_thread->join(); + return true; + } + else + return false; + } + //---------------------------------------------------------------------------- + +protected: + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief The function to be added by the addon as a child to carry out the + /// process thread. + /// + /// Use @ref m_threadStop to check about active of thread and want stopped from + /// external place. + /// + /// @note This function is necessary and must be implemented by the addon. + /// + virtual void Process() = 0; + //---------------------------------------------------------------------------- + + //============================================================================ + /// @ingroup cpp_kodi_tools_CThread + /// @brief Atomic bool to indicate thread is active. + /// + /// This should be used in @ref Process() to check the activity of the thread and, + /// if true, to terminate the process. + /// + /// - `false`: Thread active and should be run + /// - `true`: Thread ends and should be stopped + /// + std::atomic m_threadStop; + //---------------------------------------------------------------------------- + +private: + bool m_autoDelete = false; + bool m_running = false; + std::condition_variable_any m_stopEvent; + std::condition_variable_any m_startEvent; + std::recursive_mutex m_threadMutex; + std::thread::id m_threadId; + std::thread* m_thread = nullptr; + std::future m_future; +}; +///@} +//------------------------------------------------------------------------------ + +} /* namespace tools */ +} /* namespace kodi */ + +#endif /* __cplusplus */ -- cgit v1.2.3