diff options
| author | Benoit Germain <bnt.germain@gmail.com> | 2025-05-07 15:43:01 +0200 |
|---|---|---|
| committer | Benoit Germain <bnt.germain@gmail.com> | 2025-05-07 15:43:01 +0200 |
| commit | 074a7157b6bd3867b60d04f685cdede6063e6e3c (patch) | |
| tree | 21f58c5c739fadaaa57b214e468524efbbe26cbb /src/threading.cpp | |
| parent | d0dd3b644b36bac119aa9e9da40c3cfe38a6e234 (diff) | |
| download | lanes-074a7157b6bd3867b60d04f685cdede6063e6e3c.tar.gz lanes-074a7157b6bd3867b60d04f685cdede6063e6e3c.tar.bz2 lanes-074a7157b6bd3867b60d04f685cdede6063e6e3c.zip | |
Thread priority rework
* thread priorities can now be set using the native range of values, if desired.
* thread API errors cause a Lua error instead of aborting the program.
* new function lanes.thread_priority_range(), to query the valid range of priorities.
* unit tests for all of the above
Diffstat (limited to 'src/threading.cpp')
| -rw-r--r-- | src/threading.cpp | 146 |
1 files changed, 88 insertions, 58 deletions
diff --git a/src/threading.cpp b/src/threading.cpp index 483a228..efca7eb 100644 --- a/src/threading.cpp +++ b/src/threading.cpp | |||
| @@ -49,6 +49,7 @@ THE SOFTWARE. | |||
| 49 | 49 | ||
| 50 | #endif // __linux__ | 50 | #endif // __linux__ |
| 51 | 51 | ||
| 52 | #include "compat.hpp" | ||
| 52 | #include "threading.hpp" | 53 | #include "threading.hpp" |
| 53 | 54 | ||
| 54 | #if !defined(PLATFORM_XBOX) && !defined(PLATFORM_WIN32) && !defined(PLATFORM_POCKETPC) | 55 | #if !defined(PLATFORM_XBOX) && !defined(PLATFORM_WIN32) && !defined(PLATFORM_POCKETPC) |
| @@ -82,25 +83,42 @@ THE SOFTWARE. | |||
| 82 | #pragma warning(disable : 4054) | 83 | #pragma warning(disable : 4054) |
| 83 | #endif | 84 | #endif |
| 84 | 85 | ||
| 86 | static constexpr std::string_view StripFuncName(std::string_view const& where_) | ||
| 87 | { | ||
| 88 | std::string_view funcname_{ where_ }; | ||
| 89 | |||
| 90 | auto _args_pos{ funcname_.find_first_of('(') }; | ||
| 91 | funcname_ = funcname_.substr(0, _args_pos); | ||
| 92 | auto _name_pos{ funcname_.find_last_of(' ') }; | ||
| 93 | funcname_.remove_prefix(_name_pos + 1); | ||
| 94 | return funcname_; | ||
| 95 | } | ||
| 96 | |||
| 85 | /* | 97 | /* |
| 86 | * FAIL is for unexpected API return values - essentially programming | 98 | * FAIL is for unexpected API return values - essentially programming |
| 87 | * error in _this_ code. | 99 | * error in _this_ code. |
| 88 | */ | 100 | */ |
| 89 | #if HAVE_WIN32 | 101 | #if HAVE_WIN32 |
| 90 | static void FAIL(char const* funcname_, DWORD const rc_) | 102 | |
| 103 | template <typename F, typename... ARGS> | ||
| 104 | void Win32Invoke(lua_State* const L_, std::string_view const& where_, F& f_, ARGS... args_) | ||
| 91 | { | 105 | { |
| 106 | auto const _ret{ std::invoke(f_, std::forward<ARGS>(args_)...) }; | ||
| 107 | if (!_ret) { | ||
| 108 | auto const _rc{ GetLastError() }; | ||
| 109 | std::string_view const _funcname{ StripFuncName(where_) }; | ||
| 110 | |||
| 92 | #if defined(PLATFORM_XBOX) | 111 | #if defined(PLATFORM_XBOX) |
| 93 | fprintf(stderr, "%s() failed! (%d)\n", funcname_, rc_); | 112 | luaG_pushstring(L_, "%s() failed with code %d", _funcname.data(), _rc); |
| 94 | #else // PLATFORM_XBOX | 113 | #else // PLATFORM_XBOX |
| 95 | char buf[256]; | 114 | char _buf[256]; |
| 96 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, rc_, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, nullptr); | 115 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, _rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), _buf, 256, nullptr); |
| 97 | fprintf(stderr, "%s() failed! [GetLastError() -> %lu] '%s'", funcname_, rc_, buf); | 116 | luaG_pushstring(L_, "%s() failed with code %d '%s'", _funcname.data(), _rc, _buf); |
| 98 | #endif // PLATFORM_XBOX | 117 | #endif // PLATFORM_XBOX |
| 99 | #ifdef _MSC_VER | 118 | raise_lua_error(L_); |
| 100 | __debugbreak(); // give a chance to the debugger! | 119 | } |
| 101 | #endif // _MSC_VER | ||
| 102 | abort(); | ||
| 103 | } | 120 | } |
| 121 | |||
| 104 | #endif // HAVE_WIN32 | 122 | #endif // HAVE_WIN32 |
| 105 | 123 | ||
| 106 | /*---=== Threading ===---*/ | 124 | /*---=== Threading ===---*/ |
| @@ -121,33 +139,35 @@ static int const gs_prio_remap[] = { | |||
| 121 | 139 | ||
| 122 | // ################################################################################################# | 140 | // ################################################################################################# |
| 123 | 141 | ||
| 124 | void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) | 142 | std::pair<int, int> THREAD_NATIVE_PRIOS() |
| 125 | { | 143 | { |
| 126 | // prio range [-3,+3] was checked by the caller | 144 | return std::make_pair(THREAD_PRIORITY_IDLE, THREAD_PRIORITY_TIME_CRITICAL); |
| 127 | if (!SetThreadPriority(GetCurrentThread(), gs_prio_remap[prio_ + 3])) { | ||
| 128 | FAIL("THREAD_SET_PRIORITY", GetLastError()); | ||
| 129 | } | ||
| 130 | } | 145 | } |
| 131 | 146 | ||
| 132 | // ################################################################################################# | 147 | // ################################################################################################# |
| 133 | 148 | ||
| 134 | void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, [[maybe_unused]] bool sudo_) | 149 | [[nodiscard]] |
| 150 | void THREAD_SET_PRIORITY(lua_State* const L_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_) | ||
| 135 | { | 151 | { |
| 136 | // prio range [-3,+3] was checked by the caller | 152 | // mapped prio range [-3,+3] was checked by the caller |
| 137 | // for some reason when building for mingw, native_handle() is an unsigned long long, but HANDLE is a void* | 153 | return Win32Invoke(L_, std::source_location::current().function_name(), SetThreadPriority, GetCurrentThread(), native_ ? prio_ : gs_prio_remap[prio_ + 3]); |
| 138 | // -> need a strong cast to make g++ happy | ||
| 139 | if (!SetThreadPriority(thread_.native_handle(), gs_prio_remap[prio_ + 3])) { | ||
| 140 | FAIL("THREAD_SET_PRIORITY", GetLastError()); | ||
| 141 | } | ||
| 142 | } | 154 | } |
| 143 | 155 | ||
| 144 | // ################################################################################################# | 156 | // ################################################################################################# |
| 145 | 157 | ||
| 146 | void THREAD_SET_AFFINITY(unsigned int aff_) | 158 | [[nodiscard]] |
| 159 | void THREAD_SET_PRIORITY(lua_State* const L_, std::thread& thread_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_) | ||
| 147 | { | 160 | { |
| 148 | if (!SetThreadAffinityMask(GetCurrentThread(), aff_)) { | 161 | // mapped prio range [-3,+3] was checked by the caller |
| 149 | FAIL("THREAD_SET_AFFINITY", GetLastError()); | 162 | return Win32Invoke(L_, std::source_location::current().function_name(), SetThreadPriority, thread_.native_handle(), native_ ? prio_ : gs_prio_remap[prio_ + 3]); |
| 150 | } | 163 | } |
| 164 | |||
| 165 | // ################################################################################################# | ||
| 166 | |||
| 167 | [[nodiscard]] | ||
| 168 | void THREAD_SET_AFFINITY(lua_State* const L_, unsigned int aff_) | ||
| 169 | { | ||
| 170 | return Win32Invoke(L_, std::source_location::current().function_name(), SetThreadAffinityMask, GetCurrentThread(), aff_); | ||
| 151 | } | 171 | } |
| 152 | 172 | ||
| 153 | // ################################################################################################# | 173 | // ################################################################################################# |
| @@ -215,24 +235,24 @@ static int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy) | |||
| 215 | #endif // pthread_attr_setschedpolicy() | 235 | #endif // pthread_attr_setschedpolicy() |
| 216 | #endif // defined(__MINGW32__) || defined(__MINGW64__) | 236 | #endif // defined(__MINGW32__) || defined(__MINGW64__) |
| 217 | 237 | ||
| 218 | static void _PT_FAIL(int rc, const char* name, const char* file, int line) | 238 | template <typename F, typename... ARGS> |
| 239 | void PthreadInvoke(lua_State* const L_, std::string_view const& where_, F& f_, ARGS... args_) | ||
| 219 | { | 240 | { |
| 220 | const char* why = (rc == EINVAL) ? "EINVAL" | 241 | auto const _rc{ std::invoke(f_, std::forward<ARGS>(args_)...) }; |
| 221 | : (rc == EBUSY) ? "EBUSY" | 242 | if (_rc) { |
| 222 | : (rc == EPERM) ? "EPERM" | 243 | std::string_view const _funcname{ StripFuncName(where_) }; |
| 223 | : (rc == ENOMEM) ? "ENOMEM" | 244 | |
| 224 | : (rc == ESRCH) ? "ESRCH" | 245 | char const* _why = (_rc == EINVAL) ? "EINVAL" |
| 225 | : (rc == ENOTSUP) ? "ENOTSUP" | 246 | : (_rc == EBUSY) ? "EBUSY" |
| 226 | : "<UNKNOWN>"; | 247 | : (_rc == EPERM) ? "EPERM" |
| 227 | fprintf(stderr, "%s %d: %s failed, %d %s\n", file, line, name, rc, why); | 248 | : (_rc == ENOMEM) ? "ENOMEM" |
| 228 | abort(); | 249 | : (_rc == ESRCH) ? "ESRCH" |
| 229 | } | 250 | : (_rc == ENOTSUP) ? "ENOTSUP" |
| 230 | #define PT_CALL(call) \ | 251 | : "<UNKNOWN>"; |
| 231 | { \ | 252 | |
| 232 | int rc = call; \ | 253 | raise_luaL_error(L_, "%s() failed with code %s", _funcname.data(), _why); |
| 233 | if (rc != 0) \ | ||
| 234 | _PT_FAIL(rc, #call, __FILE__, __LINE__); \ | ||
| 235 | } | 254 | } |
| 255 | } | ||
| 236 | 256 | ||
| 237 | // array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range | 257 | // array of 7 thread priority values, hand-tuned by platform so that we offer a uniform [-3,+3] public priority range |
| 238 | static int const gs_prio_remap[] = { | 258 | static int const gs_prio_remap[] = { |
| @@ -357,7 +377,18 @@ static int const gs_prio_remap[] = { | |||
| 357 | #endif // _PRIO_0 | 377 | #endif // _PRIO_0 |
| 358 | }; | 378 | }; |
| 359 | 379 | ||
| 360 | void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) | 380 | // ################################################################################################# |
| 381 | |||
| 382 | std::pair<int, int> THREAD_NATIVE_PRIOS() | ||
| 383 | { | ||
| 384 | int const _prio_min{ sched_get_priority_min(_PRIO_MODE) }; | ||
| 385 | int const _prio_max{ sched_get_priority_max(_PRIO_MODE) }; | ||
| 386 | return std::make_pair(_prio_min, _prio_max); | ||
| 387 | } | ||
| 388 | |||
| 389 | // ################################################################################################# | ||
| 390 | |||
| 391 | void THREAD_SET_PRIORITY(lua_State* const L_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_) | ||
| 361 | { | 392 | { |
| 362 | #ifdef PLATFORM_LINUX | 393 | #ifdef PLATFORM_LINUX |
| 363 | if (!sudo_) // only root-privileged process can change priorities | 394 | if (!sudo_) // only root-privileged process can change priorities |
| @@ -366,13 +397,13 @@ void THREAD_SET_PRIORITY(int prio_, [[maybe_unused]] bool sudo_) | |||
| 366 | 397 | ||
| 367 | struct sched_param sp; | 398 | struct sched_param sp; |
| 368 | // prio range [-3,+3] was checked by the caller | 399 | // prio range [-3,+3] was checked by the caller |
| 369 | sp.sched_priority = gs_prio_remap[prio_ + 3]; | 400 | sp.sched_priority = native_ ? prio_ : gs_prio_remap[prio_ + 3]; |
| 370 | PT_CALL(pthread_setschedparam(pthread_self(), _PRIO_MODE, &sp)); | 401 | PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setschedparam, pthread_self(), _PRIO_MODE, &sp); |
| 371 | } | 402 | } |
| 372 | 403 | ||
| 373 | // ################################################################################################# | 404 | // ################################################################################################# |
| 374 | 405 | ||
| 375 | void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, [[maybe_unused]] bool sudo_) | 406 | void THREAD_SET_PRIORITY(lua_State* const L_, std::thread& thread_, int const prio_, NativePrioFlag const native_, [[maybe_unused]] SudoFlag const sudo_) |
| 376 | { | 407 | { |
| 377 | #ifdef PLATFORM_LINUX | 408 | #ifdef PLATFORM_LINUX |
| 378 | if (!sudo_) // only root-privileged process can change priorities | 409 | if (!sudo_) // only root-privileged process can change priorities |
| @@ -381,28 +412,26 @@ void THREAD_SET_PRIORITY(std::thread& thread_, int prio_, [[maybe_unused]] bool | |||
| 381 | 412 | ||
| 382 | struct sched_param sp; | 413 | struct sched_param sp; |
| 383 | // prio range [-3,+3] was checked by the caller | 414 | // prio range [-3,+3] was checked by the caller |
| 384 | sp.sched_priority = gs_prio_remap[prio_ + 3]; | 415 | sp.sched_priority = native_ ? prio_ : gs_prio_remap[prio_ + 3]; |
| 385 | PT_CALL(pthread_setschedparam(thread_.native_handle(), _PRIO_MODE, &sp)); | 416 | PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setschedparam, thread_.native_handle(), _PRIO_MODE, &sp); |
| 386 | } | 417 | } |
| 387 | 418 | ||
| 388 | // ################################################################################################# | 419 | // ################################################################################################# |
| 389 | 420 | ||
| 390 | #ifdef __PROSPERO__ | 421 | #ifdef __PROSPERO__ |
| 391 | 422 | ||
| 392 | void THREAD_SET_AFFINITY(unsigned int aff_) | 423 | void THREAD_SET_AFFINITY(lua_State* const L_, unsigned int aff_) |
| 393 | { | 424 | { |
| 394 | scePthreadSetaffinity(scePthreadSelf(), aff_); | 425 | PthreadInvoke(L_, std::source_location::current().function_name(), scePthreadSetaffinity, scePthreadSelf(), aff_); |
| 395 | } | 426 | } |
| 396 | 427 | ||
| 397 | #else // __PROSPERO__ | 428 | #else // __PROSPERO__ |
| 398 | 429 | ||
| 399 | void THREAD_SET_AFFINITY(unsigned int aff_) | 430 | void THREAD_SET_AFFINITY(lua_State* const L_, unsigned int aff_) |
| 400 | { | 431 | { |
| 401 | #if HAVE_WIN32 // "hybrid": Win32 API is available, and pthread too | 432 | #if HAVE_WIN32 // "hybrid": Win32 API is available, and pthread too |
| 402 | // since pthread_setaffinity_np can be missing (for example mingw), use win32 api instead | 433 | // since pthread_setaffinity_np can be missing (for example mingw), use win32 api instead |
| 403 | if (!SetThreadAffinityMask(GetCurrentThread(), aff_)) { | 434 | Win32Invoke(L_, std::source_location::current().function_name(), SetThreadAffinityMask, GetCurrentThread(), aff_); |
| 404 | FAIL("THREAD_SET_AFFINITY", GetLastError()); | ||
| 405 | } | ||
| 406 | #else // pure pthread | 435 | #else // pure pthread |
| 407 | int bit = 0; | 436 | int bit = 0; |
| 408 | #ifdef __NetBSD__ | 437 | #ifdef __NetBSD__ |
| @@ -422,12 +451,13 @@ void THREAD_SET_AFFINITY(unsigned int aff_) | |||
| 422 | aff_ >>= 1; | 451 | aff_ >>= 1; |
| 423 | } | 452 | } |
| 424 | #ifdef __ANDROID__ | 453 | #ifdef __ANDROID__ |
| 425 | PT_CALL(sched_setaffinity(pthread_self(), sizeof(cpu_set_t), &cpuset)); | 454 | |
| 455 | PthreadInvoke(L_, std::source_location::current().function_name(), sched_setaffinity, pthread_self(), sizeof(cpu_set_t), &cpuset); | ||
| 426 | #elif defined(__NetBSD__) | 456 | #elif defined(__NetBSD__) |
| 427 | PT_CALL(pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), cpuset)); | 457 | PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setaffinity_np, pthread_self(), cpuset_size(cpuset), cpuset); |
| 428 | cpuset_destroy(cpuset); | 458 | cpuset_destroy(cpuset); |
| 429 | #else | 459 | #else |
| 430 | PT_CALL(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset)); | 460 | PthreadInvoke(L_, std::source_location::current().function_name(), pthread_setaffinity_np, pthread_self(), sizeof(cpu_set_t), &cpuset); |
| 431 | #endif | 461 | #endif |
| 432 | #endif // PLATFORM_MINGW | 462 | #endif // PLATFORM_MINGW |
| 433 | } | 463 | } |
| @@ -447,7 +477,7 @@ void THREAD_SETNAME(std::string_view const& name_) | |||
| 447 | 477 | ||
| 448 | void THREAD_SETNAME(std::string_view const& name_) | 478 | void THREAD_SETNAME(std::string_view const& name_) |
| 449 | { | 479 | { |
| 450 | // exact API to set the thread name is platform-dependant | 480 | // exact API to set the thread name is platform-dependent |
| 451 | // 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. | 481 | // 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. |
| 452 | #if defined PLATFORM_MINGW | 482 | #if defined PLATFORM_MINGW |
| 453 | pthread_setname_np(pthread_self(), name_.data()); | 483 | pthread_setname_np(pthread_self(), name_.data()); |
