summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
committermanuel <manuel@mausz.at>2020-10-19 00:52:24 +0200
commitbe933ef2241d79558f91796cc5b3a161f72ebf9c (patch)
treefe3ab2f130e20c99001f2d7a81d610c78c96a3f4 /xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h
parent5f8335c1e49ce108ef3481863833c98efa00411b (diff)
downloadkodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.gz
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.tar.bz2
kodi-pvr-build-be933ef2241d79558f91796cc5b3a161f72ebf9c.zip
sync with upstream
Diffstat (limited to 'xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h')
-rw-r--r--xbmc/addons/kodi-dev-kit/include/kodi/tools/Thread.h399
1 files changed, 399 insertions, 0 deletions
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 @@
1/*
2 * Copyright (C) 2005-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9#pragma once
10
11#ifdef __cplusplus
12
13#include "../General.h"
14
15#include <chrono>
16#include <condition_variable>
17#include <future>
18#include <mutex>
19#include <thread>
20
21namespace kodi
22{
23namespace tools
24{
25
26//==============================================================================
27/// @defgroup cpp_kodi_tools_CThread class CThread
28/// @ingroup cpp_kodi_tools
29/// @brief **Helper class to represent threads of execution**\n
30/// An execution thread is a sequence of instructions that can run concurrently
31/// with other such sequences in multithreaded environments while sharing the
32/// same address space.
33///
34/// Is intended to reduce any code work of C++ on addons and to have them faster
35/// to use.
36///
37/// His code uses the support of platform-independent thread system introduced
38/// with C++11.
39///
40/// ----------------------------------------------------------------------------
41///
42/// **Example:**
43/// ~~~~~~~~~~~~~{.cpp}
44/// #include <kodi/tools/Thread.h>
45/// #include <kodi/AddonBase.h>
46///
47/// class ATTRIBUTE_HIDDEN CTestAddon
48/// : public kodi::addon::CAddonBase,
49/// public kodi::tools::CThread
50/// {
51/// public:
52/// CTestAddon() = default;
53///
54/// ADDON_STATUS Create() override;
55///
56/// void Process() override;
57/// };
58///
59/// ADDON_STATUS CTestAddon::Create()
60/// {
61/// kodi::Log(ADDON_LOG_INFO, "Starting thread");
62/// CreateThread();
63///
64/// Sleep(4000);
65///
66/// kodi::Log(ADDON_LOG_INFO, "Stopping thread");
67/// // This added as example and also becomes stopped by class destructor
68/// StopThread();
69///
70/// return ADDON_STATUS_OK;
71/// }
72///
73/// void CTestAddon::Process()
74/// {
75/// kodi::Log(ADDON_LOG_INFO, "Thread started");
76///
77/// while (!m_threadStop)
78/// {
79/// kodi::Log(ADDON_LOG_INFO, "Hello World");
80/// Sleep(1000);
81/// }
82///
83/// kodi::Log(ADDON_LOG_INFO, "Thread ended");
84/// }
85///
86/// ADDONCREATOR(CTestAddon)
87/// ~~~~~~~~~~~~~
88///
89///@{
90class CThread
91{
92public:
93 //============================================================================
94 /// @ingroup cpp_kodi_tools_CThread
95 /// @brief Class constructor.
96 ///
97 CThread() : m_threadStop(false) {}
98 //----------------------------------------------------------------------------
99
100 //============================================================================
101 /// @ingroup cpp_kodi_tools_CThread
102 /// @brief Class destructor.
103 ///
104 virtual ~CThread()
105 {
106 StopThread();
107 if (m_thread != nullptr)
108 {
109 m_thread->detach();
110 delete m_thread;
111 }
112 }
113 //----------------------------------------------------------------------------
114
115 //============================================================================
116 /// @ingroup cpp_kodi_tools_CThread
117 /// @brief Check auto delete is enabled on this thread class.
118 ///
119 /// @return true if auto delete is used, false otherwise
120 ///
121 bool IsAutoDelete() const { return m_autoDelete; }
122 //----------------------------------------------------------------------------
123
124 //============================================================================
125 /// @ingroup cpp_kodi_tools_CThread
126 /// @brief Check caller is on this running thread.
127 ///
128 /// @return true if called from thread inside the class, false if from another
129 /// thread
130 ///
131 bool IsCurrentThread() const { return m_threadId == std::this_thread::get_id(); }
132 //----------------------------------------------------------------------------
133
134 //============================================================================
135 /// @ingroup cpp_kodi_tools_CThread
136 /// @brief Check thread inside this class is running and active.
137 ///
138 /// @note This function should be used from outside and not within process to
139 /// check thread is active. Use use atomic bool @ref m_threadStop for this.
140 ///
141 /// @return true if running, false if not
142 ///
143 bool IsRunning() const
144 {
145 if (m_thread != nullptr)
146 {
147 // it's possible that the thread exited on it's own without a call to StopThread. If so then
148 // the promise should be fulfilled.
149 std::future_status stat = m_future.wait_for(std::chrono::milliseconds(0));
150 // a status of 'ready' means the future contains the value so the thread has exited
151 // since the thread can't exit without setting the future.
152 if (stat == std::future_status::ready) // this is an indication the thread has exited.
153 return false;
154 return true; // otherwise the thread is still active.
155 }
156 else
157 return false;
158 }
159 //----------------------------------------------------------------------------
160
161 //============================================================================
162 /// @ingroup cpp_kodi_tools_CThread
163 /// @brief Create a new thread defined by this class on child.
164 ///
165 /// This starts then @ref Process() where is available on the child by addon.
166 ///
167 /// @param[in] autoDelete To set thread to delete itself after end, default is
168 /// false
169 ///
170 void CreateThread(bool autoDelete = false)
171 {
172 if (m_thread != nullptr)
173 {
174 // if the thread exited on it's own, without a call to StopThread, then we can get here
175 // incorrectly. We should be able to determine this by checking the promise.
176 std::future_status stat = m_future.wait_for(std::chrono::milliseconds(0));
177 // a status of 'ready' means the future contains the value so the thread has exited
178 // since the thread can't exit without setting the future.
179 if (stat == std::future_status::ready) // this is an indication the thread has exited.
180 StopThread(true); // so let's just clean up
181 else
182 { // otherwise we have a problem.
183 kodi::Log(ADDON_LOG_FATAL, "%s - fatal error creating thread - old thread id not null",
184 __func__);
185 exit(1);
186 }
187 }
188
189 m_autoDelete = autoDelete;
190 m_threadStop = false;
191 m_startEvent.notify_all();
192 m_stopEvent.notify_all();
193
194 std::promise<bool> prom;
195 m_future = prom.get_future();
196
197 {
198 // The std::thread internals must be set prior to the lambda doing
199 // any work. This will cause the lambda to wait until m_thread
200 // is fully initialized. Interestingly, using a std::atomic doesn't
201 // have the appropriate memory barrier behavior to accomplish the
202 // same thing so a full system mutex needs to be used.
203 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
204 m_thread = new std::thread(
205 [](CThread* thread, std::promise<bool> promise) {
206 try
207 {
208 {
209 // Wait for the pThread->m_thread internals to be set. Otherwise we could
210 // get to a place where we're reading, say, the thread id inside this
211 // lambda's call stack prior to the thread that kicked off this lambda
212 // having it set. Once this lock is released, the CThread::Create function
213 // that kicked this off is done so everything should be set.
214 std::unique_lock<std::recursive_mutex> lock(thread->m_threadMutex);
215 }
216
217 thread->m_threadId = std::this_thread::get_id();
218 std::stringstream ss;
219 ss << thread->m_threadId;
220 std::string id = ss.str();
221 bool autodelete = thread->m_autoDelete;
222
223 kodi::Log(ADDON_LOG_DEBUG, "Thread %s start, auto delete: %s", id.c_str(),
224 (autodelete ? "true" : "false"));
225
226 thread->m_running = true;
227 thread->m_startEvent.notify_one();
228
229 thread->Process();
230
231 if (autodelete)
232 {
233 kodi::Log(ADDON_LOG_DEBUG, "Thread %s terminating (autodelete)", id.c_str());
234 delete thread;
235 thread = nullptr;
236 }
237 else
238 kodi::Log(ADDON_LOG_DEBUG, "Thread %s terminating", id.c_str());
239 }
240 catch (const std::exception& e)
241 {
242 kodi::Log(ADDON_LOG_DEBUG, "Thread Terminating with Exception: %s", e.what());
243 }
244 catch (...)
245 {
246 kodi::Log(ADDON_LOG_DEBUG, "Thread Terminating with Exception");
247 }
248
249 promise.set_value(true);
250 },
251 this, std::move(prom));
252
253 m_startEvent.wait(lock);
254 }
255 }
256 //----------------------------------------------------------------------------
257
258 //============================================================================
259 /// @ingroup cpp_kodi_tools_CThread
260 /// @brief Stop a running thread.
261 ///
262 /// @param[in] wait As true (default) to wait until thread is finished and
263 /// stopped, as false the function return directly and thread
264 /// becomes independently stopped.
265 ///
266 void StopThread(bool wait = true)
267 {
268 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
269
270 if (m_threadStop)
271 return;
272
273 if (m_thread && !m_running)
274 m_startEvent.wait(lock);
275 m_running = false;
276 m_threadStop = true;
277 m_stopEvent.notify_one();
278
279 std::thread* lthread = m_thread;
280 if (lthread != nullptr && wait && !IsCurrentThread())
281 {
282 lock.unlock();
283 if (lthread->joinable())
284 lthread->join();
285 delete m_thread;
286 m_thread = nullptr;
287 m_threadId = std::thread::id();
288 }
289 }
290 //----------------------------------------------------------------------------
291
292 //============================================================================
293 /// @ingroup cpp_kodi_tools_CThread
294 /// @brief Thread sleep with given amount of milliseconds.
295 ///
296 /// This makes a sleep in the thread with a given time value. If it is called
297 /// within the process itself, it is also checked whether the thread is
298 /// terminated and the sleep process is thereby interrupted.
299 ///
300 /// If the external point calls this, only a regular sleep is used, which runs
301 /// through completely.
302 ///
303 /// @param[in] milliseconds Time to sleep
304 ///
305 void Sleep(uint32_t milliseconds)
306 {
307 if (milliseconds > 10 && IsCurrentThread())
308 {
309 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
310 m_stopEvent.wait_for(lock, std::chrono::milliseconds(milliseconds));
311 }
312 else
313 {
314 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
315 }
316 }
317 //----------------------------------------------------------------------------
318
319 //============================================================================
320 /// @ingroup cpp_kodi_tools_CThread
321 /// @brief The function returns when the thread execution has completed or
322 /// timing is reached in milliseconds beforehand
323 ///
324 /// This synchronizes the moment this function returns with the completion of
325 /// all operations on the thread.
326 ///
327 /// @param[in] milliseconds Time to wait for join
328 ///
329 bool Join(unsigned int milliseconds)
330 {
331 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
332 std::thread* lthread = m_thread;
333 if (lthread != nullptr)
334 {
335 if (IsCurrentThread())
336 return false;
337
338 {
339 m_threadMutex.unlock(); // don't hold the thread lock while we're waiting
340 std::future_status stat = m_future.wait_for(std::chrono::milliseconds(milliseconds));
341 if (stat != std::future_status::ready)
342 return false;
343 m_threadMutex.lock();
344 }
345
346 // it's possible it's already joined since we released the lock above.
347 if (lthread->joinable())
348 m_thread->join();
349 return true;
350 }
351 else
352 return false;
353 }
354 //----------------------------------------------------------------------------
355
356protected:
357 //============================================================================
358 /// @ingroup cpp_kodi_tools_CThread
359 /// @brief The function to be added by the addon as a child to carry out the
360 /// process thread.
361 ///
362 /// Use @ref m_threadStop to check about active of thread and want stopped from
363 /// external place.
364 ///
365 /// @note This function is necessary and must be implemented by the addon.
366 ///
367 virtual void Process() = 0;
368 //----------------------------------------------------------------------------
369
370 //============================================================================
371 /// @ingroup cpp_kodi_tools_CThread
372 /// @brief Atomic bool to indicate thread is active.
373 ///
374 /// This should be used in @ref Process() to check the activity of the thread and,
375 /// if true, to terminate the process.
376 ///
377 /// - <b>`false`</b>: Thread active and should be run
378 /// - <b>`true`</b>: Thread ends and should be stopped
379 ///
380 std::atomic<bool> m_threadStop;
381 //----------------------------------------------------------------------------
382
383private:
384 bool m_autoDelete = false;
385 bool m_running = false;
386 std::condition_variable_any m_stopEvent;
387 std::condition_variable_any m_startEvent;
388 std::recursive_mutex m_threadMutex;
389 std::thread::id m_threadId;
390 std::thread* m_thread = nullptr;
391 std::future<bool> m_future;
392};
393///@}
394//------------------------------------------------------------------------------
395
396} /* namespace tools */
397} /* namespace kodi */
398
399#endif /* __cplusplus */