8000 gh-59705: Implement _thread.set_name() on Windows (#128675) · python/cpython@d7f703d · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d7f703d

Browse files
vstinnereryksun
andauthored
gh-59705: Implement _thread.set_name() on Windows (#128675)
Implement set_name() with SetThreadDescription() and _get_name() with GetThreadDescription(). If SetThreadDescription() or GetThreadDescription() is not available in kernelbase.dll, delete the method when the _thread module is imported. Truncate the thread name to 32766 characters. Co-authored-by: Eryk Sun <eryksun@gmail.com>
1 parent 76856ae commit d7f703d

File tree

4 files changed

+123
-15
lines changed

4 files changed

+123
-15
lines changed

Lib/test/test_threading.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2130,6 +2130,15 @@ def test_set_name(self):
21302130

21312131
# Test long non-ASCII name (truncated)
21322132
"x" * (limit - 1) + "é€",
2133+
2134+
# Test long non-BMP names (truncated) creating surrogate pairs
2135+
# on Windows
2136+
"x" * (limit - 1) + "\U0010FFFF",
2137+
"x" * (limit - 2) + "\U0010FFFF" * 2,
2138+
"x" + "\U0001f40d" * limit,
2139+
"xx" + "\U0001f40d" * limit,
2140+
"xxx" + "\U0001f40d" * limit,
2141+
"xxxx" + "\U0001f40d" * limit,
21332142
]
21342143
if os_helper.FS_NONASCII:
21352144
tests.append(f"nonascii:{os_helper.FS_NONASCII}")
@@ -2146,15 +2155,31 @@ def work():
21462155
work_name = _thread._get_name()
21472156

21482157
for name in tests:
2149-
encoded = name.encode(encoding, "replace")
2150-
if b'\0' in encoded:
2151-
encoded = encoded.split(b'\0', 1)[0]
2152-
if truncate is not None:
2153-
encoded = encoded[:truncate]
2154-
if sys.platform.startswith("solaris"):
2155-
expected = encoded.decode("utf-8", "surrogateescape")
2158+
if not support.MS_WINDOWS:
2159+
encoded = name.encode(encoding, "replace")
2160+
if b'\0' in encoded:
2161+
encoded = encoded.split(b'\0', 1)[0]
2162+
if truncate is not None:
2163+
encoded = encoded[:truncate]
2164+
if sys.platform.startswith("solaris"):
2165+
expected = encoded.decode("utf-8", "surrogateescape")
2166+
else:
2167+
expected = os.fsdecode(encoded)
21562168
else:
2157-
expected = os.fsdecode(encoded)
2169+
size = 0
2170+
chars = []
2171+
for ch in name:
2172+
if ord(ch) > 0xFFFF:
2173+
size += 2
2174+
else:
2175+
size += 1
2176+
if size > truncate:
2177+
break
2178+
chars.append(ch)
2179+
expected = ''.join(chars)
2180+
2181+
if '\0' in expected:
2182+
expected = expected.split('\0', 1)[0]
21582183

21592184
with self.subTest(name=name, expected=expected):
21602185
work_name = None

Modules/_threadmodule.c

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line nu 8000 mberDiff line change
@@ -47,6 +47,14 @@ get_thread_state(PyObject *module)
4747
}
4848

4949

50+
#ifdef MS_WINDOWS
51+
typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*);
52+
typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR);
53+
static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL;
54+
static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL;
55+
#endif
56+
57+
5058
/*[clinic input]
5159
module _thread
5260
[clinic start generated code]*/
@@ -2368,7 +2376,7 @@ Internal only. Return a non-zero integer that uniquely identifies the main threa
23682376
of the main interpreter.");
23692377

23702378

2371-
#ifdef HAVE_PTHREAD_GETNAME_NP
2379+
#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(MS_WINDOWS)
23722380
/*[clinic input]
23732381
_thread._get_name
23742382
@@ -2379,6 +2387,7 @@ static PyObject *
23792387
_thread__get_name_impl(PyObject *module)
23802388
/*[clinic end generated code: output=20026e7ee3da3dd7 input=35cec676833d04c8]*/
23812389
{
2390+
#ifndef MS_WINDOWS
23822391
// Linux and macOS are limited to respectively 16 and 64 bytes
23832392
char name[100];
23842393
pthread_t thread = pthread_self();
@@ -2393,11 +2402,26 @@ _thread__get_name_impl(PyObject *module)
23932402
#else
23942403
return PyUnicode_DecodeFSDefault(name);
23952404
#endif
2405+
#else
2406+
// Windows implementation
2407+
assert(pGetThreadDescription != NULL);
2408+
2409+
wchar_t *name;
2410+
HRESULT hr = pGetThreadDescription(GetCurrentThread(), &name);
2411+
if (FAILED(hr)) {
2412+
PyErr_SetFromWindowsErr(0);
2413+
return NULL;
2414+
}
2415+
2416+
PyObject *name_obj = PyUnicode_FromWideChar(name, -1);
2417+
LocalFree(name);
2418+
return name_obj;
2419+
#endif
23962420
}
23972421
#endif // HAVE_PTHREAD_GETNAME_NP
23982422

23992423

2400-
#ifdef HAVE_PTHREAD_SETNAME_NP
2424+
#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(MS_WINDOWS)
24012425
/*[clinic input]
24022426
_thread.set_name
24032427
@@ -2410,6 +2434,7 @@ static PyObject *
24102434
_thread_set_name_impl(PyObject *module, PyObject *name_obj)
24112435
/*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/
24122436
{
2437+
#ifndef MS_WINDOWS
24132438
#ifdef __sun
24142439
// Solaris always uses UTF-8
24152440
const char *encoding = "utf-8";
@@ -2455,6 +2480,35 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj)
24552480
return PyErr_SetFromErrno(PyExc_OSError);
24562481
}
24572482
Py_RETURN_NONE;
2483+
#else
2484+
// Windows implementation
2485+
assert(pSetThreadDescription != NULL);
2486+
2487+
Py_ssize_t len;
2488+
wchar_t *name = PyUnicode_AsWideCharString(name_obj, &len);
2489+
if (name == NULL) {
2490+
return NULL;
2491+
}
2492+
2493+
if (len > PYTHREAD_NAME_MAXLEN) {
2494+
// Truncate the name
2495+
Py_UCS4 ch = name[PYTHREAD_NAME_MAXLEN-1];
2496+
if (Py_UNICODE_IS_HIGH_SURROGATE(ch)) {
2497+
name[PYTHREAD_NAME_MAXLEN-1] = 0;
2498+
}
2499+
else {
2500+
name[PYTHREAD_NAME_MAXLEN] = 0;
2501+
}
2502+
}
2503+
2504+
HRESULT hr = pSetThreadDescription(GetCurrentThread(), name);
2505+
PyMem_Free(name);
2506+
if (FAILED(hr)) {
2507+
PyErr_SetFromWindowsErr((int)hr);
2508+
return NULL;
2509+
}
2510+
Py_RETURN_NONE;
2511+
#endif
24582512
}
24592513
#endif // HAVE_PTHREAD_SETNAME_NP
24602514

@@ -2598,6 +2652,31 @@ thread_module_exec(PyObject *module)
25982652
}
25992653
#endif
26002654

2655+
#ifdef MS_WINDOWS
2656+
HMODULE kernelbase = GetModuleHandleW(L"kernelbase.dll");
2657+
if (kernelbase != NULL) {
2658+
if (pGetThreadDescription == NULL) {
2659+
pGetThreadDescription = (PF_GET_THREAD_DESCRIPTION)GetProcAddress(
2660+
kernelbase, "GetThreadDescription");
2661+
}
2662+
if (pSetThreadDescription == NULL) {
2663+
pSetThreadDescription = (PF_SET_THREAD_DESCRIPTION)GetProcAddress(
2664+
kernelbase, "SetThreadDescription");
2665+
}
2666+
}
2667+
2668+
if (pGetThreadDescription == NULL) {
2669+
if (PyObject_DelAttrString(module, "_get_name") < 0) {
2670+
return -1;
2671+
}
2672+
}
2673+
if (pSetThreadDescription == NULL) {
2674+
if (PyObject_DelAttrString(module, "set_name") < 0) {
2675+
return -1;
2676+
}
2677+
}
2678+
#endif
2679+
26012680
return 0;
26022681
}
26032682

Modules/clinic/_threadmodule.c.h

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PC/pyconfig.h.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,4 +753,8 @@ Py_NO_ENABLE_SHARED to find out. Also support MS_NO_COREDLL for b/w compat */
753753
/* Define if libssl has X509_VERIFY_PARAM_set1_host and related function */
754754
#define HAVE_X509_VERIFY_PARAM_SET1_HOST 1
755755

756+
// Truncate the thread name to 64 characters. The OS limit is 32766 wide
757+
// characters, but long names aren't of practical use.
758+
#define PYTHREAD_NAME_MAXLEN 32766
759+
756760
#endif /* !Py_CONFIG_H */

0 commit comments

Comments
 (0)
0