diff options
Diffstat (limited to '')
-rw-r--r-- | src/threading.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/src/threading.cpp b/src/threading.cpp new file mode 100644 index 0000000..259693a --- /dev/null +++ b/src/threading.cpp | |||
@@ -0,0 +1,448 @@ | |||
1 | /* | ||
2 | * THREADING.CPP Copyright (c) 2007-08, Asko Kauppi | ||
3 | * Copyright (C) 2009-24, Benoit Germain | ||
4 | * | ||
5 | * Lua Lanes OS threading specific code. | ||
6 | * | ||
7 | * References: | ||
8 | * <http://www.cse.wustl.edu/~schmidt/win32-cv-1.html> | ||
9 | */ | ||
10 | |||
11 | /* | ||
12 | =============================================================================== | ||
13 | |||
14 | Copyright (C) 2007-10 Asko Kauppi <akauppi@gmail.com> | ||
15 | Copyright (C) 2009-24, Benoit Germain <bnt.germain@gmail.com> | ||
16 | |||
17 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
18 | of this software and associated documentation files (the "Software"), to deal | ||
19 | in the Software without restriction, including without limitation the rights | ||
20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
21 | copies of the Software, and to permit persons to whom the Software is | ||
22 | furnished to do so, subject to the following conditions: | ||
23 | |||
24 | The above copyright notice and this permission notice shall be included in | ||
25 | all copies or substantial portions of the Software. | ||
26 | |||
27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
33 | THE SOFTWARE. | ||
34 | |||
35 | =============================================================================== | ||
36 | */ | ||
37 | #if defined(__linux__) | ||
38 | |||
39 | # ifndef _GNU_SOURCE // definition by the makefile can cause a redefinition error | ||
40 | # define _GNU_SOURCE // must be defined before any include | ||
41 | # endif // _GNU_SOURCE | ||
42 | |||
43 | # ifdef __ANDROID__ | ||
44 | # include <android/log.h> | ||
45 | # define LOG_TAG "LuaLanes" | ||
46 | # endif // __ANDROID__ | ||
47 | |||
48 | #endif // __linux__ | ||
49 | |||
50 | #include "threading.h" | ||
51 | |||
52 | #if !defined( PLATFORM_XBOX) && !defined( PLATFORM_WIN32) && !defined( PLATFORM_POCKETPC) | ||
53 | # include <sys/time.h> | ||
54 | #endif // non-WIN32 timing | ||
55 | |||
56 | |||
57 | #if defined(PLATFORM_LINUX) || defined(PLATFORM_CYGWIN) | ||
58 | # include <sys/types.h> | ||
59 | # include <unistd.h> | ||
60 | #endif | ||
61 | |||
62 | #ifdef PLATFORM_OSX | ||
63 | # include "threading_osx.h" | ||
64 | #endif | ||
65 | |||
66 | /* Linux with older glibc (such as Debian) don't have pthread_setname_np, but have prctl | ||
67 | */ | ||
68 | #if defined PLATFORM_LINUX | ||
69 | #if defined __GNU_LIBRARY__ && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 12 | ||
70 | #define LINUX_USE_PTHREAD_SETNAME_NP 1 | ||
71 | #else // glibc without pthread_setname_np | ||
72 | #include <sys/prctl.h> | ||
73 | #define LINUX_USE_PTHREAD_SETNAME_NP 0 | ||
74 | #endif // glibc without pthread_setname_np | ||
75 | #endif // PLATFORM_LINUX | ||
76 | |||
77 | #ifdef _MSC_VER | ||
78 | // ".. selected for automatic inline expansion" (/O2 option) | ||
79 | # pragma warning( disable : 4711 ) | ||
80 | // ".. type cast from function pointer ... to data pointer" | ||
81 | # pragma warning( disable : 4054 ) | ||
82 | #endif | ||
83 | |||
84 | /* | ||
85 | * FAIL is for unexpected API return values - essentially programming | ||
86 | * error in _this_ code. | ||
87 | */ | ||
88 | #if defined(PLATFORM_XBOX) || defined(PLATFORM_WIN32) || defined(PLATFORM_POCKETPC) | ||
89 | static void FAIL(char const* funcname, int rc) | ||
90 | { | ||
91 | #if defined(PLATFORM_XBOX) | ||
92 | fprintf(stderr, "%s() failed! (%d)\n", funcname, rc); | ||
93 | #else // PLATFORM_XBOX | ||
94 | char buf[256]; | ||
95 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, nullptr); | ||
96 | fprintf(stderr, "%s() failed! [GetLastError() -> %d] '%s'", funcname, rc, buf); | ||
97 | #endif // PLATFORM_XBOX | ||
98 | #ifdef _MSC_VER | ||
99 | __debugbreak(); // give a chance to the debugger! | ||
100 | #endif // _MSC_VER | ||
101 | abort(); | ||
102 | } | ||
103 | #endif // win32 build | ||
104 | |||
105 | |||
106 | /*---=== Threading ===---*/ | ||
107 | |||
108 | // ################################################################################################## | ||
109 | // ################################################################################################## | ||
110 | #if THREADAPI == THREADAPI_WINDOWS | ||
111 | |||
112 | static int const gs_prio_remap[] = | ||
113 | { | ||
114 | THREAD_PRIORITY_IDLE, | ||
115 | THREAD_PRIORITY_LOWEST, | ||
116 | THREAD_PRIORITY_BELOW_NORMAL, | ||
117 | THREAD_PRIORITY_NORMAL, | ||
118 | THREAD_PRIORITY_ABOVE_NORMAL, | ||
119 | THREAD_PRIORITY_HIGHEST, | ||
120 | THREAD_PRIORITY_TIME_CRITICAL | ||
121 | }; | ||
122 | |||
123 | // ############################################################################################### | ||
124 | |||
125 | void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) | ||
126 | { | ||
127 | // prio range [-3,+3] was checked by the caller | ||
128 | if (!SetThreadPriority(GetCurrentThread(), gs_prio_remap[prio_ + 3])) | ||
129 | { | ||
130 | FAIL("THREAD_SET_PRIORITY", GetLastError()); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | // ############################################################################################### | ||
135 | |||
136 | void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_, [[maybe_unused]] bool sudo_) | ||
137 | { | ||
138 | // prio range [-3,+3] was checked by the caller | ||
139 | if (!SetThreadPriority(thread_.native_handle(), gs_prio_remap[prio_ + 3])) | ||
140 | { | ||
141 | FAIL("JTHREAD_SET_PRIORITY", GetLastError()); | ||
142 | } | ||
143 | } | ||
144 | |||
145 | // ############################################################################################### | ||
146 | |||
147 | void THREAD_SET_AFFINITY(unsigned int aff) | ||
148 | { | ||
149 | if (!SetThreadAffinityMask(GetCurrentThread(), aff)) | ||
150 | { | ||
151 | FAIL("THREAD_SET_AFFINITY", GetLastError()); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | // ############################################################################################### | ||
156 | |||
157 | #if !defined __GNUC__ | ||
158 | //see http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx | ||
159 | #define MS_VC_EXCEPTION 0x406D1388 | ||
160 | #pragma pack(push,8) | ||
161 | typedef struct tagTHREADNAME_INFO | ||
162 | { | ||
163 | DWORD dwType; // Must be 0x1000. | ||
164 | LPCSTR szName; // Pointer to name (in user addr space). | ||
165 | DWORD dwThreadID; // Thread ID (-1=caller thread). | ||
166 | DWORD dwFlags; // Reserved for future use, must be zero. | ||
167 | } THREADNAME_INFO; | ||
168 | #pragma pack(pop) | ||
169 | #endif // !__GNUC__ | ||
170 | |||
171 | void THREAD_SETNAME(char const* _name) | ||
172 | { | ||
173 | #if !defined __GNUC__ | ||
174 | THREADNAME_INFO info; | ||
175 | info.dwType = 0x1000; | ||
176 | info.szName = _name; | ||
177 | info.dwThreadID = GetCurrentThreadId(); | ||
178 | info.dwFlags = 0; | ||
179 | |||
180 | __try | ||
181 | { | ||
182 | RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); | ||
183 | } | ||
184 | __except(EXCEPTION_EXECUTE_HANDLER) | ||
185 | { | ||
186 | } | ||
187 | #endif // !__GNUC__ | ||
188 | } | ||
189 | |||
190 | // ################################################################################################## | ||
191 | // ################################################################################################## | ||
192 | #else // THREADAPI == THREADAPI_PTHREAD | ||
193 | // ################################################################################################## | ||
194 | // ################################################################################################## | ||
195 | |||
196 | // PThread (Linux, OS X, ...) | ||
197 | // | ||
198 | // On OS X, user processes seem to be able to change priorities. | ||
199 | // On Linux, SCHED_RR and su privileges are required.. !-( | ||
200 | // | ||
201 | #include <errno.h> | ||
202 | #include <sched.h> | ||
203 | |||
204 | #if (defined(__MINGW32__) || defined(__MINGW64__)) && defined pthread_attr_setschedpolicy | ||
205 | #if pthread_attr_setschedpolicy(A, S) == ENOTSUP | ||
206 | // from the mingw-w64 team: | ||
207 | // Well, we support pthread_setschedparam by which you can specify | ||
208 | // threading-policy. Nevertheless, yes we lack this function. In | ||
209 | // general its implementation is pretty much trivial, as on Win32 target | ||
210 | // just SCHED_OTHER can be supported. | ||
211 | #undef pthread_attr_setschedpolicy | ||
212 | [[nodiscard]] static int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy) | ||
213 | { | ||
214 | if (policy != SCHED_OTHER) | ||
215 | { | ||
216 | return ENOTSUP; | ||
217 | } | ||
218 | return 0; | ||
219 | } | ||
220 | #endif // pthread_attr_setschedpolicy() | ||
221 | #endif // defined(__MINGW32__) || defined(__MINGW64__) | ||
222 | |||
223 | static void _PT_FAIL( int rc, const char *name, const char *file, int line ) | ||
224 | { | ||
225 | const char *why= (rc==EINVAL) ? "EINVAL" : | ||
226 | (rc==EBUSY) ? "EBUSY" : | ||
227 | (rc==EPERM) ? "EPERM" : | ||
228 | (rc==ENOMEM) ? "ENOMEM" : | ||
229 | (rc==ESRCH) ? "ESRCH" : | ||
230 | (rc==ENOTSUP) ? "ENOTSUP": | ||
231 | //... | ||
232 | "<UNKNOWN>"; | ||
233 | fprintf( stderr, "%s %d: %s failed, %d %s\n", file, line, name, rc, why ); | ||
234 | abort(); | ||
235 | } | ||
236 | #define PT_CALL( call ) { int rc= call; if (rc!=0) _PT_FAIL( rc, #call, __FILE__, __LINE__ ); } | ||
237 | |||
238 | // array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range | ||
239 | static int const gs_prio_remap[] = | ||
240 | { | ||
241 | // NB: PThreads priority handling is about as twisty as one can get it | ||
242 | // (and then some). DON*T TRUST ANYTHING YOU READ ON THE NET!!! | ||
243 | |||
244 | //--- | ||
245 | // "Select the scheduling policy for the thread: one of SCHED_OTHER | ||
246 | // (regular, non-real-time scheduling), SCHED_RR (real-time, | ||
247 | // round-robin) or SCHED_FIFO (real-time, first-in first-out)." | ||
248 | // | ||
249 | // "Using the RR policy ensures that all threads having the same | ||
250 | // priority level will be scheduled equally, regardless of their activity." | ||
251 | // | ||
252 | // "For SCHED_FIFO and SCHED_RR, the only required member of the | ||
253 | // sched_param structure is the priority sched_priority. For SCHED_OTHER, | ||
254 | // the affected scheduling parameters are implementation-defined." | ||
255 | // | ||
256 | // "The priority of a thread is specified as a delta which is added to | ||
257 | // the priority of the process." | ||
258 | // | ||
259 | // ".. priority is an integer value, in the range from 1 to 127. | ||
260 | // 1 is the least-favored priority, 127 is the most-favored." | ||
261 | // | ||
262 | // "Priority level 0 cannot be used: it is reserved for the system." | ||
263 | // | ||
264 | // "When you use specify a priority of -99 in a call to | ||
265 | // pthread_setschedparam(), the priority of the target thread is | ||
266 | // lowered to the lowest possible value." | ||
267 | // | ||
268 | // ... | ||
269 | |||
270 | // ** CONCLUSION ** | ||
271 | // | ||
272 | // PThread priorities are _hugely_ system specific, and we need at | ||
273 | // least OS specific settings. Hopefully, Linuxes and OS X versions | ||
274 | // are uniform enough, among each other... | ||
275 | // | ||
276 | # if defined PLATFORM_OSX | ||
277 | // AK 10-Apr-07 (OS X PowerPC 10.4.9): | ||
278 | // | ||
279 | // With SCHED_RR, 26 seems to be the "normal" priority, where setting | ||
280 | // it does not seem to affect the order of threads processed. | ||
281 | // | ||
282 | // With SCHED_OTHER, the range 25..32 is normal (maybe the same 26, | ||
283 | // but the difference is not so clear with OTHER). | ||
284 | // | ||
285 | // 'sched_get_priority_min()' and '..max()' give 15, 47 as the | ||
286 | // priority limits. This could imply, user mode applications won't | ||
287 | // be able to use values outside of that range. | ||
288 | // | ||
289 | # define _PRIO_MODE SCHED_OTHER | ||
290 | |||
291 | // OS X 10.4.9 (PowerPC) gives ENOTSUP for process scope | ||
292 | //#define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS | ||
293 | |||
294 | # define _PRIO_HI 32 // seems to work (_carefully_ picked!) | ||
295 | # define _PRIO_0 26 // detected | ||
296 | # define _PRIO_LO 1 // seems to work (tested) | ||
297 | |||
298 | # elif defined PLATFORM_LINUX | ||
299 | // (based on Ubuntu Linux 2.6.15 kernel) | ||
300 | // | ||
301 | // SCHED_OTHER is the default policy, but does not allow for priorities. | ||
302 | // SCHED_RR allows priorities, all of which (1..99) are higher than | ||
303 | // a thread with SCHED_OTHER policy. | ||
304 | // | ||
305 | // <http://kerneltrap.org/node/6080> | ||
306 | // <http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library> | ||
307 | // <http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html> | ||
308 | // | ||
309 | // Manuals suggest checking #ifdef _POSIX_THREAD_PRIORITY_SCHEDULING, | ||
310 | // but even Ubuntu does not seem to define it. | ||
311 | // | ||
312 | # define _PRIO_MODE SCHED_RR | ||
313 | |||
314 | // NTLP 2.5: only system scope allowed (being the basic reason why | ||
315 | // root privileges are required..) | ||
316 | //#define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS | ||
317 | |||
318 | # define _PRIO_HI 99 | ||
319 | # define _PRIO_0 50 | ||
320 | # define _PRIO_LO 1 | ||
321 | |||
322 | # elif defined(PLATFORM_BSD) | ||
323 | // | ||
324 | // <http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html> | ||
325 | // | ||
326 | // "When control over the thread scheduling is desired, then FreeBSD | ||
327 | // with the libpthread implementation is by far the best choice .." | ||
328 | // | ||
329 | # define _PRIO_MODE SCHED_OTHER | ||
330 | # define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS | ||
331 | # define _PRIO_HI 31 | ||
332 | # define _PRIO_0 15 | ||
333 | # define _PRIO_LO 1 | ||
334 | |||
335 | # elif defined(PLATFORM_CYGWIN) | ||
336 | // | ||
337 | // TBD: Find right values for Cygwin | ||
338 | // | ||
339 | # else | ||
340 | # error "Unknown OS: not implemented!" | ||
341 | # endif | ||
342 | |||
343 | #if defined _PRIO_0 | ||
344 | # define _PRIO_AN (_PRIO_0 + ((_PRIO_HI-_PRIO_0)/2)) | ||
345 | # define _PRIO_BN (_PRIO_LO + ((_PRIO_0-_PRIO_LO)/2)) | ||
346 | |||
347 | _PRIO_LO, _PRIO_LO, _PRIO_BN, _PRIO_0, _PRIO_AN, _PRIO_HI, _PRIO_HI | ||
348 | #endif // _PRIO_0 | ||
349 | }; | ||
350 | |||
351 | [[nodiscard]] static int select_prio(int prio /* -3..+3 */) | ||
352 | { | ||
353 | if (prio == THREAD_PRIO_DEFAULT) | ||
354 | prio = 0; | ||
355 | // prio range [-3,+3] was checked by the caller | ||
356 | return gs_prio_remap[prio + 3]; | ||
357 | } | ||
358 | |||
359 | void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) | ||
360 | { | ||
361 | #ifdef PLATFORM_LINUX | ||
362 | if (!sudo_) // only root-privileged process can change priorities | ||
363 | return; | ||
364 | #endif // PLATFORM_LINUX | ||
365 | |||
366 | struct sched_param sp; | ||
367 | // prio range [-3,+3] was checked by the caller | ||
368 | sp.sched_priority = gs_prio_remap[prio_ + 3]; | ||
369 | PT_CALL(pthread_setschedparam(pthread_self(), _PRIO_MODE, &sp)); | ||
370 | } | ||
371 | |||
372 | // ################################################################################################# | ||
373 | |||
374 | void JTHREAD_SET_PRIORITY(std::jthread& thread_, int prio_, [[maybe_unused]] bool sudo_) | ||
375 | { | ||
376 | #ifdef PLATFORM_LINUX | ||
377 | if (!sudo_) // only root-privileged process can change priorities | ||
378 | return; | ||
379 | #endif // PLATFORM_LINUX | ||
380 | |||
381 | struct sched_param sp; | ||
382 | // prio range [-3,+3] was checked by the caller | ||
383 | sp.sched_priority = gs_prio_remap[prio_ + 3]; | ||
384 | PT_CALL(pthread_setschedparam(static_cast<pthread_t>(thread_.native_handle()), _PRIO_MODE, &sp)); | ||
385 | } | ||
386 | |||
387 | // ################################################################################################# | ||
388 | |||
389 | void THREAD_SET_AFFINITY(unsigned int aff) | ||
390 | { | ||
391 | int bit = 0; | ||
392 | #ifdef __NetBSD__ | ||
393 | cpuset_t* cpuset = cpuset_create(); | ||
394 | if (cpuset == nullptr) | ||
395 | _PT_FAIL(errno, "cpuset_create", __FILE__, __LINE__ - 2); | ||
396 | #define CPU_SET(b, s) cpuset_set(b, *(s)) | ||
397 | #else | ||
398 | cpu_set_t cpuset; | ||
399 | CPU_ZERO(&cpuset); | ||
400 | #endif | ||
401 | while (aff != 0) | ||
402 | { | ||
403 | if (aff & 1) | ||
404 | { | ||
405 | CPU_SET(bit, &cpuset); | ||
406 | } | ||
407 | ++bit; | ||
408 | aff >>= 1; | ||
409 | } | ||
410 | #ifdef __ANDROID__ | ||
411 | PT_CALL(sched_setaffinity(pthread_self(), sizeof(cpu_set_t), &cpuset)); | ||
412 | #elif defined(__NetBSD__) | ||
413 | PT_CALL(pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset)); | ||
414 | cpuset_destroy(cpuset); | ||
415 | #else | ||
416 | PT_CALL(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset)); | ||
417 | #endif | ||
418 | } | ||
419 | |||
420 | // ################################################################################################# | ||
421 | |||
422 | void THREAD_SETNAME(char const* _name) | ||
423 | { | ||
424 | // exact API to set the thread name is platform-dependant | ||
425 | // if you need to fix the build, or if you know how to fill a hole, tell me (bnt.germain@gmail.com) so that I can submit the fix in github. | ||
426 | #if defined PLATFORM_BSD && !defined __NetBSD__ | ||
427 | pthread_set_name_np(pthread_self(), _name); | ||
428 | #elif defined PLATFORM_BSD && defined __NetBSD__ | ||
429 | pthread_setname_np(pthread_self(), "%s", (void*) _name); | ||
430 | #elif defined PLATFORM_LINUX | ||
431 | #if LINUX_USE_PTHREAD_SETNAME_NP | ||
432 | pthread_setname_np(pthread_self(), _name); | ||
433 | #else // LINUX_USE_PTHREAD_SETNAME_NP | ||
434 | prctl(PR_SET_NAME, _name, 0, 0, 0); | ||
435 | #endif // LINUX_USE_PTHREAD_SETNAME_NP | ||
436 | #elif defined PLATFORM_QNX || defined PLATFORM_CYGWIN | ||
437 | pthread_setname_np(pthread_self(), _name); | ||
438 | #elif defined PLATFORM_OSX | ||
439 | pthread_setname_np(_name); | ||
440 | #else | ||
441 | fprintf(stderr, "THREAD_SETNAME: unsupported platform\n"); | ||
442 | abort(); | ||
443 | #endif | ||
444 | } | ||
445 | |||
446 | #endif // THREADAPI == THREADAPI_PTHREAD | ||
447 | // ################################################################################################# | ||
448 | // ################################################################################################# | ||