From 4c5fb694d12f61f409b753adcf6a0d4b6c027e6c Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:35:21 +0200 Subject: [PATCH 01/10] Fix collision of QOS_CLASS_MAINTENANCE and DISPATCH_QOS_USER_INITIATED --- src/init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/init.c b/src/init.c index d54da4121..d4dc842cf 100644 --- a/src/init.c +++ b/src/init.c @@ -383,9 +383,9 @@ dispatch_get_global_queue(intptr_t priority, uintptr_t flags) } dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority); #if !HAVE_PTHREAD_WORKQUEUE_QOS - if (qos == QOS_CLASS_MAINTENANCE) { + if (qos == DISPATCH_QOS_MAINTENANCE) { qos = DISPATCH_QOS_BACKGROUND; - } else if (qos == QOS_CLASS_USER_INTERACTIVE) { + } else if (qos == DISPATCH_QOS_USER_INTERACTIVE) { qos = DISPATCH_QOS_USER_INITIATED; } #endif From ca24a17f7dd63edda950a030e04be5e38b5d0b2a Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:35:32 +0200 Subject: [PATCH 02/10] Check for pthread_setname_np --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca03e573a..bbc99f381 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,7 @@ check_function_exists(pthread_attr_setcpupercent_np HAVE_PTHREAD_ATTR_SETCPUPERC check_function_exists(pthread_yield_np HAVE_PTHREAD_YIELD_NP) check_function_exists(pthread_main_np HAVE_PTHREAD_MAIN_NP) check_function_exists(pthread_workqueue_setdispatch_np HAVE_PTHREAD_WORKQUEUE_SETDISPATCH_NP) +check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP) check_function_exists(strlcpy HAVE_STRLCPY) check_function_exists(sysconf HAVE_SYSCONF) check_function_exists(arc4random HAVE_ARC4RANDOM) From aabf51dc6e3b89e0ce8597d9d6a94984532d0e01 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:35:49 +0200 Subject: [PATCH 03/10] Add HAVE_PTHREAD_SETNAME_NP to config.h.in --- cmake/config.h.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/config.h.in b/cmake/config.h.in index 2896a2083..a5a854260 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -208,6 +208,9 @@ /* Define to 1 if you have the `_pthread_workqueue_init' function. */ #cmakedefine HAVE__PTHREAD_WORKQUEUE_INIT +/* Define to 1 if you have the `pthread_setname_np' function. */ +#cmakedefine01 HAVE_PTHREAD_SETNAME_NP + /* Define to use non-portable pthread TSD optimizations for Mac OS X) */ #cmakedefine USE_APPLE_TSD_OPTIMIZATIONS From 9208a63633f214f28a94c8b813aefb102f451e74 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:37:38 +0200 Subject: [PATCH 04/10] Implement _dispatch_pp_to_nice --- src/shims/priority.h | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/shims/priority.h b/src/shims/priority.h index 3a79c5efb..e4e51283c 100644 --- a/src/shims/priority.h +++ b/src/shims/priority.h @@ -210,6 +210,46 @@ _dispatch_qos_to_pp(dispatch_qos_t qos) return pp | _PTHREAD_PRIORITY_PRIORITY_MASK; } + +#if defined(__linux__) +// These presets roughly match the `android.os.Process' constants +// used for `setThreadPriority()'. +// +// Be aware that with the Completely Fair Scheduler (CFS) the weight is computed +// as 1024 / (1.25) ^ (nice) where nice is in the range -20 to 19. +// This means that nice is not a linear scale. +#define DISPATCH_NICE_BACKGROUND 10 +#define DISPATCH_NICE_UTILITY 2 +#define DISPATCH_NICE_DEFAULT 0 +// Note that you might not have permission to increase the priority +// of a thread beyond the default priority. +#define DISPATCH_NICE_USER_INITIATED -2 +#define DISPATCH_NICE_USER_INTERACTIVE -4 + +DISPATCH_ALWAYS_INLINE +static inline int _dispatch_pp_to_nice(pthread_priority_t pp) +{ + // FIXME: What about relative priorities? + uint32_t qos = _dispatch_qos_from_pp(pp); + + switch (qos) { + case DISPATCH_QOS_BACKGROUND: + return DISPATCH_NICE_BACKGROUND; + case DISPATCH_QOS_UTILITY: + return DISPATCH_NICE_UTILITY; + case DISPATCH_QOS_DEFAULT: + return DISPATCH_NICE_DEFAULT; + case DISPATCH_QOS_USER_INITIATED: + return DISPATCH_NICE_USER_INITIATED; + case DISPATCH_QOS_USER_INTERACTIVE: + return DISPATCH_NICE_USER_INTERACTIVE; + } + + return DISPATCH_NICE_DEFAULT; +} +#endif // defined(__linux__) + + // including maintenance DISPATCH_ALWAYS_INLINE static inline bool From 9a6b76df97d8176d198ef1b7c91516738f1dd30d Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:39:08 +0200 Subject: [PATCH 05/10] Basic worker thread prioritization on Linux --- src/queue.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/queue.c b/src/queue.c index 40f059c7a..99fa3ffef 100644 --- a/src/queue.c +++ b/src/queue.c @@ -23,6 +23,11 @@ #include "protocol.h" // _dispatch_send_wakeup_runloop_thread #endif +#if defined(__linux__) +#include +#include +#endif + static inline void _dispatch_root_queues_init(void); static void _dispatch_lane_barrier_complete(dispatch_lane_class_t dqu, dispatch_qos_t qos, dispatch_wakeup_flags_t flags); @@ -6169,6 +6174,7 @@ _dispatch_root_queue_init_pthread_pool(dispatch_queue_global_t dq, } if (pool_size && pool_size < thread_pool_size) thread_pool_size = pool_size; dq->dgq_thread_pool_size = thread_pool_size; + // TODO: Save priority for later configuration in _dispatch_worker_thread qos_class_t cls = _dispatch_qos_to_qos_class(_dispatch_priority_qos(pri) ?: _dispatch_priority_fallback_qos(pri)); if (cls) { @@ -6216,10 +6222,39 @@ _dispatch_worker_thread(void *context) _dispatch_sigmask(); #endif _dispatch_introspection_thread_add(); + dispatch_priority_t pri = dq->dq_priority; + pthread_priority_t pp = _dispatch_get_priority(); + + // The Linux kernel does not have a direct analogue to the QoS-based + // thread policy engine found in XNU. + // + // We cannot use 'pthread_setschedprio', because all threads with default + // scheduling policy (SCHED_OTHER) have the same pthread 'priority'. + // For both CFS, which was introduced in Linux 2.6.23, and its successor + // EEVDF (since 6.6) 'sched_get_priority_max' and 'sched_get_priority_min' + // will just return 0. + // + // However, as outlined in "man 2 setpriority", the nice value is a + // per‐thread attribute: different threads in the same process can have + // different nice values. We can thus setup the thread's initial priority + // by converting the QoS class and relative priority to a 'nice' value. + #if defined(__linux__) + pp = _dispatch_priority_to_pp_strip_flags(pri); + int nice = _dispatch_pp_to_nice(pp); + + #if HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), "DispatchWorker"); + #endif + + errno = 0; + int rc = setpriority(PRIO_PROCESS, 0, nice); + if (rc != -1 || errno == 0) { + _dispatch_thread_setspecific(dispatch_priority_key, (void *)(uintptr_t)pp); + } + + #endif // defined(__linux__) const int64_t timeout = 5ull * NSEC_PER_SEC; - pthread_priority_t pp = _dispatch_get_priority(); - dispatch_priority_t pri = dq->dq_priority; // If the queue is neither // - the manager From 246cbfcba77522ddf8d33299cfd798d5f1ed1698 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:39:20 +0200 Subject: [PATCH 06/10] Fix small typo --- dispatch/queue.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dispatch/queue.h b/dispatch/queue.h index e2f7e05e9..ff68d2308 100644 --- a/dispatch/queue.h +++ b/dispatch/queue.h @@ -173,12 +173,12 @@ DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial); * * @discussion * Dispatch concurrent queues are lightweight objects to which regular and - * barrier workitems may be submited. Barrier workitems are invoked in + * barrier workitems may be submitted. Barrier workitems are invoked in * exclusion of any other kind of workitem in FIFO order. * * Regular workitems can be invoked concurrently for the same concurrent queue, * in any order. However, regular workitems will not be invoked before any - * barrier workitem submited ahead of them has been invoked. + * barrier workitem submitted ahead of them has been invoked. * * In other words, if a serial queue is equivalent to a mutex in the Dispatch * world, a concurrent queue is equivalent to a reader-writer lock, where From 800fcbedad415dad0c42b718d4aaf91948e970c3 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 16:17:21 +0200 Subject: [PATCH 07/10] Remove TODO --- src/queue.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/queue.c b/src/queue.c index 99fa3ffef..4def68392 100644 --- a/src/queue.c +++ b/src/queue.c @@ -6174,7 +6174,6 @@ _dispatch_root_queue_init_pthread_pool(dispatch_queue_global_t dq, } if (pool_size && pool_size < thread_pool_size) thread_pool_size = pool_size; dq->dgq_thread_pool_size = thread_pool_size; - // TODO: Save priority for later configuration in _dispatch_worker_thread qos_class_t cls = _dispatch_qos_to_qos_class(_dispatch_priority_qos(pri) ?: _dispatch_priority_fallback_qos(pri)); if (cls) { From a3106e097c139e21d11e5f7ab62775d7be281c19 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 18:56:10 -0700 Subject: [PATCH 08/10] Basic prioritisation on Windows --- src/queue.c | 61 ++++++++++++++++++++++++++++++++++++++++++-- src/shims/priority.h | 23 +++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/queue.c b/src/queue.c index 4def68392..0965cfbe5 100644 --- a/src/queue.c +++ b/src/queue.c @@ -28,6 +28,29 @@ #include #endif +#if defined(_WIN32) +// Needs to be free'd after use +static inline wchar_t *_Nullable _dispatch_char_to_wchar_str(const char *str) { + int wideCharSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if (wideCharSize == 0) { + return NULL; + } + + wchar_t* wideCharStr = (wchar_t*)malloc(wideCharSize * sizeof(wchar_t)); + if (wideCharStr == NULL) { + return NULL; + } + + int result = MultiByteToWideChar(CP_UTF8, 0, str, -1, wideCharStr, wideCharSize); + if (result == 0) { + free(wideCharStr); + return NULL; + } + + return wideCharStr; +} +#endif + static inline void _dispatch_root_queues_init(void); static void _dispatch_lane_barrier_complete(dispatch_lane_class_t dqu, dispatch_qos_t qos, dispatch_wakeup_flags_t flags); @@ -6237,11 +6260,14 @@ _dispatch_worker_thread(void *context) // per‐thread attribute: different threads in the same process can have // different nice values. We can thus setup the thread's initial priority // by converting the QoS class and relative priority to a 'nice' value. - #if defined(__linux__) +#if defined(__linux__) pp = _dispatch_priority_to_pp_strip_flags(pri); int nice = _dispatch_pp_to_nice(pp); #if HAVE_PTHREAD_SETNAME_NP + // pthread thread names are restricted to just 16 characters + // including NUL. It does not make sense to pass the queue's + // label as a name. pthread_setname_np(pthread_self(), "DispatchWorker"); #endif @@ -6249,9 +6275,32 @@ _dispatch_worker_thread(void *context) int rc = setpriority(PRIO_PROCESS, 0, nice); if (rc != -1 || errno == 0) { _dispatch_thread_setspecific(dispatch_priority_key, (void *)(uintptr_t)pp); + } else { + _dispatch_log("Failed to set thread priority for worker thread: pqc=%p errno=%d\n", pqc, errno); } +#elif defined(_WIN32) + pp = _dispatch_priority_to_pp_strip_flags(pri); + int win_priority = _dispatch_pp_to_win32_priority(pp); + + HANDLE current = GetCurrentThread(); - #endif // defined(__linux__) + // Set thread description to the label of the root queue + if (dq->dq_label) { + wchar_t *desc = _dispatch_char_to_wchar_str(dq->dq_label); + if (likely(desc != NULL)) { + SetThreadDescription(current, desc); + free(desc); + } + } + + int rc = SetThreadPriority(current, win_priority); + if (rc) { + _dispatch_thread_setspecific(dispatch_priority_key, (void *)(uintptr_t)pp); + } else { + DWORD dwError = GetLastError(); + _dispatch_log("Failed to set thread priority for worker thread: pqc=%p win_priority=%d dwError=%lu\n", pqc, win_priority, dwError); + } +#endif const int64_t timeout = 5ull * NSEC_PER_SEC; @@ -6292,6 +6341,14 @@ _dispatch_worker_thread(void *context) (void)os_atomic_inc2o(dq, dgq_thread_pool_size, release); _dispatch_root_queue_poke(dq, 1, 0); _dispatch_release(dq); // retained in _dispatch_root_queue_poke_slow + +#if defined(_WIN32) + // Make sure to properly end the background processing mode + if (win_priority == THREAD_MODE_BACKGROUND_BEGIN) { + SetThreadPriority(current, THREAD_MODE_BACKGROUND_END); + } +#endif + return NULL; } #if defined(_WIN32) diff --git a/src/shims/priority.h b/src/shims/priority.h index e4e51283c..e20ebb091 100644 --- a/src/shims/priority.h +++ b/src/shims/priority.h @@ -249,6 +249,29 @@ static inline int _dispatch_pp_to_nice(pthread_priority_t pp) } #endif // defined(__linux__) +#if defined(_WIN32) +DISPATCH_ALWAYS_INLINE +static inline int _dispatch_pp_to_win32_priority(pthread_priority_t pp) { + uint32_t qos = _dispatch_qos_from_pp(pp); + + switch (qos) { + case DISPATCH_QOS_BACKGROUND: + // Make sure to end background mode before exiting the thread! + return THREAD_MODE_BACKGROUND_BEGIN; + case DISPATCH_QOS_UTILITY: + return THREAD_PRIORITY_BELOW_NORMAL; + case DISPATCH_QOS_DEFAULT: + return THREAD_PRIORITY_NORMAL; + case DISPATCH_QOS_USER_INITIATED: + return THREAD_PRIORITY_ABOVE_NORMAL; + case DISPATCH_QOS_USER_INTERACTIVE: + return THREAD_PRIORITY_HIGHEST; + } + + return THREAD_PRIORITY_NORMAL; +} +#endif // defined(_WIN32) + // including maintenance DISPATCH_ALWAYS_INLINE From d5afe08a9ea32989dbacc071578bb57f851e6586 Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 18 Nov 2024 10:19:21 +0100 Subject: [PATCH 09/10] Cap thread priority to normal for qos >= DISPATCH_QOS_DEFAULT The high-priority class should be reserved for threads that must respond to time-critical events, user input threads should be THREAD_PRIORITY_NORMAL. --- src/shims/priority.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shims/priority.h b/src/shims/priority.h index e20ebb091..92646c3db 100644 --- a/src/shims/priority.h +++ b/src/shims/priority.h @@ -262,10 +262,12 @@ static inline int _dispatch_pp_to_win32_priority(pthread_priority_t pp) { return THREAD_PRIORITY_BELOW_NORMAL; case DISPATCH_QOS_DEFAULT: return THREAD_PRIORITY_NORMAL; + // User input threads should be THREAD_PRIORITY_NORMAL, to + // avoid unintentionally starving the system case DISPATCH_QOS_USER_INITIATED: - return THREAD_PRIORITY_ABOVE_NORMAL; + return THREAD_PRIORITY_NORMAL; case DISPATCH_QOS_USER_INTERACTIVE: - return THREAD_PRIORITY_HIGHEST; + return THREAD_PRIORITY_NORMAL; } return THREAD_PRIORITY_NORMAL; From 4be2a89e86750a2e30fd061e86a83aeb4e9c4f6b Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 18 Nov 2024 10:33:40 +0100 Subject: [PATCH 10/10] Implement _dispatch_win32_set_thread_description This function is a wrapper around SetThreadDescription, which accepts UTF-8 strings. --- src/queue.c | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/queue.c b/src/queue.c index 0965cfbe5..3244110a5 100644 --- a/src/queue.c +++ b/src/queue.c @@ -29,25 +29,28 @@ #endif #if defined(_WIN32) -// Needs to be free'd after use -static inline wchar_t *_Nullable _dispatch_char_to_wchar_str(const char *str) { - int wideCharSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); - if (wideCharSize == 0) { - return NULL; +// Wrapper around SetThreadDescription for UTF-8 strings +void _dispatch_win32_set_thread_description(HANDLE hThread, const char *description) { + int wcsize = MultiByteToWideChar(CP_UTF8, 0, description, -1, NULL, 0); + if (wcsize == 0) { + return; } - wchar_t* wideCharStr = (wchar_t*)malloc(wideCharSize * sizeof(wchar_t)); - if (wideCharStr == NULL) { - return NULL; + wchar_t* wcstr = (wchar_t*)malloc(wcsize * sizeof(wchar_t)); + if (wcstr == NULL) { + return; } - int result = MultiByteToWideChar(CP_UTF8, 0, str, -1, wideCharStr, wideCharSize); + int result = MultiByteToWideChar(CP_UTF8, 0, description, -1, wcstr, wcsize); if (result == 0) { - free(wideCharStr); - return NULL; + free(wcstr); + return; } - return wideCharStr; + if (likely(wcstr != NULL)) { + SetThreadDescription(hThread, wcstr); + free(wcstr); + } } #endif @@ -6286,11 +6289,7 @@ _dispatch_worker_thread(void *context) // Set thread description to the label of the root queue if (dq->dq_label) { - wchar_t *desc = _dispatch_char_to_wchar_str(dq->dq_label); - if (likely(desc != NULL)) { - SetThreadDescription(current, desc); - free(desc); - } + _dispatch_win32_set_thread_description(current, dq->dq_label); } int rc = SetThreadPriority(current, win_priority);