Kodi Development 22.0
for Binary and Script based Add-Ons
 
Loading...
Searching...
No Matches
Thread.h
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//==============================================================================
91{
92public:
93 //============================================================================
97 CThread() : m_threadStop(false) {}
98 //----------------------------------------------------------------------------
99
100 //============================================================================
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 //============================================================================
121 bool IsAutoDelete() const { return m_autoDelete; }
122 //----------------------------------------------------------------------------
123
124 //============================================================================
131 bool IsCurrentThread() const { return m_threadId == std::this_thread::get_id(); }
132 //----------------------------------------------------------------------------
133
134 //============================================================================
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 //============================================================================
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 {
207 try
208 {
209 {
210 // Wait for the pThread->m_thread internals to be set. Otherwise we could
211 // get to a place where we're reading, say, the thread id inside this
212 // lambda's call stack prior to the thread that kicked off this lambda
213 // having it set. Once this lock is released, the CThread::Create function
214 // that kicked this off is done so everything should be set.
215 std::unique_lock<std::recursive_mutex> lock(thread->m_threadMutex);
216 }
217
218 thread->m_threadId = std::this_thread::get_id();
219 std::stringstream ss;
220 ss << thread->m_threadId;
221 std::string id = ss.str();
222 bool autodelete = thread->m_autoDelete;
223
224 kodi::Log(ADDON_LOG_DEBUG, "Thread %s start, auto delete: %s", id.c_str(),
225 (autodelete ? "true" : "false"));
226
227 thread->m_running = true;
228 thread->m_startEvent.notify_one();
229
230 thread->Process();
231
232 if (autodelete)
233 {
234 kodi::Log(ADDON_LOG_DEBUG, "Thread %s terminating (autodelete)", id.c_str());
235 delete thread;
236 thread = nullptr;
237 }
238 else
239 kodi::Log(ADDON_LOG_DEBUG, "Thread %s terminating", id.c_str());
240 }
241 catch (const std::exception& e)
242 {
243 kodi::Log(ADDON_LOG_DEBUG, "Thread Terminating with Exception: %s", e.what());
244 }
245 catch (...)
246 {
247 kodi::Log(ADDON_LOG_DEBUG, "Thread Terminating with Exception");
248 }
249
250 promise.set_value(true);
251 },
252 this, std::move(prom));
253
254 m_startEvent.wait(lock);
255 }
256 }
257 //----------------------------------------------------------------------------
258
259 //============================================================================
267 void StopThread(bool wait = true)
268 {
269 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
270
271 if (m_threadStop)
272 return;
273
274 if (m_thread && !m_running)
275 m_startEvent.wait(lock);
276 m_running = false;
277 m_threadStop = true;
278 m_stopEvent.notify_one();
279
280 std::thread* lthread = m_thread;
281 if (lthread != nullptr && wait && !IsCurrentThread())
282 {
283 lock.unlock();
284 if (lthread->joinable())
285 lthread->join();
286 delete m_thread;
287 m_thread = nullptr;
288 m_threadId = std::thread::id();
289 }
290 }
291 //----------------------------------------------------------------------------
292
293 //============================================================================
306 void Sleep(uint32_t milliseconds)
307 {
308 if (milliseconds > 10 && IsCurrentThread())
309 {
310 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
311 m_stopEvent.wait_for(lock, std::chrono::milliseconds(milliseconds));
312 }
313 else
314 {
315 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
316 }
317 }
318 //----------------------------------------------------------------------------
319
320 //============================================================================
330 bool Join(unsigned int milliseconds)
331 {
332 std::unique_lock<std::recursive_mutex> lock(m_threadMutex);
333 std::thread* lthread = m_thread;
334 if (lthread != nullptr)
335 {
336 if (IsCurrentThread())
337 return false;
338
339 {
340 m_threadMutex.unlock(); // don't hold the thread lock while we're waiting
341 std::future_status stat = m_future.wait_for(std::chrono::milliseconds(milliseconds));
342 if (stat != std::future_status::ready)
343 return false;
344 m_threadMutex.lock();
345 }
346
347 // it's possible it's already joined since we released the lock above.
348 if (lthread->joinable())
349 m_thread->join();
350 return true;
351 }
352 else
353 return false;
354 }
355 //----------------------------------------------------------------------------
356
357protected:
358 //============================================================================
368 virtual void Process() = 0;
369 //----------------------------------------------------------------------------
370
371 //============================================================================
381 std::atomic<bool> m_threadStop;
382 //----------------------------------------------------------------------------
383
384private:
385 bool m_autoDelete = false;
386 bool m_running = false;
387 std::condition_variable_any m_stopEvent;
388 std::condition_variable_any m_startEvent;
389 std::recursive_mutex m_threadMutex;
390 std::thread::id m_threadId;
391 std::thread* m_thread = nullptr;
392 std::future<bool> m_future;
393};
395//------------------------------------------------------------------------------
396
397} /* namespace tools */
398} /* namespace kodi */
399
400#endif /* __cplusplus */
Definition Thread.h:91
@ ADDON_LOG_FATAL
4 : To notify fatal unrecoverable errors, which can may also indicate upcoming crashes.
Definition addon_base.h:197
@ ADDON_LOG_DEBUG
0 : To include debug information in the log file.
Definition addon_base.h:184
virtual void Process()=0
The function to be added by the addon as a child to carry out the process thread.
bool IsCurrentThread() const
Check caller is on this running thread.
Definition Thread.h:131
void Sleep(uint32_t milliseconds)
Thread sleep with given amount of milliseconds.
Definition Thread.h:306
bool Join(unsigned int milliseconds)
The function returns when the thread execution has completed or timing is reached in milliseconds bef...
Definition Thread.h:330
std::atomic< bool > m_threadStop
Atomic bool to indicate thread is active.
Definition Thread.h:381
void StopThread(bool wait=true)
Stop a running thread.
Definition Thread.h:267
virtual ~CThread()
Class destructor.
Definition Thread.h:104
bool IsRunning() const
Check thread inside this class is running and active.
Definition Thread.h:143
bool IsAutoDelete() const
Check auto delete is enabled on this thread class.
Definition Thread.h:121
CThread()
Class constructor.
Definition Thread.h:97
void CreateThread(bool autoDelete=false)
Create a new thread defined by this class on child.
Definition Thread.h:170
void ATTR_DLL_LOCAL Log(const ADDON_LOG loglevel, const char *format,...)
Add a message to Kodi's log.
Definition AddonBase.h:1938