aboutsummaryrefslogtreecommitdiff
path: root/src/threading.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/threading.cpp448
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
14Copyright (C) 2007-10 Asko Kauppi <akauppi@gmail.com>
15Copyright (C) 2009-24, Benoit Germain <bnt.germain@gmail.com>
16
17Permission is hereby granted, free of charge, to any person obtaining a copy
18of this software and associated documentation files (the "Software"), to deal
19in the Software without restriction, including without limitation the rights
20to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21copies of the Software, and to permit persons to whom the Software is
22furnished to do so, subject to the following conditions:
23
24The above copyright notice and this permission notice shall be included in
25all copies or substantial portions of the Software.
26
27THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33THE 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
112static 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
125void 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
136void 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
147void 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)
161typedef 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
171void 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
223static 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
239static 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
359void 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
374void 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
389void 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
422void 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// #################################################################################################