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