src: add uv_thread_set/getname() methods (#4599)

`uv_thread_setname()` sets the name of the current thread. Different
platforms define different limits on the max number of characters
a thread name can be: Linux, IBMi (16), macOS (64), Windows (32767),
and NetBSD (32), etc. `uv_thread_setname()` will truncate it in case
`name` is larger than the limit of the platform.

`uv_thread_getname()` gets the name of the thread specified by `tid`.
The thread name is copied into the buffer pointed to by `name`. The
`size` parameter specifies the size of the buffer pointed to by `name`.
The buffer should be large enough to hold the name of the thread plus
the trailing NUL, or it will be truncated to fit.
This commit is contained in:
Santiago Gimeno 2024-11-27 12:52:18 +01:00 committed by GitHub
parent b7d07d78e9
commit 61c966cf0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 350 additions and 18 deletions

View File

@ -667,6 +667,7 @@ if(LIBUV_BUILD_TESTS)
test/test-thread-affinity.c
test/test-thread-equal.c
test/test-thread.c
test/test-thread-name.c
test/test-thread-priority.c
test/test-threadpool-cancel.c
test/test-threadpool.c

View File

@ -294,6 +294,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \
test/test-thread-equal.c \
test/test-thread.c \
test/test-thread-affinity.c \
test/test-thread-name.c \
test/test-thread-priority.c \
test/test-threadpool-cancel.c \
test/test-threadpool.c \

View File

@ -140,6 +140,23 @@ Threads
.. c:function:: int uv_thread_join(uv_thread_t *tid)
.. c:function:: int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2)
.. c:function:: int uv_thread_setname(const char* name)
Sets the name of the current thread. Different platforms define different limits on the max number of characters
a thread name can be: Linux, IBM i (16), macOS (64), Windows (32767), and NetBSD (32), etc. `uv_thread_setname()`
will truncate it in case `name` is larger than the limit of the platform.
.. versionadded:: 1.50.0
.. c:function:: int uv_thread_getname(uv_thread_t* tid, char* name, size_t* size)
Gets the name of the thread specified by `tid`. The thread name is copied, with the trailing NUL, into the buffer
pointed to by `name`. The `size` parameter specifies the size of the buffer pointed to by `name`.
The buffer should be large enough to hold the name of the thread plus the trailing NUL, or it will be truncated to fit
with the trailing NUL.
.. versionadded:: 1.50.0
.. c:function:: int uv_thread_setpriority(uv_thread_t tid, int priority)
If the function succeeds, the return value is 0.
If the function fails, the return value is less than zero.

View File

@ -1900,6 +1900,9 @@ UV_EXTERN int uv_thread_getcpu(void);
UV_EXTERN uv_thread_t uv_thread_self(void);
UV_EXTERN int uv_thread_join(uv_thread_t *tid);
UV_EXTERN int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2);
UV_EXTERN int uv_thread_setname(const char* name);
UV_EXTERN int uv_thread_getname(uv_thread_t* tid, char* name, size_t size);
/* The presence of these unions force similar struct layout. */
#define XX(_, name) uv_ ## name ## _t name;

View File

@ -33,25 +33,9 @@
#include "darwin-stub.h"
#endif
static int uv__pthread_setname_np(const char* name) {
char namebuf[64]; /* MAXTHREADNAMESIZE */
int err;
strncpy(namebuf, name, sizeof(namebuf) - 1);
namebuf[sizeof(namebuf) - 1] = '\0';
err = pthread_setname_np(namebuf);
if (err)
return UV__ERR(err);
return 0;
}
int uv__set_process_title(const char* title) {
#if TARGET_OS_IPHONE
return uv__pthread_setname_np(title);
return uv__thread_setname(title);
#else
CFStringRef (*pCFStringCreateWithCString)(CFAllocatorRef,
const char*,
@ -177,7 +161,7 @@ int uv__set_process_title(const char* title) {
goto out;
}
uv__pthread_setname_np(title); /* Don't care if it fails. */
uv__thread_setname(title); /* Don't care if it fails. */
err = 0;
out:

View File

@ -327,6 +327,8 @@ void uv__prepare_close(uv_prepare_t* handle);
void uv__process_close(uv_process_t* handle);
void uv__stream_close(uv_stream_t* handle);
void uv__tcp_close(uv_tcp_t* handle);
int uv__thread_setname(const char* name);
int uv__thread_getname(uv_thread_t* tid, char* name, size_t size);
size_t uv__thread_stack_size(void);
void uv__udp_close(uv_udp_t* handle);
void uv__udp_finish_close(uv_udp_t* handle);

View File

@ -23,6 +23,9 @@
#include "internal.h"
#include <pthread.h>
#ifdef __OpenBSD__
#include <pthread_np.h>
#endif
#include <assert.h>
#include <errno.h>
@ -297,6 +300,18 @@ int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2) {
return pthread_equal(*t1, *t2);
}
int uv_thread_setname(const char* name) {
if (name == NULL)
return UV_EINVAL;
return uv__thread_setname(name);
}
int uv_thread_getname(uv_thread_t* tid, char* name, size_t size) {
if (name == NULL || size == 0)
return UV_EINVAL;
return uv__thread_getname(tid, name, size);
}
int uv_mutex_init(uv_mutex_t* mutex) {
#if defined(NDEBUG) || !defined(PTHREAD_MUTEX_ERRORCHECK)
@ -881,3 +896,80 @@ void uv_key_set(uv_key_t* key, void* value) {
if (pthread_setspecific(*key, value))
abort();
}
#if defined(_AIX) || defined(__MVS__) || defined(__PASE__)
int uv__thread_setname(const char* name) {
return UV_ENOSYS;
}
#elif defined(__APPLE__)
int uv__thread_setname(const char* name) {
char namebuf[UV_PTHREAD_MAX_NAMELEN_NP];
strncpy(namebuf, name, sizeof(namebuf) - 1);
namebuf[sizeof(namebuf) - 1] = '\0';
int err = pthread_setname_np(namebuf);
if (err)
return UV__ERR(errno);
return 0;
}
#elif defined(__NetBSD__)
int uv__thread_setname(const char* name) {
char namebuf[UV_PTHREAD_MAX_NAMELEN_NP];
strncpy(namebuf, name, sizeof(namebuf) - 1);
namebuf[sizeof(namebuf) - 1] = '\0';
return UV__ERR(pthread_setname_np(pthread_self(), "%s", namebuf));
}
#elif defined(__OpenBSD__)
int uv__thread_setname(const char* name) {
char namebuf[UV_PTHREAD_MAX_NAMELEN_NP];
strncpy(namebuf, name, sizeof(namebuf) - 1);
namebuf[sizeof(namebuf) - 1] = '\0';
pthread_set_name_np(pthread_self(), namebuf);
return 0;
}
#else
int uv__thread_setname(const char* name) {
char namebuf[UV_PTHREAD_MAX_NAMELEN_NP];
strncpy(namebuf, name, sizeof(namebuf) - 1);
namebuf[sizeof(namebuf) - 1] = '\0';
return UV__ERR(pthread_setname_np(pthread_self(), namebuf));
}
#endif
#if (defined(__ANDROID_API__) && __ANDROID_API__ < 26) || \
defined(_AIX) || \
defined(__MVS__) || \
defined(__PASE__)
int uv__thread_getname(uv_thread_t* tid, char* name, size_t size) {
return UV_ENOSYS;
}
#elif defined(__OpenBSD__)
int uv__thread_getname(uv_thread_t* tid, char* name, size_t size) {
char thread_name[UV_PTHREAD_MAX_NAMELEN_NP];
pthread_get_name_np(*tid, thread_name, sizeof(thread_name));
strncpy(name, thread_name, size - 1);
name[size - 1] = '\0';
return 0;
}
#elif defined(__APPLE__)
int uv__thread_getname(uv_thread_t* tid, char* name, size_t size) {
char thread_name[UV_PTHREAD_MAX_NAMELEN_NP];
if (pthread_getname_np(*tid, thread_name, sizeof(thread_name)) != 0)
return UV__ERR(errno);
strncpy(name, thread_name, size - 1);
name[size - 1] = '\0';
return 0;
}
#else
int uv__thread_getname(uv_thread_t* tid, char* name, size_t size) {
int r;
char thread_name[UV_PTHREAD_MAX_NAMELEN_NP];
r = pthread_getname_np(*tid, thread_name, sizeof(thread_name));
if (r != 0)
return UV__ERR(r);
strncpy(name, thread_name, size - 1);
name[size - 1] = '\0';
return 0;
}
#endif

View File

@ -428,4 +428,18 @@ struct uv__loop_internal_fields_s {
#endif /* __linux__ */
};
#if defined(_WIN32)
# define UV_PTHREAD_MAX_NAMELEN_NP 32767
#elif defined(__APPLE__)
# define UV_PTHREAD_MAX_NAMELEN_NP 64
#elif defined(__NetBSD__) || defined(__illumos__)
# define UV_PTHREAD_MAX_NAMELEN_NP PTHREAD_MAX_NAMELEN_NP
#elif defined (__linux__)
# define UV_PTHREAD_MAX_NAMELEN_NP 16
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
# define UV_PTHREAD_MAX_NAMELEN_NP (MAXCOMLEN + 1)
#else
# define UV_PTHREAD_MAX_NAMELEN_NP 16
#endif
#endif /* UV_COMMON_H_ */

View File

@ -278,6 +278,79 @@ int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2) {
}
int uv_thread_setname(const char* name) {
#ifdef __MINGW32__
return UV_ENOSYS;
#else
HRESULT hr;
WCHAR* namew;
int err;
char namebuf[UV_PTHREAD_MAX_NAMELEN_NP];
if (name == NULL)
return UV_EINVAL;
strncpy(namebuf, name, sizeof(namebuf) - 1);
namebuf[sizeof(namebuf) - 1] = '\0';
namew = NULL;
err = uv__convert_utf8_to_utf16(namebuf, &namew);
if (err)
return err;
hr = SetThreadDescription(GetCurrentThread(), namew);
uv__free(namew);
if (FAILED(hr))
return uv_translate_sys_error(HRESULT_CODE(hr));
return 0;
#endif /* __MINGW32__ */
}
int uv_thread_getname(uv_thread_t* tid, char* name, size_t size) {
#ifdef __MINGW32__
return UV_ENOSYS;
#else
HRESULT hr;
WCHAR* namew;
char* thread_name;
size_t buf_size;
int r;
DWORD exit_code;
if (name == NULL || size == 0)
return UV_EINVAL;
if (tid == NULL || *tid == NULL)
return UV_EINVAL;
/* Check if the thread handle is valid */
if (!GetExitCodeThread(*tid, &exit_code) || exit_code != STILL_ACTIVE)
return UV_ENOENT;
namew = NULL;
thread_name = NULL;
hr = GetThreadDescription(*tid, &namew);
if (FAILED(hr))
return uv_translate_sys_error(HRESULT_CODE(hr));
buf_size = size;
r = uv__copy_utf16_to_utf8(namew, -1, name, &buf_size);
if (r == UV_ENOBUFS) {
r = uv__convert_utf16_to_utf8(namew, wcslen(namew), &thread_name);
if (r == 0) {
uv__strscpy(name, thread_name, size);
uv__free(thread_name);
}
}
LocalFree(namew);
return r;
#endif /* __MINGW32__ */
}
int uv_mutex_init(uv_mutex_t* mutex) {
InitializeCriticalSection(mutex);
return 0;

View File

@ -478,6 +478,7 @@ TEST_DECLARE (thread_create)
TEST_DECLARE (thread_equal)
TEST_DECLARE (thread_affinity)
TEST_DECLARE (thread_priority)
TEST_DECLARE (thread_name)
TEST_DECLARE (dlerror)
#if (defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))) && \
!defined(__sun)
@ -1193,6 +1194,7 @@ TASK_LIST_START
TEST_ENTRY (thread_equal)
TEST_ENTRY (thread_affinity)
TEST_ENTRY (thread_priority)
TEST_ENTRY (thread_name)
TEST_ENTRY (dlerror)
TEST_ENTRY (ip4_addr)
TEST_ENTRY (ip6_addr_link_local)

143
test/test-thread-name.c Normal file
View File

@ -0,0 +1,143 @@
/* Copyright libuv project contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "uv.h"
#include "task.h"
#include "../src/uv-common.h"
#include <string.h>
struct semaphores {
uv_sem_t main;
uv_sem_t worker;
};
static void thread_run(void* arg) {
int r;
char thread_name[16];
struct semaphores* sem;
uv_thread_t thread;
sem = arg;
#ifdef _WIN32
/* uv_thread_self isn't defined for the main thread on Windows. */
thread = GetCurrentThread();
#else
thread = uv_thread_self();
#endif
r = uv_thread_setname("worker-thread");
ASSERT_OK(r);
uv_sem_post(&sem->worker);
r = uv_thread_getname(&thread, thread_name, sizeof(thread_name));
ASSERT_OK(r);
ASSERT_STR_EQ(thread_name, "worker-thread");
uv_sem_wait(&sem->main);
}
TEST_IMPL(thread_name) {
int r;
uv_thread_t threads[2];
char tn[UV_PTHREAD_MAX_NAMELEN_NP];
char thread_name[UV_PTHREAD_MAX_NAMELEN_NP];
char long_thread_name[UV_PTHREAD_MAX_NAMELEN_NP + 1];
struct semaphores sem;
#if defined(__MINGW32__) || \
defined(__ANDROID_API__) && __ANDROID_API__ < 26 || \
defined(_AIX) || \
defined(__MVS__) || \
defined(__PASE__)
RETURN_SKIP("API not available on this platform");
#endif
ASSERT_OK(uv_sem_init(&sem.main, 0));
ASSERT_OK(uv_sem_init(&sem.worker, 0));
memset(thread_name, 'a', sizeof(thread_name) - 1);
thread_name[sizeof(thread_name) - 1] = '\0';
memset(long_thread_name, 'a', sizeof(long_thread_name) - 1);
long_thread_name[sizeof(long_thread_name) - 1] = '\0';
#ifdef _WIN32
/* uv_thread_self isn't defined for the main thread on Windows. */
threads[0] = GetCurrentThread();
#else
threads[0] = uv_thread_self();
#endif
r = uv_thread_getname(&threads[0], tn, sizeof(tn));
ASSERT_OK(r);
r = uv_thread_setname(long_thread_name);
ASSERT_OK(r);
r = uv_thread_getname(&threads[0], tn, sizeof(tn));
ASSERT_OK(r);
ASSERT_STR_EQ(tn, thread_name);
r = uv_thread_setname(thread_name);
ASSERT_OK(r);
r = uv_thread_getname(&threads[0], tn, sizeof(tn));
ASSERT_OK(r);
ASSERT_STR_EQ(tn, thread_name);
r = uv_thread_getname(&threads[0], tn, 3);
ASSERT_OK(r);
ASSERT_EQ(strlen(tn), 2);
ASSERT_OK(memcmp(thread_name, tn, 2));
/* Illumos doesn't support non-ASCII thread names. */
#ifndef __illumos__
r = uv_thread_setname("~½¬{½");
ASSERT_OK(r);
r = uv_thread_getname(&threads[0], tn, sizeof(tn));
ASSERT_OK(r);
ASSERT_STR_EQ(tn, "~½¬{½");
#endif
ASSERT_OK(uv_thread_create(threads + 1, thread_run, &sem));
uv_sem_wait(&sem.worker);
r = uv_thread_getname(threads + 1, tn, sizeof(tn));
ASSERT_OK(r);
ASSERT_STR_EQ(tn, "worker-thread");
uv_sem_post(&sem.main);
ASSERT_OK(uv_thread_join(threads + 1));
uv_sem_destroy(&sem.main);
uv_sem_destroy(&sem.worker);
return 0;
}