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 
21 namespace kodi
22 {
23 namespace 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 ///@{
90 class CThread
91 {
92 public:
93   //============================================================================
94   /// @ingroup cpp_kodi_tools_CThread
95   /// @brief Class constructor.
96   ///
CThread()97   CThread() : m_threadStop(false) {}
98   //----------------------------------------------------------------------------
99 
100   //============================================================================
101   /// @ingroup cpp_kodi_tools_CThread
102   /// @brief Class destructor.
103   ///
~CThread()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   ///
IsAutoDelete()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   ///
IsCurrentThread()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   ///
IsRunning()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   ///
Sleep(uint32_t milliseconds)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   ///
Join(unsigned int milliseconds)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 
356 protected:
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 
383 private:
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 */
400