Thread.hpp (9051B)
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 DISTRHO_THREAD_HPP_INCLUDED 18 #define DISTRHO_THREAD_HPP_INCLUDED 19 20 #include "Mutex.hpp" 21 #include "Sleep.hpp" 22 #include "String.hpp" 23 24 #ifdef DISTRHO_OS_LINUX 25 # include <sys/prctl.h> 26 #endif 27 28 #ifdef DISTRHO_OS_WASM 29 # error Threads do not work under wasm! 30 #endif 31 32 START_NAMESPACE_DISTRHO 33 34 // ----------------------------------------------------------------------- 35 // Thread class 36 37 class Thread 38 { 39 protected: 40 /* 41 * Constructor. 42 */ 43 Thread(const char* const threadName = nullptr) noexcept 44 : fLock(), 45 fSignal(), 46 fName(threadName), 47 #ifdef PTW32_DLLPORT 48 fHandle({nullptr, 0}), 49 #else 50 fHandle(0), 51 #endif 52 fShouldExit(false) {} 53 54 /* 55 * Destructor. 56 */ 57 virtual ~Thread() /*noexcept*/ 58 { 59 DISTRHO_SAFE_ASSERT(! isThreadRunning()); 60 61 stopThread(-1); 62 } 63 64 /* 65 * Virtual function to be implemented by the subclass. 66 */ 67 virtual void run() = 0; 68 69 // ------------------------------------------------------------------- 70 71 public: 72 /* 73 * Check if the thread is running. 74 */ 75 bool isThreadRunning() const noexcept 76 { 77 #ifdef PTW32_DLLPORT 78 return (fHandle.p != nullptr); 79 #else 80 return (fHandle != 0); 81 #endif 82 } 83 84 /* 85 * Check if the thread should exit. 86 */ 87 bool shouldThreadExit() const noexcept 88 { 89 return fShouldExit; 90 } 91 92 /* 93 * Start the thread. 94 */ 95 bool startThread(const bool withRealtimePriority = false) noexcept 96 { 97 // check if already running 98 DISTRHO_SAFE_ASSERT_RETURN(! isThreadRunning(), true); 99 100 pthread_t handle; 101 102 pthread_attr_t attr; 103 pthread_attr_init(&attr); 104 105 struct sched_param sched_param = {}; 106 107 if (withRealtimePriority) 108 { 109 #ifdef __MOD_DEVICES__ 110 int rtprio; 111 const char* const srtprio = std::getenv("MOD_PLUGIN_THREAD_PRIORITY"); 112 if (srtprio != nullptr && (rtprio = std::atoi(srtprio)) > 0) 113 sched_param.sched_priority = rtprio - 1; 114 else 115 #endif 116 sched_param.sched_priority = 80; 117 118 #ifndef DISTRHO_OS_HAIKU 119 if (pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM) == 0 && 120 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) == 0 && 121 #ifndef DISTRHO_OS_WINDOWS 122 (pthread_attr_setschedpolicy(&attr, SCHED_FIFO) == 0 || 123 pthread_attr_setschedpolicy(&attr, SCHED_RR) == 0) && 124 #endif 125 pthread_attr_setschedparam(&attr, &sched_param) == 0) 126 { 127 d_stdout("Thread setup with realtime priority successful"); 128 } 129 else 130 #endif 131 { 132 d_stdout("Thread setup with realtime priority failed, going with normal priority instead"); 133 pthread_attr_destroy(&attr); 134 pthread_attr_init(&attr); 135 } 136 } 137 138 const MutexLocker ml(fLock); 139 140 fShouldExit = false; 141 142 bool ok = pthread_create(&handle, &attr, _entryPoint, this) == 0; 143 pthread_attr_destroy(&attr); 144 145 if (withRealtimePriority && !ok) 146 { 147 d_stdout("Thread with realtime priority failed on creation, going with normal priority instead"); 148 pthread_attr_init(&attr); 149 ok = pthread_create(&handle, &attr, _entryPoint, this) == 0; 150 pthread_attr_destroy(&attr); 151 } 152 153 DISTRHO_SAFE_ASSERT_RETURN(ok, false); 154 #ifdef PTW32_DLLPORT 155 DISTRHO_SAFE_ASSERT_RETURN(handle.p != nullptr, false); 156 #else 157 DISTRHO_SAFE_ASSERT_RETURN(handle != 0, false); 158 #endif 159 pthread_detach(handle); 160 _copyFrom(handle); 161 162 // wait for thread to start 163 fSignal.wait(); 164 return true; 165 } 166 167 /* 168 * Stop the thread. 169 * In the 'timeOutMilliseconds': 170 * = 0 -> no wait 171 * > 0 -> wait timeout value 172 * < 0 -> wait forever 173 */ 174 bool stopThread(const int timeOutMilliseconds) noexcept 175 { 176 const MutexLocker ml(fLock); 177 178 if (isThreadRunning()) 179 { 180 signalThreadShouldExit(); 181 182 if (timeOutMilliseconds != 0) 183 { 184 // Wait for the thread to stop 185 int timeOutCheck = (timeOutMilliseconds == 1 || timeOutMilliseconds == -1) ? timeOutMilliseconds : timeOutMilliseconds/2; 186 187 for (; isThreadRunning();) 188 { 189 d_msleep(2); 190 191 if (timeOutCheck < 0) 192 continue; 193 194 if (timeOutCheck > 0) 195 timeOutCheck -= 1; 196 else 197 break; 198 } 199 } 200 201 if (isThreadRunning()) 202 { 203 // should never happen! 204 d_stderr2("assertion failure: \"! isThreadRunning()\" in file %s, line %i", __FILE__, __LINE__); 205 206 // copy thread id so we can clear our one 207 pthread_t threadId; 208 _copyTo(threadId); 209 _init(); 210 211 pthread_detach(threadId); 212 return false; 213 } 214 } 215 216 return true; 217 } 218 219 /* 220 * Tell the thread to stop as soon as possible. 221 */ 222 void signalThreadShouldExit() noexcept 223 { 224 fShouldExit = true; 225 } 226 227 // ------------------------------------------------------------------- 228 229 /* 230 * Returns the name of the thread. 231 * This is the name that gets set in the constructor. 232 */ 233 const String& getThreadName() const noexcept 234 { 235 return fName; 236 } 237 238 /* 239 * Returns the Id/handle of the thread. 240 */ 241 pthread_t getThreadId() const noexcept 242 { 243 return fHandle; 244 } 245 246 /* 247 * Changes the name of the caller thread. 248 */ 249 static void setCurrentThreadName(const char* const name) noexcept 250 { 251 DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0',); 252 253 #ifdef DISTRHO_OS_LINUX 254 prctl(PR_SET_NAME, name, 0, 0, 0); 255 #endif 256 #if defined(__GLIBC__) && (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 && !defined(DISTRHO_OS_GNU_HURD) 257 pthread_setname_np(pthread_self(), name); 258 #endif 259 } 260 261 // ------------------------------------------------------------------- 262 263 private: 264 Mutex fLock; // Thread lock 265 Signal fSignal; // Thread start wait signal 266 const String fName; // Thread name 267 volatile pthread_t fHandle; // Handle for this thread 268 volatile bool fShouldExit; // true if thread should exit 269 270 /* 271 * Init pthread type. 272 */ 273 void _init() noexcept 274 { 275 #ifdef PTW32_DLLPORT 276 fHandle.p = nullptr; 277 fHandle.x = 0; 278 #else 279 fHandle = 0; 280 #endif 281 } 282 283 /* 284 * Copy our pthread type from another var. 285 */ 286 void _copyFrom(const pthread_t& handle) noexcept 287 { 288 #ifdef PTW32_DLLPORT 289 fHandle.p = handle.p; 290 fHandle.x = handle.x; 291 #else 292 fHandle = handle; 293 #endif 294 } 295 296 /* 297 * Copy our pthread type to another var. 298 */ 299 void _copyTo(volatile pthread_t& handle) const noexcept 300 { 301 #ifdef PTW32_DLLPORT 302 handle.p = fHandle.p; 303 handle.x = fHandle.x; 304 #else 305 handle = fHandle; 306 #endif 307 } 308 309 /* 310 * Thread entry point. 311 */ 312 void _runEntryPoint() noexcept 313 { 314 if (fName.isNotEmpty()) 315 setCurrentThreadName(fName); 316 317 // report ready 318 fSignal.signal(); 319 320 try { 321 run(); 322 } catch(...) {} 323 324 // done 325 _init(); 326 } 327 328 /* 329 * Thread entry point. 330 */ 331 static void* _entryPoint(void* userData) noexcept 332 { 333 static_cast<Thread*>(userData)->_runEntryPoint(); 334 return nullptr; 335 } 336 337 DISTRHO_DECLARE_NON_COPYABLE(Thread) 338 }; 339 340 // ----------------------------------------------------------------------- 341 342 END_NAMESPACE_DISTRHO 343 344 #endif // DISTRHO_THREAD_HPP_INCLUDED