From e37c2465e08a516099ec2dfc8588380c53c27c44 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:35:21 +0200 Subject: [PATCH 01/12] 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 2f2efb0ae..b7364fea3 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 6fb87f82faa14cdf90042fa3849a9c31aec766b8 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:35:32 +0200 Subject: [PATCH 02/12] Check for pthread_setname_np --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac6e56857..189cd9fc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,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 cdeb87de902ba41b81bad5cd7771108f8655e2a2 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:35:49 +0200 Subject: [PATCH 03/12] 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 27737c991..fc630ea67 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 8e935e022f7d495cbcb237e59b2e92212ee939a6 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:37:38 +0200 Subject: [PATCH 04/12] 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 8913f063942864b664383148bb77288d5824b12b Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:39:08 +0200 Subject: [PATCH 05/12] 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 1d948e947..c4859b35d 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 dc8a3abb019ef9a3b91f80348fd1ece11d1160e6 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 15:39:20 +0200 Subject: [PATCH 06/12] 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 cf2abb1ab4b516c633a3357b65e5fa8d43c67a92 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 16:17:21 +0200 Subject: [PATCH 07/12] Remove TODO --- src/queue.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/queue.c b/src/queue.c index c4859b35d..490d7be47 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 1072e1ca5ae1872c4f981f93c922edafb58255f3 Mon Sep 17 00:00:00 2001 From: hmelder Date: Tue, 27 Aug 2024 18:56:10 -0700 Subject: [PATCH 08/12] 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 490d7be47..256ce2dbe 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 2d98ac7a7c0c22106c6729ca15d7c7d9654b15ab Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 18 Nov 2024 10:19:21 +0100 Subject: [PATCH 09/12] 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 9d76b1764121f79c54b777ea20d8203e5a115424 Mon Sep 17 00:00:00 2001 From: hmelder Date: Mon, 18 Nov 2024 10:33:40 +0100 Subject: [PATCH 10/12] 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 256ce2dbe..8fea3c6c5 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); From adcc48b80944578c2e82fcd7d5d8d3a76817bca2 Mon Sep 17 00:00:00 2001 From: Hugo Melder Date: Tue, 3 Jun 2025 09:57:37 +0200 Subject: [PATCH 11/12] Simplify error handling of string conversion Unconditionally free heap-allocated buffer, and only set thread description if MultiByteToWideChar returns a non-zero integer. --- src/queue.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/queue.c b/src/queue.c index 8fea3c6c5..cbead2ed0 100644 --- a/src/queue.c +++ b/src/queue.c @@ -42,15 +42,11 @@ void _dispatch_win32_set_thread_description(HANDLE hThread, const char *descript } int result = MultiByteToWideChar(CP_UTF8, 0, description, -1, wcstr, wcsize); - if (result == 0) { - free(wcstr); - return; - } - - if (likely(wcstr != NULL)) { + if (result != 0) { SetThreadDescription(hThread, wcstr); - free(wcstr); } + + free(wcstr); } #endif From cf63fca0be3a551b6584b954f79960c79d5be89d Mon Sep 17 00:00:00 2001 From: Hugo Melder Date: Tue, 3 Jun 2025 10:09:06 +0200 Subject: [PATCH 12/12] Move discussion about thread policy below macro --- src/queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queue.c b/src/queue.c index cbead2ed0..e369e5b9f 100644 --- a/src/queue.c +++ b/src/queue.c @@ -6246,6 +6246,7 @@ _dispatch_worker_thread(void *context) dispatch_priority_t pri = dq->dq_priority; pthread_priority_t pp = _dispatch_get_priority(); +#if defined(__linux__) // The Linux kernel does not have a direct analogue to the QoS-based // thread policy engine found in XNU. // @@ -6259,7 +6260,6 @@ _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__) pp = _dispatch_priority_to_pp_strip_flags(pri); int nice = _dispatch_pp_to_nice(pp);