Dusk Audio Framework
Loading...
Searching...
No Matches
Thread.hpp
1/*
2 * DISTRHO Plugin Framework (DPF)
3 * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any purpose with
6 * or without fee is hereby granted, provided that the above copyright notice and this
7 * permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
10 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
11 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
13 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#ifndef DAF_THREAD_HPP_INCLUDED
18#define DAF_THREAD_HPP_INCLUDED
19
20#include "Mutex.hpp"
21#include "Sleep.hpp"
22#include "String.hpp"
23
24#include <atomic>
25
26#ifdef DAF_OS_LINUX
27# include <sys/prctl.h>
28#endif
29
30#ifdef DAF_OS_WASM
31# error Threads do not work under wasm!
32#endif
33
35
36// -----------------------------------------------------------------------
37// Thread class
38
39class Thread
40{
41protected:
42 /*
43 * Constructor.
44 */
45 Thread(const char* const threadName = nullptr) noexcept
46 : fLock(),
47 fSignal(),
48 fHandleSignal(),
49 fName(threadName),
50 #ifdef PTW32_DLLPORT
51 fHandle({nullptr, 0}),
52 #else
53 fHandle(0),
54 #endif
55 fRunToken(0),
56 fLastRunToken(0),
57 fShouldExit(false) {}
58
59 /*
60 * Destructor.
61 */
62 virtual ~Thread() /*noexcept*/
63 {
64 DAF_SAFE_ASSERT(! isThreadRunning());
65
66 stopThread(-1);
67 }
68
69 /*
70 * Virtual function to be implemented by the subclass.
71 */
72 virtual void run() = 0;
73
74 // -------------------------------------------------------------------
75
76public:
77 /*
78 * Check if the thread is running.
79 */
80 bool isThreadRunning() const noexcept
81 {
82 return fRunToken.load(std::memory_order_acquire) != 0;
83 }
84
85 /*
86 * Check if the thread should exit.
87 */
88 bool shouldThreadExit() const noexcept
89 {
90 return fShouldExit.load(std::memory_order_acquire);
91 }
92
93 /*
94 * Start the thread.
95 */
96 bool startThread(const bool withRealtimePriority = false) noexcept
97 {
98 // check if already running
99 DAF_SAFE_ASSERT_RETURN(! isThreadRunning(), true);
100
101 pthread_t handle;
102
103 pthread_attr_t attr;
104 pthread_attr_init(&attr);
105
106 struct sched_param sched_param = {};
107
108 if (withRealtimePriority)
109 {
110 #ifdef __MOD_DEVICES__
111 int rtprio;
112 const char* const srtprio = std::getenv("MOD_PLUGIN_THREAD_PRIORITY");
113 if (srtprio != nullptr && (rtprio = std::atoi(srtprio)) > 0)
114 sched_param.sched_priority = rtprio - 1;
115 else
116 #endif
117 sched_param.sched_priority = 80;
118
119 #ifndef DAF_OS_HAIKU
120 if (pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM) == 0 &&
121 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) == 0 &&
122 #ifndef DAF_OS_WINDOWS
123 (pthread_attr_setschedpolicy(&attr, SCHED_FIFO) == 0 ||
124 pthread_attr_setschedpolicy(&attr, SCHED_RR) == 0) &&
125 #endif
126 pthread_attr_setschedparam(&attr, &sched_param) == 0)
127 {
128 d_stdout("Thread setup with realtime priority successful");
129 }
130 else
131 #endif
132 {
133 d_stdout("Thread setup with realtime priority failed, going with normal priority instead");
134 pthread_attr_destroy(&attr);
135 pthread_attr_init(&attr);
136 }
137 }
138
139 const MutexLocker ml(fLock);
140
141 fShouldExit.store(false, std::memory_order_release);
142
143 // Mark the thread as running before it exists, so that a thread which finishes
144 // straight away cannot have its "done" overwritten by a late "running" from here.
145 // Every run gets its own non-zero token: an abandoned thread (see stopThread) that
146 // finishes later only clears the token it was started with, never a newer run's.
147 if (++fLastRunToken == 0)
148 ++fLastRunToken;
149 fRunToken.store(fLastRunToken, std::memory_order_release);
150
151 bool ok = pthread_create(&handle, &attr, _entryPoint, this) == 0;
152 pthread_attr_destroy(&attr);
153
154 if (withRealtimePriority && !ok)
155 {
156 d_stdout("Thread with realtime priority failed on creation, going with normal priority instead");
157 pthread_attr_init(&attr);
158 ok = pthread_create(&handle, &attr, _entryPoint, this) == 0;
159 pthread_attr_destroy(&attr);
160 }
161
162 if (! ok)
163 {
164 fRunToken.store(0, std::memory_order_release);
165 d_safe_assert("ok", __FILE__, __LINE__);
166 return false;
167 }
168
169 // publish the handle, then let the new thread proceed past its entry point;
170 // the signal orders this write before anything the thread does in run().
171 fHandle = handle;
172 fHandleSignal.signal();
173
174 pthread_detach(handle);
175
176 // wait for thread to start
177 fSignal.wait();
178 return true;
179 }
180
181 /*
182 * Stop the thread.
183 * In the 'timeOutMilliseconds':
184 * = 0 -> no wait
185 * > 0 -> wait timeout value
186 * < 0 -> wait forever
187 */
188 bool stopThread(const int timeOutMilliseconds) noexcept
189 {
190 const MutexLocker ml(fLock);
191
192 if (isThreadRunning())
193 {
194 signalThreadShouldExit();
195
196 if (timeOutMilliseconds != 0)
197 {
198 // Wait for the thread to stop
199 int timeOutCheck = (timeOutMilliseconds == 1 || timeOutMilliseconds == -1) ? timeOutMilliseconds : timeOutMilliseconds/2;
200
201 for (; isThreadRunning();)
202 {
203 d_msleep(2);
204
205 if (timeOutCheck < 0)
206 continue;
207
208 if (timeOutCheck > 0)
209 timeOutCheck -= 1;
210 else
211 break;
212 }
213 }
214
215 if (isThreadRunning())
216 {
217 // should never happen!
218 d_stderr2("assertion failure: \"! isThreadRunning()\" in file %s, line %i", __FILE__, __LINE__);
219
220 // give up on the thread: it is already detached, so just stop tracking it.
221 // Clearing the token (rather than letting the thread do it) allows a restart,
222 // and the thread's own exit will leave a newer run's token alone.
223 fRunToken.store(0, std::memory_order_release);
224 return false;
225 }
226 }
227
228 return true;
229 }
230
231 /*
232 * Tell the thread to stop as soon as possible.
233 */
234 void signalThreadShouldExit() noexcept
235 {
236 fShouldExit.store(true, std::memory_order_release);
237 }
238
239 // -------------------------------------------------------------------
240
241 /*
242 * Returns the name of the thread.
243 * This is the name that gets set in the constructor.
244 */
245 const String& getThreadName() const noexcept
246 {
247 return fName;
248 }
249
250 /*
251 * Returns the Id/handle of the thread, or a null handle if the thread is not running.
252 * The handle is written only by startThread(), so this must not race with a startThread() call.
253 */
254 pthread_t getThreadId() const noexcept
255 {
256 if (isThreadRunning())
257 return fHandle;
258
259 #ifdef PTW32_DLLPORT
260 const pthread_t nullHandle = {nullptr, 0};
261 return nullHandle;
262 #else
263 return 0;
264 #endif
265 }
266
267 /*
268 * Changes the name of the caller thread.
269 */
270 static void setCurrentThreadName(const char* const name) noexcept
271 {
272 DAF_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0',);
273
274 #ifdef DAF_OS_LINUX
275 prctl(PR_SET_NAME, name, 0, 0, 0);
276 #endif
277 #if defined(__GLIBC__) && (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 && !defined(DAF_OS_GNU_HURD)
278 pthread_setname_np(pthread_self(), name);
279 #endif
280 #ifdef DAF_OS_MAC
281 // macOS only provides the single-argument form, which always renames the calling thread
282 pthread_setname_np(name);
283 #endif
284 #ifdef DAF_OS_WINDOWS
285 // SetThreadDescription requires Windows 10 1607 or later,
286 // resolve it dynamically so that older systems simply get no thread name.
287 typedef HRESULT(WINAPI* PFN_SetThreadDescription)(HANDLE, PCWSTR);
288
289 #if defined(__GNUC__) && (__GNUC__ >= 9)
290 # pragma GCC diagnostic push
291 # pragma GCC diagnostic ignored "-Wcast-function-type"
292 #endif
293 static const PFN_SetThreadDescription setThreadDescription = (PFN_SetThreadDescription)
294 GetProcAddress(GetModuleHandleA("kernel32.dll"), "SetThreadDescription");
295 #if defined(__GNUC__) && (__GNUC__ >= 9)
296 # pragma GCC diagnostic pop
297 #endif
298
299 if (setThreadDescription != nullptr)
300 {
301 // thread names are short by convention, a fixed buffer is plenty
302 WCHAR wname[128];
303
304 if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, static_cast<int>(ARRAY_SIZE(wname))) > 0)
305 setThreadDescription(GetCurrentThread(), wname);
306 }
307 #endif
308 }
309
310 // -------------------------------------------------------------------
311
312private:
313 Mutex fLock; // Thread lock, serialises startThread() and stopThread()
314 Signal fSignal; // Thread start wait signal
315 Signal fHandleSignal; // Handle published, the new thread may proceed
316 const String fName; // Thread name
317 pthread_t fHandle; // Handle for this thread, written by startThread() only
318 std::atomic<uint32_t> fRunToken; // non-zero while running, unique per run
319 uint32_t fLastRunToken; // last token handed out, guarded by fLock
320 std::atomic<bool> fShouldExit; // true if thread should exit
321
322 /*
323 * Thread entry point.
324 */
325 void _runEntryPoint() noexcept
326 {
327 // wait until startThread() has stored our handle
328 fHandleSignal.wait();
329
330 // startThread() holds fLock until we report ready, so this is our own run's token
331 uint32_t runToken = fRunToken.load(std::memory_order_acquire);
332
333 if (fName.isNotEmpty())
334 setCurrentThreadName(fName);
335
336 // report ready
337 fSignal.signal();
338
339 try {
340 run();
341 } catch(...) {}
342
343 // done. Only clear our own token, stopThread() may have given up on us and started anew.
344 // This must be the last access to *this, the owner may destroy us as soon as it sees it.
345 fRunToken.compare_exchange_strong(runToken, 0, std::memory_order_acq_rel, std::memory_order_relaxed);
346 }
347
348 /*
349 * Thread entry point.
350 */
351 static void* _entryPoint(void* userData) noexcept
352 {
353 static_cast<Thread*>(userData)->_runEntryPoint();
354 return nullptr;
355 }
356
357 DAF_DECLARE_NON_COPYABLE(Thread)
358};
359
360// -----------------------------------------------------------------------
361
363
364#endif // DAF_THREAD_HPP_INCLUDED
Definition Mutex.hpp:45
Definition Mutex.hpp:220
Definition String.hpp:35
#define END_NAMESPACE_DAF
Definition DafInfo.hpp:996
#define START_NAMESPACE_DAF
Definition DafInfo.hpp:990