Kodi Development 19.0
for Binary and Script based Add-Ons
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 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 //============================================================================
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 //============================================================================
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 //============================================================================
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 //============================================================================
367 virtual void Process() = 0;
368 //----------------------------------------------------------------------------
369
370 //============================================================================
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};
394//------------------------------------------------------------------------------
395
396} /* namespace tools */
397} /* namespace kodi */
398
399#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:164
@ ADDON_LOG_DEBUG
0 : To include debug information in the log file.
Definition: addon_base.h:151
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:305
bool Join(unsigned int milliseconds)
The function returns when the thread execution has completed or timing is reached in milliseconds bef...
Definition: Thread.h:329
std::atomic< bool > m_threadStop
Atomic bool to indicate thread is active.
Definition: Thread.h:380
void StopThread(bool wait=true)
Stop a running thread.
Definition: Thread.h:266
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 ATTRIBUTE_HIDDEN Log(const AddonLog loglevel, const char *format,...)
Add a message to Kodi's log.
Definition: AddonBase.h:749