From d97417df9a553a8608bf08c2de40d1ec761df10d Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 02:02:38 -0400 Subject: [PATCH 01/41] fix threadmodule ascii and make test more lenient --- Lib/test/test_threading.py | 15 +++++++++---- Misc/ACKS | 1 + Modules/_threadmodule.c | 45 ++++++++++++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 0ba78b9a1807d2..5fa717461fc18a 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2358,10 +2358,17 @@ def work(): with self.subTest(name=name, expected=expected): work_name = None thread = threading.Thread(target=work, name=name) - thread.start() - thread.join() - self.assertEqual(work_name, expected, - f"{len(work_name)=} and {len(expected)=}") + try: + thread.start() + thread.join() + self.assertEqual(work_name, expected, + f"{len(work_name)=} and {len(expected)=}") + except OSError as exc: + # Accept EINVAL (22) for non-ASCII names on platforms that do not support them + if getattr(exc, 'errno', None) == 22 and any(ord(c) > 127 for c in name): + self.skipTest(f"Platform does not support non-ASCII thread names: {exc}") + else: + raise @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") diff --git a/Misc/ACKS b/Misc/ACKS index dc28ccf8f57eda..37b7988606fa99 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -483,6 +483,7 @@ Weilin Du John DuBois Paul Dubois Jacques Ducasse +Jadon Duff Andrei Dorian Duma Graham Dumpleton Quinn Dunkan diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 0a22907375b0dd..5b1542338eab4a 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2611,20 +2611,57 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) #endif const char *name = PyBytes_AS_STRING(name_encoded); + int rc; #ifdef __APPLE__ - int rc = pthread_setname_np(name); + rc = pthread_setname_np(name); #elif defined(__NetBSD__) pthread_t thread = pthread_self(); - int rc = pthread_setname_np(thread, "%s", (void *)name); + rc = pthread_setname_np(thread, "%s", (void *)name); #elif defined(HAVE_PTHREAD_SETNAME_NP) pthread_t thread = pthread_self(); - int rc = pthread_setname_np(thread, name); + rc = pthread_setname_np(thread, name); #else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ pthread_t thread = pthread_self(); - int rc = 0; /* pthread_set_name_np() returns void */ + rc = 0; /* pthread_set_name_np() returns void */ pthread_set_name_np(thread, name); #endif Py_DECREF(name_encoded); + + // Fallback: If EINVAL, try ASCII encoding with "replace" + if (rc == EINVAL) { + name_encoded = PyUnicode_AsEncodedString(name_obj, "ascii", "replace"); + if (name_encoded == NULL) { + return NULL; + } +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated; + truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); + return NULL; + } + Py_SETREF(name_encoded, truncated); + } +#endif + name = PyBytes_AS_STRING(name_encoded); +#ifdef __APPLE__ + rc = pthread_setname_np(name); +#elif defined(__NetBSD__) + thread = pthread_self(); + rc = pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + thread = pthread_self(); + rc = pthread_setname_np(thread, name); +#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ + thread = pthread_self(); + rc = 0; + pthread_set_name_np(thread, name); +#endif + Py_DECREF(name_encoded); + } + if (rc) { errno = rc; return PyErr_SetFromErrno(PyExc_OSError); From 1db08a750eacdb52456e77128155fc62a26110ad Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 06:31:43 +0000 Subject: [PATCH 02/41] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst new file mode 100644 index 00000000000000..cc7c6bbded619e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst @@ -0,0 +1,7 @@ +``` +Core and Builtins +----------------- + +* _thread.set_name() now retries with an ASCII fallback if pthread_setname_np() rejects UTF-8 names on some platforms (e.g. OpenIndiana). +* test_threading.py was updated to skip non-ASCII thread name tests on platforms that do not support them. +``` From 060696862d54b5f6850e4fb6f66961dde9439b2a Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 20:42:37 -0400 Subject: [PATCH 03/41] implement requested changes --- Lib/test/test_threading.py | 4 +++ Modules/_threadmodule.c | 63 ++++++++++++++++---------------------- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 5fa717461fc18a..96a19646eec594 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2241,6 +2241,7 @@ def __init__(self, a, *, b) -> None: with warnings.catch_warnings(record=True) as warnings_log: CustomRLock(1, b=2) + self.assertEqual(warnings_log, []) class EventTests(lock_tests.EventTests): @@ -2361,6 +2362,9 @@ def work(): try: thread.start() thread.join() + # If the name is non-ASCII and the result is empty, skip (platform limitation) + if any(ord(c) > 127 for c in name) and (not work_name or work_name == ""): + self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}") self.assertEqual(work_name, expected, f"{len(work_name)=} and {len(expected)=}") except OSError as exc: diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 5b1542338eab4a..a8c696d0e8b39c 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2576,6 +2576,27 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ +// Helper to set the thread name using platform-specific APIs +static int +_set_thread_name(const char *name) +{ + int rc; +#ifdef __APPLE__ + rc = pthread_setname_np(name); +#elif defined(__NetBSD__) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, name); +#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ + pthread_t thread = pthread_self(); + rc = 0; /* pthread_set_name_np() returns void */ + pthread_set_name_np(thread, name); +#endif + return rc; +} + static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ @@ -2591,17 +2612,15 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) const char *encoding = interp->unicode.fs_codec.encoding; #endif PyObject *name_encoded; + int rc; + name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); if (name_encoded == NULL) { return NULL; } - #ifdef _PYTHREAD_NAME_MAXLEN - // Truncate to _PYTHREAD_NAME_MAXLEN bytes + the NUL byte if needed if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated; - truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); if (truncated == NULL) { Py_DECREF(name_encoded); return NULL; @@ -2609,22 +2628,8 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) Py_SETREF(name_encoded, truncated); } #endif - const char *name = PyBytes_AS_STRING(name_encoded); - int rc; -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ - pthread_t thread = pthread_self(); - rc = 0; /* pthread_set_name_np() returns void */ - pthread_set_name_np(thread, name); -#endif + rc = _set_thread_name(name); Py_DECREF(name_encoded); // Fallback: If EINVAL, try ASCII encoding with "replace" @@ -2635,9 +2640,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) } #ifdef _PYTHREAD_NAME_MAXLEN if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated; - truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); if (truncated == NULL) { Py_DECREF(name_encoded); return NULL; @@ -2646,19 +2649,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) } #endif name = PyBytes_AS_STRING(name_encoded); -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ - thread = pthread_self(); - rc = 0; - pthread_set_name_np(thread, name); -#endif + rc = _set_thread_name(name); Py_DECREF(name_encoded); } From b556774383a5a89ccf87cc05e72f23cb0d77d4a4 Mon Sep 17 00:00:00 2001 From: jadonduff Date: Thu, 21 Aug 2025 20:45:23 -0400 Subject: [PATCH 04/41] reformat and remove test news --- .../2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst index cc7c6bbded619e..dce0f89c34f9b4 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst @@ -1,7 +1 @@ -``` -Core and Builtins ------------------ - -* _thread.set_name() now retries with an ASCII fallback if pthread_setname_np() rejects UTF-8 names on some platforms (e.g. OpenIndiana). -* test_threading.py was updated to skip non-ASCII thread name tests on platforms that do not support them. -``` +_thread.set_name() now retries with an ASCII fallback if pthread_setname_np() rejects UTF-8 names on some POSIX-compliant platforms. From 38a75d3c1d191f6b5668d0c76e8009768723c9b6 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 21:13:38 -0400 Subject: [PATCH 05/41] patch for windows & non-posix compliant platforms --- Modules/_threadmodule.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index a8c696d0e8b39c..6d21b9a9269e59 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2576,7 +2576,9 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ -// Helper to set the thread name using platform-specific APIs + +#ifndef MS_WINDOWS +// Helper to set the thread name using platform-specific APIs (POSIX only) static int _set_thread_name(const char *name) { @@ -2596,18 +2598,18 @@ _set_thread_name(const char *name) #endif return rc; } +#endif // !MS_WINDOWS + static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS + // POSIX and non-Windows platforms #ifdef __sun - // Solaris always uses UTF-8 const char *encoding = "utf-8"; #else - // Encode the thread name to the filesystem encoding using the "replace" - // error handler PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; #endif From 31731b1b03854ef9145c0f14ebfa86aa0dd7c74f Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 21:49:35 -0400 Subject: [PATCH 06/41] patch --- Modules/_threadmodule.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 6d21b9a9269e59..b438c1151aaea5 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -71,7 +71,6 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } - #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -79,6 +78,10 @@ static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL; static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif +#ifndef MS_WINDOWS +static int _set_thread_name(const char *name); +#endif + /*[clinic input] module _thread From 83fe205c822109e5dbacc2bc428c7f77af36b6e2 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 21:56:38 -0400 Subject: [PATCH 07/41] Check if generated files are up to date patch --- Modules/_threadmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index b438c1151aaea5..839a2114627cbe 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -78,7 +78,7 @@ static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL; static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif -#ifndef MS_WINDOWS +#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) static int _set_thread_name(const char *name); #endif From aadf7f3eb172e8adf09339652376b6931654f6f7 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 22:33:08 -0400 Subject: [PATCH 08/41] test --- Modules/_threadmodule.c | 79 ++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 839a2114627cbe..3d9d663f291a09 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -71,6 +71,7 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } + #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -78,10 +79,6 @@ static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL; static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif -#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) -static int _set_thread_name(const char *name); -#endif - /*[clinic input] module _thread @@ -2579,12 +2576,24 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ - -#ifndef MS_WINDOWS -// Helper to set the thread name using platform-specific APIs (POSIX only) static int -_set_thread_name(const char *name) +_set_thread_name_encoded(PyObject *name_obj, const char *encoding, int *perrno) { + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return -1; + } +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); + return -1; + } + Py_SETREF(name_encoded, truncated); + } +#endif + const char *name = PyBytes_AS_STRING(name_encoded); int rc; #ifdef __APPLE__ rc = pthread_setname_np(name); @@ -2599,66 +2608,32 @@ _set_thread_name(const char *name) rc = 0; /* pthread_set_name_np() returns void */ pthread_set_name_np(thread, name); #endif + Py_DECREF(name_encoded); + if (perrno) { + *perrno = rc; + } return rc; } -#endif // !MS_WINDOWS - static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS - // POSIX and non-Windows platforms #ifdef __sun const char *encoding = "utf-8"; #else PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; #endif - PyObject *name_encoded; - int rc; - - name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return NULL; - } -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); - return NULL; - } - Py_SETREF(name_encoded, truncated); - } -#endif - const char *name = PyBytes_AS_STRING(name_encoded); - rc = _set_thread_name(name); - Py_DECREF(name_encoded); - - // Fallback: If EINVAL, try ASCII encoding with "replace" + int rc = _set_thread_name_encoded(name_obj, encoding, NULL); if (rc == EINVAL) { - name_encoded = PyUnicode_AsEncodedString(name_obj, "ascii", "replace"); - if (name_encoded == NULL) { - return NULL; - } -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); - return NULL; - } - Py_SETREF(name_encoded, truncated); + int rc2 = _set_thread_name_encoded(name_obj, "ascii", NULL); + if (rc2 != 0) { + errno = rc2; + return PyErr_SetFromErrno(PyExc_OSError); } -#endif - name = PyBytes_AS_STRING(name_encoded); - rc = _set_thread_name(name); - Py_DECREF(name_encoded); - } - - if (rc) { + } else if (rc != 0) { errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } From 612a0a48f4280594eae15d961b927fa5ff17bd70 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 22:46:26 -0400 Subject: [PATCH 09/41] test2 --- Modules/_threadmodule.c | 92 +++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 3d9d663f291a09..d3ed6002ee977d 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -71,7 +71,6 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } - #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -79,6 +78,10 @@ static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL; static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif +#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) +static int _set_thread_name(const char *name); +#endif + /*[clinic input] module _thread @@ -2576,24 +2579,12 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ + +#ifndef MS_WINDOWS +// Helper to set the thread name using platform-specific APIs (POSIX only) static int -_set_thread_name_encoded(PyObject *name_obj, const char *encoding, int *perrno) +_set_thread_name(const char *name) { - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return -1; - } -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); - return -1; - } - Py_SETREF(name_encoded, truncated); - } -#endif - const char *name = PyBytes_AS_STRING(name_encoded); int rc; #ifdef __APPLE__ rc = pthread_setname_np(name); @@ -2608,32 +2599,66 @@ _set_thread_name_encoded(PyObject *name_obj, const char *encoding, int *perrno) rc = 0; /* pthread_set_name_np() returns void */ pthread_set_name_np(thread, name); #endif - Py_DECREF(name_encoded); - if (perrno) { - *perrno = rc; - } return rc; } +#endif // !MS_WINDOWS + static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS + // POSIX and non-Windows platforms #ifdef __sun const char *encoding = "utf-8"; #else PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; #endif - int rc = _set_thread_name_encoded(name_obj, encoding, NULL); + PyObject *name_encoded; + int rc; + + name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return NULL; + } +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); + return NULL; + } + Py_SETREF(name_encoded, truncated); + } +#endif + const char *name = PyBytes_AS_STRING(name_encoded); + rc = _set_thread_name(name); + Py_DECREF(name_encoded); + + // Fallback: If EINVAL, try ASCII encoding with "replace" if (rc == EINVAL) { - int rc2 = _set_thread_name_encoded(name_obj, "ascii", NULL); - if (rc2 != 0) { - errno = rc2; - return PyErr_SetFromErrno(PyExc_OSError); + name_encoded = PyUnicode_AsEncodedString(name_obj, "ascii", "replace"); + if (name_encoded == NULL) { + return NULL; } - } else if (rc != 0) { +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); + return NULL; + } + Py_SETREF(name_encoded, truncated); + } +#endif + name = PyBytes_AS_STRING(name_encoded); + rc = _set_thread_name(name); + Py_DECREF(name_encoded); + } + + if (rc) { errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } @@ -2670,6 +2695,19 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) } #endif // HAVE_PTHREAD_SETNAME_NP || HAVE_PTHREAD_SET_NAME_NP || MS_WINDOWS +/* Fallback no-op implementation for builds that didn't compile the + * platform-specific _set_thread_name. This prevents undefined-reference + * linker errors on CI images that don't compile the native helper. + */ +#if !defined(HAVE_PTHREAD_SETNAME_NP) && !defined(HAVE_PTHREAD_SET_NAME_NP) && !defined(MS_WINDOWS) +static int +_set_thread_name(const char *name) +{ + /* name is unused for the no-op fallback */ + (void)name; + return 0; /* indicate success (no-op) */ +} +#endif static PyMethodDef thread_methods[] = { {"start_new_thread", thread_PyThread_start_new_thread, From 1fa51f83668cc9d4d0e8a5802fccf3f949940465 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 22:59:26 -0400 Subject: [PATCH 10/41] test3 --- Modules/_threadmodule.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index d3ed6002ee977d..e2685fef3dc3ec 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -79,7 +79,7 @@ static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif #if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) -static int _set_thread_name(const char *name); +int _set_thread_name(const char *name); #endif @@ -2582,7 +2582,7 @@ Set the name of the current thread. #ifndef MS_WINDOWS // Helper to set the thread name using platform-specific APIs (POSIX only) -static int +int _set_thread_name(const char *name) { int rc; @@ -2709,6 +2709,25 @@ _set_thread_name(const char *name) } #endif +/* Weak global fallback when the platform-specific helper isn't compiled. + * On toolchains that support weak symbols (GCC/Clang), mark it weak so a + * real strong implementation will override it. */ +#if !defined(HAVE_PTHREAD_SETNAME_NP) && !defined(HAVE_PTHREAD_SET_NAME_NP) && !defined(MS_WINDOWS) + +#if defined(__GNUC__) || defined(__clang__) +int __attribute__((weak)) +_set_thread_name(const char *name) +#else +int +_set_thread_name(const char *name) +#endif +{ + (void)name; + return 0; +} +#endif + + static PyMethodDef thread_methods[] = { {"start_new_thread", thread_PyThread_start_new_thread, METH_VARARGS, start_new_thread_doc}, From d24d0bbdbb7b77b4a916962f022fb55da551991f Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Thu, 21 Aug 2025 23:12:46 -0400 Subject: [PATCH 11/41] attempt fix --- Modules/_threadmodule.c | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index e2685fef3dc3ec..26f96210a48001 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -79,7 +79,12 @@ static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif #if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) -int _set_thread_name(const char *name); +static int _set_thread_name(const char *name); +#endif + +// Fallback: Provides a no-op implementation if neither pthread naming API is available. This avoids linker errors and provides a portable stub. +#if !defined(HAVE_PTHREAD_SETNAME_NP) && !defined(HAVE_PTHREAD_SET_NAME_NP) +static int _set_thread_name(const char *name) { return 0; } #endif @@ -2582,7 +2587,7 @@ Set the name of the current thread. #ifndef MS_WINDOWS // Helper to set the thread name using platform-specific APIs (POSIX only) -int +static int _set_thread_name(const char *name) { int rc; @@ -2695,38 +2700,6 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) } #endif // HAVE_PTHREAD_SETNAME_NP || HAVE_PTHREAD_SET_NAME_NP || MS_WINDOWS -/* Fallback no-op implementation for builds that didn't compile the - * platform-specific _set_thread_name. This prevents undefined-reference - * linker errors on CI images that don't compile the native helper. - */ -#if !defined(HAVE_PTHREAD_SETNAME_NP) && !defined(HAVE_PTHREAD_SET_NAME_NP) && !defined(MS_WINDOWS) -static int -_set_thread_name(const char *name) -{ - /* name is unused for the no-op fallback */ - (void)name; - return 0; /* indicate success (no-op) */ -} -#endif - -/* Weak global fallback when the platform-specific helper isn't compiled. - * On toolchains that support weak symbols (GCC/Clang), mark it weak so a - * real strong implementation will override it. */ -#if !defined(HAVE_PTHREAD_SETNAME_NP) && !defined(HAVE_PTHREAD_SET_NAME_NP) && !defined(MS_WINDOWS) - -#if defined(__GNUC__) || defined(__clang__) -int __attribute__((weak)) -_set_thread_name(const char *name) -#else -int -_set_thread_name(const char *name) -#endif -{ - (void)name; - return 0; -} -#endif - static PyMethodDef thread_methods[] = { {"start_new_thread", thread_PyThread_start_new_thread, From d7a47bf21955bc07a2dedd2cc60a36667f2af5ff Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Fri, 22 Aug 2025 13:28:17 -0400 Subject: [PATCH 12/41] test --- Lib/test/test_threading.py | 21 ++--- Modules/_threadmodule.c | 152 ++++++++++++++++++------------------- 2 files changed, 79 insertions(+), 94 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 96a19646eec594..28ea94f9986e2a 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2359,20 +2359,13 @@ def work(): with self.subTest(name=name, expected=expected): work_name = None thread = threading.Thread(target=work, name=name) - try: - thread.start() - thread.join() - # If the name is non-ASCII and the result is empty, skip (platform limitation) - if any(ord(c) > 127 for c in name) and (not work_name or work_name == ""): - self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}") - self.assertEqual(work_name, expected, - f"{len(work_name)=} and {len(expected)=}") - except OSError as exc: - # Accept EINVAL (22) for non-ASCII names on platforms that do not support them - if getattr(exc, 'errno', None) == 22 and any(ord(c) > 127 for c in name): - self.skipTest(f"Platform does not support non-ASCII thread names: {exc}") - else: - raise + thread.start() + thread.join() + # If the name is non-ASCII and the result is empty, skip (platform limitation) + if any(ord(c) > 127 for c in name) and (not work_name or work_name == ""): + self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}") + self.assertEqual(work_name, expected, + f"{len(work_name)=} and {len(expected)=}") @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 26f96210a48001..20998348cb947e 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -17,6 +17,10 @@ #ifdef HAVE_SIGNAL_H # include // SIGINT #endif +#ifdef HAVE_PTHREAD_H +# include +#endif +#include // ThreadError is just an alias to PyExc_RuntimeError #define ThreadError PyExc_RuntimeError @@ -71,6 +75,52 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } +// Helper to set the thread name using platform-specific APIs +static int +set_native_thread_name(const char *name) +{ +#ifdef __APPLE__ + return pthread_setname_np(name); +#elif defined(__NetBSD__) + pthread_t thread = pthread_self(); + return pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + pthread_t thread = pthread_self(); + return pthread_setname_np(thread, name); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_t thread = pthread_self(); + pthread_set_name_np(thread, name); + return 0; /* pthread_set_name_np() returns void */ +#else + return 0; +#endif +} + +// Helper to encode and truncate thread name +static PyObject * +encode_thread_name(PyObject *name_obj, const char *encoding) +{ +#ifdef __sun + // Solaris always uses UTF-8 + encoding = "utf-8"; +#endif + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return NULL; + } +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); + return NULL; + } + Py_SETREF(name_encoded, truncated); + } +#endif + return name_encoded; +} + #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -78,15 +128,6 @@ static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL; static PF_SET_THREAD_DESCRIPTION pSetThreadDescription = NULL; #endif -#if defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) -static int _set_thread_name(const char *name); -#endif - -// Fallback: Provides a no-op implementation if neither pthread naming API is available. This avoids linker errors and provides a portable stub. -#if !defined(HAVE_PTHREAD_SETNAME_NP) && !defined(HAVE_PTHREAD_SET_NAME_NP) -static int _set_thread_name(const char *name) { return 0; } -#endif - /*[clinic input] module _thread @@ -2584,100 +2625,52 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ - -#ifndef MS_WINDOWS -// Helper to set the thread name using platform-specific APIs (POSIX only) -static int -_set_thread_name(const char *name) -{ - int rc; -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ - pthread_t thread = pthread_self(); - rc = 0; /* pthread_set_name_np() returns void */ - pthread_set_name_np(thread, name); -#endif - return rc; -} -#endif // !MS_WINDOWS - - static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS - // POSIX and non-Windows platforms -#ifdef __sun - const char *encoding = "utf-8"; -#else PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; -#endif - PyObject *name_encoded; - int rc; - - name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + PyObject *name_encoded = encode_thread_name(name_obj, encoding); if (name_encoded == NULL) { return NULL; } -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); - return NULL; - } - Py_SETREF(name_encoded, truncated); - } -#endif const char *name = PyBytes_AS_STRING(name_encoded); - rc = _set_thread_name(name); - Py_DECREF(name_encoded); - - // Fallback: If EINVAL, try ASCII encoding with "replace" - if (rc == EINVAL) { - name_encoded = PyUnicode_AsEncodedString(name_obj, "ascii", "replace"); - if (name_encoded == NULL) { - return NULL; - } -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); + int rc = set_native_thread_name(name); + if (rc) { + int err = rc; + Py_DECREF(name_encoded); + if (err == EINVAL && strcmp(encoding, "ascii") != 0) { + // Retry with ASCII encoding and 'replace' if not already ASCII + name_encoded = encode_thread_name(name_obj, "ascii"); + if (name_encoded == NULL) { return NULL; } - Py_SETREF(name_encoded, truncated); + name = PyBytes_AS_STRING(name_encoded); + rc = set_native_thread_name(name); + if (rc) { + err = rc; + Py_DECREF(name_encoded); + errno = err; + return PyErr_SetFromErrno(PyExc_OSError); + } + Py_DECREF(name_encoded); + Py_RETURN_NONE; } -#endif - name = PyBytes_AS_STRING(name_encoded); - rc = _set_thread_name(name); - Py_DECREF(name_encoded); - } - - if (rc) { - errno = rc; + errno = err; return PyErr_SetFromErrno(PyExc_OSError); } + Py_DECREF(name_encoded); Py_RETURN_NONE; #else // Windows implementation assert(pSetThreadDescription != NULL); - Py_ssize_t len; wchar_t *name = PyUnicode_AsWideCharString(name_obj, &len); if (name == NULL) { return NULL; } - if (len > _PYTHREAD_NAME_MAXLEN) { // Truncate the name Py_UCS4 ch = name[_PYTHREAD_NAME_MAXLEN-1]; @@ -2688,7 +2681,6 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) name[_PYTHREAD_NAME_MAXLEN] = 0; } } - HRESULT hr = pSetThreadDescription(GetCurrentThread(), name); PyMem_Free(name); if (FAILED(hr)) { @@ -2932,4 +2924,4 @@ PyMODINIT_FUNC PyInit__thread(void) { return PyModuleDef_Init(&thread_module); -} +} \ No newline at end of file From 1970a00784479d79ddca3fe4710c4c7b18e387a6 Mon Sep 17 00:00:00 2001 From: jadonduff Date: Fri, 22 Aug 2025 14:45:56 -0400 Subject: [PATCH 13/41] fix stray newline Co-authored-by: Peter Bierma --- Lib/test/test_threading.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 28ea94f9986e2a..e0a1d8f41295e6 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2241,7 +2241,6 @@ def __init__(self, a, *, b) -> None: with warnings.catch_warnings(record=True) as warnings_log: CustomRLock(1, b=2) - self.assertEqual(warnings_log, []) class EventTests(lock_tests.EventTests): From 241e097ed59069e7c0ae1140d40c21ede6c854c9 Mon Sep 17 00:00:00 2001 From: jadonduff Date: Fri, 22 Aug 2025 16:58:54 -0400 Subject: [PATCH 14/41] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/_threadmodule.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 20998348cb947e..c03b00c735339b 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -79,21 +79,21 @@ get_thread_state_by_cls(PyTypeObject *cls) static int set_native_thread_name(const char *name) { + int rc; #ifdef __APPLE__ - return pthread_setname_np(name); + rc = pthread_setname_np(name); #elif defined(__NetBSD__) pthread_t thread = pthread_self(); - return pthread_setname_np(thread, "%s", (void *)name); + rc = pthread_setname_np(thread, "%s", (void *)name); #elif defined(HAVE_PTHREAD_SETNAME_NP) pthread_t thread = pthread_self(); - return pthread_setname_np(thread, name); + rc = pthread_setname_np(thread, name); #elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_t thread = pthread_self(); pthread_set_name_np(thread, name); - return 0; /* pthread_set_name_np() returns void */ -#else - return 0; + rc = 0; /* pthread_set_name_np() returns void */ #endif + return rc; } // Helper to encode and truncate thread name @@ -111,11 +111,8 @@ encode_thread_name(PyObject *name_obj, const char *encoding) #ifdef _PYTHREAD_NAME_MAXLEN if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); - return NULL; - } - Py_SETREF(name_encoded, truncated); + Py_DECREF(name_encoded); + return truncated } #endif return name_encoded; From 66c058b4cf5cf0039496fe95e7bca39e42c4c6c0 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Fri, 22 Aug 2025 17:16:46 -0400 Subject: [PATCH 15/41] fixes --- Lib/test/test_threading.py | 3 ++- Modules/_threadmodule.c | 39 +++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index e0a1d8f41295e6..81ea3028e3e2df 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2241,6 +2241,7 @@ def __init__(self, a, *, b) -> None: with warnings.catch_warnings(record=True) as warnings_log: CustomRLock(1, b=2) + self.assertEqual(warnings_log, []) class EventTests(lock_tests.EventTests): @@ -2361,7 +2362,7 @@ def work(): thread.start() thread.join() # If the name is non-ASCII and the result is empty, skip (platform limitation) - if any(ord(c) > 127 for c in name) and (not work_name or work_name == ""): + if not name.isascii() and not work_name: self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}") self.assertEqual(work_name, expected, f"{len(work_name)=} and {len(expected)=}") diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index c03b00c735339b..d0503beefa51d3 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -112,12 +112,28 @@ encode_thread_name(PyObject *name_obj, const char *encoding) if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); Py_DECREF(name_encoded); + return truncated; + Py_DECREF(name_encoded); return truncated } #endif return name_encoded; } +// Helper to encode, set, and cleanup thread name in one step +static int +set_thread_name_with_encoding(PyObject *name_obj, const char *encoding) +{ + PyObject *name_encoded = encode_thread_name(name_obj, encoding); + if (name_encoded == NULL) { + return -1; // error, exception set + } + const char *name = PyBytes_AS_STRING(name_encoded); + int rc = set_native_thread_name(name); + Py_DECREF(name_encoded); + return rc; +} + #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -2629,40 +2645,25 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) #ifndef MS_WINDOWS PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; - PyObject *name_encoded = encode_thread_name(name_obj, encoding); - if (name_encoded == NULL) { - return NULL; - } - const char *name = PyBytes_AS_STRING(name_encoded); - int rc = set_native_thread_name(name); + int rc = set_thread_name_with_encoding(name_obj, encoding); if (rc) { int err = rc; - Py_DECREF(name_encoded); if (err == EINVAL && strcmp(encoding, "ascii") != 0) { - // Retry with ASCII encoding and 'replace' if not already ASCII - name_encoded = encode_thread_name(name_obj, "ascii"); - if (name_encoded == NULL) { - return NULL; - } - name = PyBytes_AS_STRING(name_encoded); - rc = set_native_thread_name(name); + rc = set_thread_name_with_encoding(name_obj, "ascii"); if (rc) { - err = rc; - Py_DECREF(name_encoded); - errno = err; + errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } - Py_DECREF(name_encoded); Py_RETURN_NONE; } errno = err; return PyErr_SetFromErrno(PyExc_OSError); } - Py_DECREF(name_encoded); Py_RETURN_NONE; #else // Windows implementation assert(pSetThreadDescription != NULL); + Py_ssize_t len; wchar_t *name = PyUnicode_AsWideCharString(name_obj, &len); if (name == NULL) { From f8174121f3b7e664f8415a7deafae5b5db7c1533 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Fri, 22 Aug 2025 17:24:09 -0400 Subject: [PATCH 16/41] apply fixes (built) --- Modules/_threadmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index d0503beefa51d3..440a1a10126c1f 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -114,7 +114,7 @@ encode_thread_name(PyObject *name_obj, const char *encoding) Py_DECREF(name_encoded); return truncated; Py_DECREF(name_encoded); - return truncated + return truncated; } #endif return name_encoded; From 33494647645ecbf4b31ae2b792cf24c7561432ae Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 15:33:43 -0400 Subject: [PATCH 17/41] remove dead code, fix err var, revert newline, fix comment --- Lib/test/test_threading.py | 1 - Modules/_threadmodule.c | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 81ea3028e3e2df..323144d7fee1d6 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2361,7 +2361,6 @@ def work(): thread = threading.Thread(target=work, name=name) thread.start() thread.join() - # If the name is non-ASCII and the result is empty, skip (platform limitation) if not name.isascii() and not work_name: self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}") self.assertEqual(work_name, expected, diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 440a1a10126c1f..5467a8083b9660 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -113,8 +113,6 @@ encode_thread_name(PyObject *name_obj, const char *encoding) PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); Py_DECREF(name_encoded); return truncated; - Py_DECREF(name_encoded); - return truncated; } #endif return name_encoded; @@ -2922,4 +2920,4 @@ PyMODINIT_FUNC PyInit__thread(void) { return PyModuleDef_Init(&thread_module); -} \ No newline at end of file +} From ac3d3ddb515f0b64336c14755cd8bc814abb6b5b Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 15:38:00 -0400 Subject: [PATCH 18/41] redundant err var replaced --- Modules/_threadmodule.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 5467a8083b9660..b4bbe91e02c0ee 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2645,8 +2645,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) const char *encoding = interp->unicode.fs_codec.encoding; int rc = set_thread_name_with_encoding(name_obj, encoding); if (rc) { - int err = rc; - if (err == EINVAL && strcmp(encoding, "ascii") != 0) { + if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { rc = set_thread_name_with_encoding(name_obj, "ascii"); if (rc) { errno = rc; @@ -2654,7 +2653,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) } Py_RETURN_NONE; } - errno = err; + errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } Py_RETURN_NONE; From 1ab9ecd89c90e32460d50d0ef65334d51d486f12 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 16:18:38 -0400 Subject: [PATCH 19/41] replaced functions with inline code, moved into set_thread_name_with_encoding --- Modules/_threadmodule.c | 108 ++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index b4bbe91e02c0ee..30e86c6a74ff91 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -75,61 +75,93 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } -// Helper to set the thread name using platform-specific APIs static int -set_native_thread_name(const char *name) +set_thread_name_with_encoding(PyObject *name_obj, const char *encoding) { - int rc; + PyObject *name_encoded = NULL; + +#ifndef MS_WINDOWS + /* Determine encoding to use. If encoding arg is NULL, use FS codec. */ + const char *enc = encoding; +#ifdef __sun + enc = "utf-8"; /* Solaris always uses UTF-8 */ +#else + if (enc == NULL) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + enc = interp->unicode.fs_codec.encoding; + } +#endif + + name_encoded = PyUnicode_AsEncodedString(name_obj, enc, "replace"); + if (name_encoded == NULL) { + return -1; /* Python exception set */ + } + +#ifdef _PYTHREAD_NAME_MAXLEN + /* Truncate to _PYTHREAD_NAME_MAXLEN bytes if needed */ + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize( + PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); + return -1; /* Python exception set */ + } + Py_SETREF(name_encoded, truncated); + } +#endif + + const char *name = PyBytes_AS_STRING(name_encoded); + #ifdef __APPLE__ - rc = pthread_setname_np(name); + int rc = pthread_setname_np(name); #elif defined(__NetBSD__) pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); + int rc = pthread_setname_np(thread, "%s", (void *)name); #elif defined(HAVE_PTHREAD_SETNAME_NP) pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) + int rc = pthread_setname_np(thread, name); +#else + /* pthread_set_name_np() (void) on some platforms */ pthread_t thread = pthread_self(); + int rc = 0; pthread_set_name_np(thread, name); - rc = 0; /* pthread_set_name_np() returns void */ #endif - return rc; -} -// Helper to encode and truncate thread name -static PyObject * -encode_thread_name(PyObject *name_obj, const char *encoding) -{ -#ifdef __sun - // Solaris always uses UTF-8 - encoding = "utf-8"; -#endif - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return NULL; + Py_DECREF(name_encoded); + return rc; /* 0 on success, errno-style >0 on error */ +#else + /* Windows: convert to wide string and call SetThreadDescription */ + assert(pSetThreadDescription != NULL); + Py_ssize_t len; + wchar_t *wname = PyUnicode_AsWideCharString(name_obj, &len); + if (wname == NULL) { + return -1; /* Python exception set */ } + + /* Truncate if necessary (len is number of wchar_t characters) */ #ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - Py_DECREF(name_encoded); - return truncated; + if (len > _PYTHREAD_NAME_MAXLEN) { + /* Ensure we null-terminate safely. Use maxlen as max characters allowed. */ + /* If the char at max-1 is a high surrogate, avoid chopping the surrogate pair. */ + Py_UCS4 ch = (Py_UCS4)wname[_PYTHREAD_NAME_MAXLEN - 1]; + if (Py_UNICODE_IS_HIGH_SURROGATE(ch) && _PYTHREAD_NAME_MAXLEN >= 2) { + wname[_PYTHREAD_NAME_MAXLEN - 1] = L'\0'; + } else { + wname[_PYTHREAD_NAME_MAXLEN] = L'\0'; + } } #endif - return name_encoded; -} -// Helper to encode, set, and cleanup thread name in one step -static int -set_thread_name_with_encoding(PyObject *name_obj, const char *encoding) -{ - PyObject *name_encoded = encode_thread_name(name_obj, encoding); - if (name_encoded == NULL) { - return -1; // error, exception set + HRESULT hr = pSetThreadDescription(GetCurrentThread(), wname); + PyMem_Free(wname); + if (FAILED(hr)) { + /* Convert to a Python exception and return -1 so caller propagates it */ + PyErr_SetFromWindowsErr((int)hr); + return -1; } - const char *name = PyBytes_AS_STRING(name_encoded); - int rc = set_native_thread_name(name); - Py_DECREF(name_encoded); - return rc; + return 0; +#endif } #ifdef MS_WINDOWS From d9ba9e8b5abda580b8631f56b1772cf6553c67f0 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 16:30:12 -0400 Subject: [PATCH 20/41] revert --- Modules/_threadmodule.c | 108 ++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 70 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 30e86c6a74ff91..b4bbe91e02c0ee 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -75,93 +75,61 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } +// Helper to set the thread name using platform-specific APIs static int -set_thread_name_with_encoding(PyObject *name_obj, const char *encoding) +set_native_thread_name(const char *name) { - PyObject *name_encoded = NULL; - -#ifndef MS_WINDOWS - /* Determine encoding to use. If encoding arg is NULL, use FS codec. */ - const char *enc = encoding; -#ifdef __sun - enc = "utf-8"; /* Solaris always uses UTF-8 */ -#else - if (enc == NULL) { - PyInterpreterState *interp = _PyInterpreterState_GET(); - enc = interp->unicode.fs_codec.encoding; - } -#endif - - name_encoded = PyUnicode_AsEncodedString(name_obj, enc, "replace"); - if (name_encoded == NULL) { - return -1; /* Python exception set */ - } - -#ifdef _PYTHREAD_NAME_MAXLEN - /* Truncate to _PYTHREAD_NAME_MAXLEN bytes if needed */ - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize( - PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); - if (truncated == NULL) { - Py_DECREF(name_encoded); - return -1; /* Python exception set */ - } - Py_SETREF(name_encoded, truncated); - } -#endif - - const char *name = PyBytes_AS_STRING(name_encoded); - + int rc; #ifdef __APPLE__ - int rc = pthread_setname_np(name); + rc = pthread_setname_np(name); #elif defined(__NetBSD__) pthread_t thread = pthread_self(); - int rc = pthread_setname_np(thread, "%s", (void *)name); + rc = pthread_setname_np(thread, "%s", (void *)name); #elif defined(HAVE_PTHREAD_SETNAME_NP) pthread_t thread = pthread_self(); - int rc = pthread_setname_np(thread, name); -#else - /* pthread_set_name_np() (void) on some platforms */ + rc = pthread_setname_np(thread, name); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_t thread = pthread_self(); - int rc = 0; pthread_set_name_np(thread, name); + rc = 0; /* pthread_set_name_np() returns void */ #endif + return rc; +} - Py_DECREF(name_encoded); - return rc; /* 0 on success, errno-style >0 on error */ -#else - /* Windows: convert to wide string and call SetThreadDescription */ - assert(pSetThreadDescription != NULL); - Py_ssize_t len; - wchar_t *wname = PyUnicode_AsWideCharString(name_obj, &len); - if (wname == NULL) { - return -1; /* Python exception set */ +// Helper to encode and truncate thread name +static PyObject * +encode_thread_name(PyObject *name_obj, const char *encoding) +{ +#ifdef __sun + // Solaris always uses UTF-8 + encoding = "utf-8"; +#endif + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return NULL; } - - /* Truncate if necessary (len is number of wchar_t characters) */ #ifdef _PYTHREAD_NAME_MAXLEN - if (len > _PYTHREAD_NAME_MAXLEN) { - /* Ensure we null-terminate safely. Use maxlen as max characters allowed. */ - /* If the char at max-1 is a high surrogate, avoid chopping the surrogate pair. */ - Py_UCS4 ch = (Py_UCS4)wname[_PYTHREAD_NAME_MAXLEN - 1]; - if (Py_UNICODE_IS_HIGH_SURROGATE(ch) && _PYTHREAD_NAME_MAXLEN >= 2) { - wname[_PYTHREAD_NAME_MAXLEN - 1] = L'\0'; - } else { - wname[_PYTHREAD_NAME_MAXLEN] = L'\0'; - } + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); + Py_DECREF(name_encoded); + return truncated; } #endif + return name_encoded; +} - HRESULT hr = pSetThreadDescription(GetCurrentThread(), wname); - PyMem_Free(wname); - if (FAILED(hr)) { - /* Convert to a Python exception and return -1 so caller propagates it */ - PyErr_SetFromWindowsErr((int)hr); - return -1; +// Helper to encode, set, and cleanup thread name in one step +static int +set_thread_name_with_encoding(PyObject *name_obj, const char *encoding) +{ + PyObject *name_encoded = encode_thread_name(name_obj, encoding); + if (name_encoded == NULL) { + return -1; // error, exception set } - return 0; -#endif + const char *name = PyBytes_AS_STRING(name_encoded); + int rc = set_native_thread_name(name); + Py_DECREF(name_encoded); + return rc; } #ifdef MS_WINDOWS From d7025f538511b9d7afd1bf870d7f197a24f8c898 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 16:55:02 -0400 Subject: [PATCH 21/41] inline test --- Modules/_threadmodule.c | 149 ++++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 66 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index b4bbe91e02c0ee..2760211b5bdbb2 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -75,63 +75,6 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } -// Helper to set the thread name using platform-specific APIs -static int -set_native_thread_name(const char *name) -{ - int rc; -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_t thread = pthread_self(); - pthread_set_name_np(thread, name); - rc = 0; /* pthread_set_name_np() returns void */ -#endif - return rc; -} - -// Helper to encode and truncate thread name -static PyObject * -encode_thread_name(PyObject *name_obj, const char *encoding) -{ -#ifdef __sun - // Solaris always uses UTF-8 - encoding = "utf-8"; -#endif - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return NULL; - } -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), _PYTHREAD_NAME_MAXLEN); - Py_DECREF(name_encoded); - return truncated; - } -#endif - return name_encoded; -} - -// Helper to encode, set, and cleanup thread name in one step -static int -set_thread_name_with_encoding(PyObject *name_obj, const char *encoding) -{ - PyObject *name_encoded = encode_thread_name(name_obj, encoding); - if (name_encoded == NULL) { - return -1; // error, exception set - } - const char *name = PyBytes_AS_STRING(name_encoded); - int rc = set_native_thread_name(name); - Py_DECREF(name_encoded); - return rc; -} - #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -2636,38 +2579,110 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ +/* Helper to set the thread name using platform-specific APIs */ +static int +set_native_thread_name(const char *name) +{ + int rc = 0; +#ifdef __APPLE__ + rc = pthread_setname_np(name); +#elif defined(__NetBSD__) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, name); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_t thread = pthread_self(); + pthread_set_name_np(thread, name); + rc = 0; /* pthread_set_name_np() returns void */ +#endif + return rc; +} + +/* Helper to encode and truncate thread name; returns new reference or NULL */ +static PyObject * +encode_thread_name(PyObject *name_obj, const char *encoding) +{ +#ifdef __sun + /* Solaris always uses UTF-8 */ + encoding = "utf-8"; +#endif + + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return NULL; + } + +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + Py_DECREF(name_encoded); + if (truncated == NULL) { + return NULL; + } + return truncated; + } +#endif + + return name_encoded; +} + +/* Implementation of _thread.set_name */ static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS +#ifdef __sun + const char *encoding = "utf-8"; +#else PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; - int rc = set_thread_name_with_encoding(name_obj, encoding); +#endif + + /* First attempt using filesystem (or platform) encoding */ + PyObject *name_encoded = encode_thread_name(name_obj, encoding); + if (name_encoded == NULL) { + return NULL; + } + const char *name = PyBytes_AS_STRING(name_encoded); + int rc = set_native_thread_name(name); + Py_DECREF(name_encoded); + + /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ if (rc) { if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { - rc = set_thread_name_with_encoding(name_obj, "ascii"); - if (rc) { - errno = rc; - return PyErr_SetFromErrno(PyExc_OSError); + PyObject *name_encoded2 = encode_thread_name(name_obj, "ascii"); + if (name_encoded2 == NULL) { + return NULL; } - Py_RETURN_NONE; + const char *name2 = PyBytes_AS_STRING(name_encoded2); + rc = set_native_thread_name(name2); + Py_DECREF(name_encoded2); + + if (rc == 0) { + Py_RETURN_NONE; + } + /* if still failing, fall through to raise errno below */ } errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } + Py_RETURN_NONE; #else - // Windows implementation assert(pSetThreadDescription != NULL); - + Py_ssize_t len; wchar_t *name = PyUnicode_AsWideCharString(name_obj, &len); if (name == NULL) { return NULL; } + if (len > _PYTHREAD_NAME_MAXLEN) { - // Truncate the name + /* Truncate the name */ Py_UCS4 ch = name[_PYTHREAD_NAME_MAXLEN-1]; if (Py_UNICODE_IS_HIGH_SURROGATE(ch)) { name[_PYTHREAD_NAME_MAXLEN-1] = 0; @@ -2676,6 +2691,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) name[_PYTHREAD_NAME_MAXLEN] = 0; } } + HRESULT hr = pSetThreadDescription(GetCurrentThread(), name); PyMem_Free(name); if (FAILED(hr)) { @@ -2685,6 +2701,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) Py_RETURN_NONE; #endif } + #endif // HAVE_PTHREAD_SETNAME_NP || HAVE_PTHREAD_SET_NAME_NP || MS_WINDOWS From 3689044dd3e70e3eb01ca239c566def2ecec39ff Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 17:13:21 -0400 Subject: [PATCH 22/41] test fix for "Check if generated files are up to date" --- Modules/_threadmodule.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 2760211b5bdbb2..eea4bf1e91b310 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -75,6 +75,10 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } +// Declarations for thread name handling +static int set_native_thread_name(const char *name); +static PyObject *encode_thread_name(PyObject *name_obj, const char *encoding); + #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); From 246f88069c28aaf848b8069025c951cdcd25da40 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 17:19:17 -0400 Subject: [PATCH 23/41] fix linker error --- Modules/_threadmodule.c | 102 +++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index eea4bf1e91b310..f7b77bca76e40f 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -75,9 +75,55 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } -// Declarations for thread name handling -static int set_native_thread_name(const char *name); -static PyObject *encode_thread_name(PyObject *name_obj, const char *encoding); +/* Helper to set the thread name using platform-specific APIs */ +static int +set_native_thread_name(const char *name) +{ + int rc = 0; +#ifdef __APPLE__ + rc = pthread_setname_np(name); +#elif defined(__NetBSD__) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, name); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_t thread = pthread_self(); + pthread_set_name_np(thread, name); + rc = 0; /* pthread_set_name_np() returns void */ +#endif + return rc; +} + +/* Helper to encode and truncate thread name; returns new reference or NULL */ +static PyObject * +encode_thread_name(PyObject *name_obj, const char *encoding) +{ +#ifdef __sun + /* Solaris always uses UTF-8 */ + encoding = "utf-8"; +#endif + + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return NULL; + } + +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + Py_DECREF(name_encoded); + if (truncated == NULL) { + return NULL; + } + return truncated; + } +#endif + + return name_encoded; +} #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); @@ -2583,56 +2629,6 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ -/* Helper to set the thread name using platform-specific APIs */ -static int -set_native_thread_name(const char *name) -{ - int rc = 0; -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_t thread = pthread_self(); - pthread_set_name_np(thread, name); - rc = 0; /* pthread_set_name_np() returns void */ -#endif - return rc; -} - -/* Helper to encode and truncate thread name; returns new reference or NULL */ -static PyObject * -encode_thread_name(PyObject *name_obj, const char *encoding) -{ -#ifdef __sun - /* Solaris always uses UTF-8 */ - encoding = "utf-8"; -#endif - - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return NULL; - } - -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); - Py_DECREF(name_encoded); - if (truncated == NULL) { - return NULL; - } - return truncated; - } -#endif - - return name_encoded; -} - /* Implementation of _thread.set_name */ static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) From 407b1e9192f2c30ea7b8ed56c479fb12384c728c Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sat, 23 Aug 2025 17:37:43 -0400 Subject: [PATCH 24/41] make regen --- Modules/_threadmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index f7b77bca76e40f..cf85e7d4330f0f 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2629,7 +2629,6 @@ _thread.set_name Set the name of the current thread. [clinic start generated code]*/ -/* Implementation of _thread.set_name */ static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) /*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ From dc43fc4538ccf3833504966f47f5b47c98a40163 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sun, 24 Aug 2025 01:45:58 -0400 Subject: [PATCH 25/41] move functions and add inline for functions used once --- Modules/_threadmodule.c | 101 ++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index cf85e7d4330f0f..051fdd2f8f3718 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -75,56 +75,6 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } -/* Helper to set the thread name using platform-specific APIs */ -static int -set_native_thread_name(const char *name) -{ - int rc = 0; -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_t thread = pthread_self(); - pthread_set_name_np(thread, name); - rc = 0; /* pthread_set_name_np() returns void */ -#endif - return rc; -} - -/* Helper to encode and truncate thread name; returns new reference or NULL */ -static PyObject * -encode_thread_name(PyObject *name_obj, const char *encoding) -{ -#ifdef __sun - /* Solaris always uses UTF-8 */ - encoding = "utf-8"; -#endif - - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return NULL; - } - -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); - Py_DECREF(name_encoded); - if (truncated == NULL) { - return NULL; - } - return truncated; - } -#endif - - return name_encoded; -} - #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -2570,6 +2520,56 @@ of the main interpreter."); # include #endif +/* Helper to set the thread name using platform-specific APIs */ +static int +set_native_thread_name(const char *name) +{ + int rc = 0; +#ifdef __APPLE__ + rc = pthread_setname_np(name); +#elif defined(__NetBSD__) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + pthread_t thread = pthread_self(); + rc = pthread_setname_np(thread, name); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_t thread = pthread_self(); + pthread_set_name_np(thread, name); + rc = 0; /* pthread_set_name_np() returns void */ +#endif + return rc; +} + +/* Helper to encode and truncate thread name; returns new reference or NULL */ +static PyObject * +encode_thread_name(PyObject *name_obj, const char *encoding) +{ +#ifdef __sun + /* Solaris always uses UTF-8 */ + encoding = "utf-8"; +#endif + + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + return NULL; + } + +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + Py_DECREF(name_encoded); + if (truncated == NULL) { + return NULL; + } + return truncated; + } +#endif + + return name_encoded; +} + #if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS) /*[clinic input] _thread._get_name @@ -2635,6 +2635,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) { #ifndef MS_WINDOWS #ifdef __sun + // Solaris always uses UTF-8 const char *encoding = "utf-8"; #else PyInterpreterState *interp = _PyInterpreterState_GET(); From 7aa256cd7e6958761ca3ca2a601a5a398c202459 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sun, 24 Aug 2025 02:06:48 -0400 Subject: [PATCH 26/41] remove duplicated __sun enforcing utf-8 from encoder --- Modules/_threadmodule.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 051fdd2f8f3718..7ad5c40a00abc1 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -21,6 +21,7 @@ # include #endif #include +#include // ThreadError is just an alias to PyExc_RuntimeError #define ThreadError PyExc_RuntimeError @@ -2541,15 +2542,10 @@ set_native_thread_name(const char *name) return rc; } -/* Helper to encode and truncate thread name; returns new reference or NULL */ +/* Helper to encode and truncate thread name */ static PyObject * encode_thread_name(PyObject *name_obj, const char *encoding) { -#ifdef __sun - /* Solaris always uses UTF-8 */ - encoding = "utf-8"; -#endif - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); if (name_encoded == NULL) { return NULL; From b4934a262fa66963106e70bccdb654cf9eb03d92 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sun, 24 Aug 2025 12:39:58 -0400 Subject: [PATCH 27/41] consolidate functions, remove unrelated changes --- Modules/_threadmodule.c | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 7ad5c40a00abc1..a3501b3191a711 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -76,6 +76,7 @@ get_thread_state_by_cls(PyTypeObject *cls) return get_thread_state(module); } + #ifdef MS_WINDOWS typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*); typedef HRESULT (WINAPI *PF_SET_THREAD_DESCRIPTION)(HANDLE, PCWSTR); @@ -2521,11 +2522,38 @@ of the main interpreter."); # include #endif -/* Helper to set the thread name using platform-specific APIs */ +/* Helper: encode/truncate and call native API to set thread name. + * Return: + * 0 : success + * >0 : errno-style native error code (e.g. EINVAL) + * -1 : Python-level error (an exception has been set) + */ static int -set_native_thread_name(const char *name) +set_encoded_thread_name(PyObject *name_obj, const char *encoding) { + PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { + /* PyUnicode_AsEncodedString set an exception */ + return -1; + } + +#ifdef _PYTHREAD_NAME_MAXLEN + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated = PyBytes_FromStringAndSize( + PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + Py_DECREF(name_encoded); + if (truncated == NULL) { + /* PyBytes_FromStringAndSize set an exception */ + return -1; + } + name_encoded = truncated; + } +#endif + + const char *name = PyBytes_AS_STRING(name_encoded); int rc = 0; + #ifdef __APPLE__ rc = pthread_setname_np(name); #elif defined(__NetBSD__) @@ -2537,33 +2565,13 @@ set_native_thread_name(const char *name) #elif defined(HAVE_PTHREAD_SET_NAME_NP) pthread_t thread = pthread_self(); pthread_set_name_np(thread, name); - rc = 0; /* pthread_set_name_np() returns void */ -#endif - return rc; -} - -/* Helper to encode and truncate thread name */ -static PyObject * -encode_thread_name(PyObject *name_obj, const char *encoding) -{ - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - return NULL; - } - -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); - Py_DECREF(name_encoded); - if (truncated == NULL) { - return NULL; - } - return truncated; - } + rc = 0; /* that API returns void */ +#else + rc = 0; /* no-op if platform unsupported */ #endif - return name_encoded; + Py_DECREF(name_encoded); + return rc; } #if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS) @@ -2627,48 +2635,51 @@ Set the name of the current thread. static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) -/*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS #ifdef __sun // Solaris always uses UTF-8 const char *encoding = "utf-8"; #else + // Encode the thread name to the filesystem encoding using the "replace" + // error handler PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; #endif - /* First attempt using filesystem (or platform) encoding */ - PyObject *name_encoded = encode_thread_name(name_obj, encoding); - if (name_encoded == NULL) { + int rc = set_encoded_thread_name(name_obj, encoding); + if (rc == -1) { + /* Confirm a Python exception was set by the helper. + If not, convert to a runtime error (defensive). */ + if (PyErr_Occurred()) { + return NULL; + } + PyErr_SetString(PyExc_RuntimeError, "internal error in set_encoded_thread_name"); return NULL; } - const char *name = PyBytes_AS_STRING(name_encoded); - int rc = set_native_thread_name(name); - Py_DECREF(name_encoded); - /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ if (rc) { + /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { - PyObject *name_encoded2 = encode_thread_name(name_obj, "ascii"); - if (name_encoded2 == NULL) { + rc = set_encoded_thread_name(name_obj, "ascii"); + if (rc == -1) { + if (PyErr_Occurred()) { + return NULL; + } + PyErr_SetString(PyExc_RuntimeError, "internal error in set_encoded_thread_name"); return NULL; } - const char *name2 = PyBytes_AS_STRING(name_encoded2); - rc = set_native_thread_name(name2); - Py_DECREF(name_encoded2); - if (rc == 0) { Py_RETURN_NONE; } - /* if still failing, fall through to raise errno below */ + /* fall through to raise errno below */ } errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } - Py_RETURN_NONE; #else + // Windows implementation assert(pSetThreadDescription != NULL); Py_ssize_t len; @@ -2678,7 +2689,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) } if (len > _PYTHREAD_NAME_MAXLEN) { - /* Truncate the name */ + // Truncate the name Py_UCS4 ch = name[_PYTHREAD_NAME_MAXLEN-1]; if (Py_UNICODE_IS_HIGH_SURROGATE(ch)) { name[_PYTHREAD_NAME_MAXLEN-1] = 0; @@ -2697,7 +2708,6 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) Py_RETURN_NONE; #endif } - #endif // HAVE_PTHREAD_SETNAME_NP || HAVE_PTHREAD_SET_NAME_NP || MS_WINDOWS From ae5ac4be81a25f8a91f1e57bb774fc7ab8ddd279 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sun, 24 Aug 2025 12:58:21 -0400 Subject: [PATCH 28/41] clinic regen fix --- Modules/_threadmodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index a3501b3191a711..2aa7277045903c 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2635,6 +2635,7 @@ Set the name of the current thread. static PyObject * _thread_set_name_impl(PyObject *module, PyObject *name_obj) +/*[clinic end generated code: output=402b0c68e0c0daed input=7e7acd98261be82f]*/ { #ifndef MS_WINDOWS #ifdef __sun From 4fc368d23b1ce86ae05a87d03811525ec2176227 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sun, 24 Aug 2025 15:04:42 -0400 Subject: [PATCH 29/41] fix error/fallback (implement changes) --- Modules/_threadmodule.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 2aa7277045903c..47ffb199a9ab98 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2649,13 +2649,9 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) #endif int rc = set_encoded_thread_name(name_obj, encoding); - if (rc == -1) { - /* Confirm a Python exception was set by the helper. - If not, convert to a runtime error (defensive). */ - if (PyErr_Occurred()) { - return NULL; - } - PyErr_SetString(PyExc_RuntimeError, "internal error in set_encoded_thread_name"); + /* Confirm a Python exception was set by the helper. + If not, convert to a runtime error (defensive). */ + if (rc == -1 && PyErr_Occurred()) { return NULL; } @@ -2663,11 +2659,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { rc = set_encoded_thread_name(name_obj, "ascii"); - if (rc == -1) { - if (PyErr_Occurred()) { - return NULL; - } - PyErr_SetString(PyExc_RuntimeError, "internal error in set_encoded_thread_name"); + if (rc == -1 && PyErr_Occurred()) { return NULL; } if (rc == 0) { From 392aa5a836ea9dcf542a75610dbc505cf4e0a8d0 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Sun, 24 Aug 2025 15:10:56 -0400 Subject: [PATCH 30/41] fix whitespace lint issue in test_threading.py --- Lib/test/test_threading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 323144d7fee1d6..69777f8ddebfa2 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2241,7 +2241,7 @@ def __init__(self, a, *, b) -> None: with warnings.catch_warnings(record=True) as warnings_log: CustomRLock(1, b=2) - + self.assertEqual(warnings_log, []) class EventTests(lock_tests.EventTests): From d5e939954e3a8ef545b2f23a73682a542b7d1c2b Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Mon, 25 Aug 2025 11:50:39 -0400 Subject: [PATCH 31/41] make _threadmodule.c clearer, remove unrelated change, add specific test case for openindiana in test_threading.py --- Lib/test/test_threading.py | 43 +++++++++++++++++++++++++++++++++----- Modules/_threadmodule.c | 19 +++++++---------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 69777f8ddebfa2..64522d7281b771 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2241,7 +2241,6 @@ def __init__(self, a, *, b) -> None: with warnings.catch_warnings(record=True) as warnings_log: CustomRLock(1, b=2) - self.assertEqual(warnings_log, []) class EventTests(lock_tests.EventTests): @@ -2361,10 +2360,44 @@ def work(): thread = threading.Thread(target=work, name=name) thread.start() thread.join() - if not name.isascii() and not work_name: - self.skipTest(f"Platform does not support non-ASCII thread names: got empty name for {name!r}") - self.assertEqual(work_name, expected, - f"{len(work_name)=} and {len(expected)=}") + + # Detect if running on OpenIndiana / illumos + try: + is_illumos = os.uname().version.startswith('illumos') + except AttributeError: + is_illumos = False + + if is_illumos: + # illumos requires ASCII-encoded thread names + if not work_name: + # name didn't get set (set_name may have failed) + self.skipTest( + f"Platform does not support non-ASCII thread names: got empty name for {name!r}" + ) + + work_name_bytes = ( + work_name.encode('ascii', errors='ignore') + if isinstance(work_name, str) + else work_name + ) + expected_bytes = expected.encode('ascii', errors='ignore') + + self.assertEqual( + work_name_bytes, + expected_bytes, + f"{len(work_name)=} and {len(expected)=}" + ) + + elif not name.isascii() and not work_name: + # Platform does not support non-ASCII thread names + self.skipTest( + f"Platform does not support non-ASCII thread names: got empty name for {name!r}" + ) + + else: + # Most platforms + self.assertEqual(work_name, expected, + f"{len(work_name)=} and {len(expected)=}") @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 47ffb199a9ab98..3e33c05104c27a 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2655,18 +2655,15 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) return NULL; } - if (rc) { - /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ - if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { - rc = set_encoded_thread_name(name_obj, "ascii"); - if (rc == -1 && PyErr_Occurred()) { - return NULL; - } - if (rc == 0) { - Py_RETURN_NONE; - } - /* fall through to raise errno below */ + /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ + if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { + rc = set_encoded_thread_name(name_obj, "ascii"); + if (rc == -1 && PyErr_Occurred()) { + return NULL; } + /* fall through to raise errno below */ + } + if (rc) { errno = rc; return PyErr_SetFromErrno(PyExc_OSError); } From 508f24db32dc51596a90ede96c561470123f96af Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Tue, 2 Sep 2025 00:13:57 -0400 Subject: [PATCH 32/41] add test for main and encode ascii --- Lib/test/test_threading.py | 51 ++++++++------------------------------ 1 file changed, 10 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 64522d7281b771..51d041d22f6b50 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2320,7 +2320,8 @@ def test_set_name(self): tests.append(os_helper.TESTFN_UNENCODABLE) if sys.platform.startswith("sunos"): - encoding = "utf-8" + # Use ASCII encoding on Solaris/Illumos/OpenIndiana + encoding = "ascii" else: encoding = sys.getfilesystemencoding() @@ -2336,7 +2337,7 @@ def work(): if truncate is not None: encoded = encoded[:truncate] if sys.platform.startswith("sunos"): - expected = encoded.decode("utf-8", "surrogateescape") + expected = encoded.decode("ascii", "replace") else: expected = os.fsdecode(encoded) else: @@ -2355,49 +2356,17 @@ def work(): if '\0' in expected: expected = expected.split('\0', 1)[0] - with self.subTest(name=name, expected=expected): + with self.subTest(name=name, expected=expected, thread="main"): + _thread.set_name(name) + self.assertEqual(_thread._get_name(), expected) + + with self.subTest(name=name, expected=expected, thread="worker"): work_name = None thread = threading.Thread(target=work, name=name) thread.start() thread.join() - - # Detect if running on OpenIndiana / illumos - try: - is_illumos = os.uname().version.startswith('illumos') - except AttributeError: - is_illumos = False - - if is_illumos: - # illumos requires ASCII-encoded thread names - if not work_name: - # name didn't get set (set_name may have failed) - self.skipTest( - f"Platform does not support non-ASCII thread names: got empty name for {name!r}" - ) - - work_name_bytes = ( - work_name.encode('ascii', errors='ignore') - if isinstance(work_name, str) - else work_name - ) - expected_bytes = expected.encode('ascii', errors='ignore') - - self.assertEqual( - work_name_bytes, - expected_bytes, - f"{len(work_name)=} and {len(expected)=}" - ) - - elif not name.isascii() and not work_name: - # Platform does not support non-ASCII thread names - self.skipTest( - f"Platform does not support non-ASCII thread names: got empty name for {name!r}" - ) - - else: - # Most platforms - self.assertEqual(work_name, expected, - f"{len(work_name)=} and {len(expected)=}") + self.assertEqual(work_name, expected, + f"{len(work_name)=} and {len(expected)=}") @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") From 25085bf27b0dea6a4ce9d16d4ca453aea709f3e0 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Tue, 2 Sep 2025 00:18:08 -0400 Subject: [PATCH 33/41] encode ascii on solaris/openindiana --- Modules/_threadmodule.c | 132 +++++++++++----------------------------- 1 file changed, 37 insertions(+), 95 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 3e33c05104c27a..06cd7e31609009 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -17,11 +17,6 @@ #ifdef HAVE_SIGNAL_H # include // SIGINT #endif -#ifdef HAVE_PTHREAD_H -# include -#endif -#include -#include // ThreadError is just an alias to PyExc_RuntimeError #define ThreadError PyExc_RuntimeError @@ -660,13 +655,6 @@ PyThreadHandleObject_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)PyThreadHandleObject_new(type); } -static int -PyThreadHandleObject_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void PyThreadHandleObject_dealloc(PyObject *op) { @@ -756,7 +744,7 @@ static PyType_Slot ThreadHandle_Type_slots[] = { {Py_tp_dealloc, PyThreadHandleObject_dealloc}, {Py_tp_repr, PyThreadHandleObject_repr}, {Py_tp_getset, ThreadHandle_getsetlist}, - {Py_tp_traverse, PyThreadHandleObject_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_methods, ThreadHandle_methods}, {Py_tp_new, PyThreadHandleObject_tp_new}, {0, 0} @@ -772,13 +760,6 @@ static PyType_Spec ThreadHandle_Type_spec = { /* Lock objects */ -static int -lock_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static void lock_dealloc(PyObject *self) { @@ -1050,7 +1031,7 @@ static PyType_Slot lock_type_slots[] = { {Py_tp_repr, lock_repr}, {Py_tp_doc, (void *)lock_doc}, {Py_tp_methods, lock_methods}, - {Py_tp_traverse, lock_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {Py_tp_new, lock_new}, {0, 0} }; @@ -1065,13 +1046,6 @@ static PyType_Spec lock_type_spec = { /* Recursive lock objects */ -static int -rlock_traverse(PyObject *self, visitproc visit, void *arg) -{ - Py_VISIT(Py_TYPE(self)); - return 0; -} - static int rlock_locked_impl(rlockobject *self) { @@ -1364,7 +1338,7 @@ static PyType_Slot rlock_type_slots[] = { {Py_tp_methods, rlock_methods}, {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_new, rlock_new}, - {Py_tp_traverse, rlock_traverse}, + {Py_tp_traverse, _PyObject_VisitType}, {0, 0}, }; @@ -2522,58 +2496,6 @@ of the main interpreter."); # include #endif -/* Helper: encode/truncate and call native API to set thread name. - * Return: - * 0 : success - * >0 : errno-style native error code (e.g. EINVAL) - * -1 : Python-level error (an exception has been set) - */ -static int -set_encoded_thread_name(PyObject *name_obj, const char *encoding) -{ - PyObject *name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); - if (name_encoded == NULL) { - /* PyUnicode_AsEncodedString set an exception */ - return -1; - } - -#ifdef _PYTHREAD_NAME_MAXLEN - if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { - PyObject *truncated = PyBytes_FromStringAndSize( - PyBytes_AS_STRING(name_encoded), - _PYTHREAD_NAME_MAXLEN); - Py_DECREF(name_encoded); - if (truncated == NULL) { - /* PyBytes_FromStringAndSize set an exception */ - return -1; - } - name_encoded = truncated; - } -#endif - - const char *name = PyBytes_AS_STRING(name_encoded); - int rc = 0; - -#ifdef __APPLE__ - rc = pthread_setname_np(name); -#elif defined(__NetBSD__) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, "%s", (void *)name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - pthread_t thread = pthread_self(); - rc = pthread_setname_np(thread, name); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_t thread = pthread_self(); - pthread_set_name_np(thread, name); - rc = 0; /* that API returns void */ -#else - rc = 0; /* no-op if platform unsupported */ -#endif - - Py_DECREF(name_encoded); - return rc; -} - #if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS) /*[clinic input] _thread._get_name @@ -2601,7 +2523,8 @@ _thread__get_name_impl(PyObject *module) } #ifdef __sun - return PyUnicode_DecodeUTF8(name, strlen(name), "surrogateescape"); + // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII with "replace" to avoid decoding errors. + return PyUnicode_DecodeASCII(name, strlen(name), "replace"); #else return PyUnicode_DecodeFSDefault(name); #endif @@ -2639,30 +2562,49 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) { #ifndef MS_WINDOWS #ifdef __sun - // Solaris always uses UTF-8 - const char *encoding = "utf-8"; + // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII to avoid decoding errors. + const char *encoding = "ascii"; #else // Encode the thread name to the filesystem encoding using the "replace" // error handler PyInterpreterState *interp = _PyInterpreterState_GET(); const char *encoding = interp->unicode.fs_codec.encoding; #endif - - int rc = set_encoded_thread_name(name_obj, encoding); - /* Confirm a Python exception was set by the helper. - If not, convert to a runtime error (defensive). */ - if (rc == -1 && PyErr_Occurred()) { + PyObject *name_encoded; + name_encoded = PyUnicode_AsEncodedString(name_obj, encoding, "replace"); + if (name_encoded == NULL) { return NULL; } - /* If native API refused (EINVAL) and we didn't try ASCII, retry with ASCII. */ - if (rc == EINVAL && strcmp(encoding, "ascii") != 0) { - rc = set_encoded_thread_name(name_obj, "ascii"); - if (rc == -1 && PyErr_Occurred()) { +#ifdef _PYTHREAD_NAME_MAXLEN + // Truncate to _PYTHREAD_NAME_MAXLEN bytes + the NUL byte if needed + if (PyBytes_GET_SIZE(name_encoded) > _PYTHREAD_NAME_MAXLEN) { + PyObject *truncated; + truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded), + _PYTHREAD_NAME_MAXLEN); + if (truncated == NULL) { + Py_DECREF(name_encoded); return NULL; } - /* fall through to raise errno below */ + Py_SETREF(name_encoded, truncated); } +#endif + + const char *name = PyBytes_AS_STRING(name_encoded); +#ifdef __APPLE__ + int rc = pthread_setname_np(name); +#elif defined(__NetBSD__) + pthread_t thread = pthread_self(); + int rc = pthread_setname_np(thread, "%s", (void *)name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + pthread_t thread = pthread_self(); + int rc = pthread_setname_np(thread, name); +#else /* defined(HAVE_PTHREAD_SET_NAME_NP) */ + pthread_t thread = pthread_self(); + int rc = 0; /* pthread_set_name_np() returns void */ + pthread_set_name_np(thread, name); +#endif + Py_DECREF(name_encoded); if (rc) { errno = rc; return PyErr_SetFromErrno(PyExc_OSError); @@ -2932,4 +2874,4 @@ PyMODINIT_FUNC PyInit__thread(void) { return PyModuleDef_Init(&thread_module); -} +} \ No newline at end of file From dc86a294a3fc6c7877aae54ddf43d59d54f9801e Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Tue, 2 Sep 2025 02:26:53 -0400 Subject: [PATCH 34/41] test_threading main name fixed, minor changes, news --- Lib/test/test_threading.py | 3 +++ .../2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst | 2 +- Modules/_threadmodule.c | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 51d041d22f6b50..06a8a41e1643d6 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2281,6 +2281,9 @@ def test__all__(self): @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") def test_set_name(self): + # Ensure main thread name is restored after test + self.addCleanup(_thread.set_name, _thread._get_name()) + # set_name() limit in bytes truncate = getattr(_thread, "_NAME_MAXLEN", None) limit = truncate or 100 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst index dce0f89c34f9b4..b676f6647e2ec2 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst @@ -1 +1 @@ -_thread.set_name() now retries with an ASCII fallback if pthread_setname_np() rejects UTF-8 names on some POSIX-compliant platforms. +On Solaris/Illumos platforms, thread names are now encoded as ASCII to avoid errors on systems (e.g. OpenIndiana) that don't support non-ASCII names. \ No newline at end of file diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 06cd7e31609009..655a15c4b76c2c 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2523,8 +2523,8 @@ _thread__get_name_impl(PyObject *module) } #ifdef __sun - // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII with "replace" to avoid decoding errors. - return PyUnicode_DecodeASCII(name, strlen(name), "replace"); + // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII since OpenIndiana only supports ASCII names. + return PyUnicode_DecodeASCII(name, strlen(name), "surrogateescape"); #else return PyUnicode_DecodeFSDefault(name); #endif @@ -2562,7 +2562,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) { #ifndef MS_WINDOWS #ifdef __sun - // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII to avoid decoding errors. + // Encode Solaris/Illumos thread names as ASCII since OpenIndiana does not support non-ASCII names const char *encoding = "ascii"; #else // Encode the thread name to the filesystem encoding using the "replace" From c0f672e778f1f715d013209888f9f0e6704514d7 Mon Sep 17 00:00:00 2001 From: jadonduff Date: Tue, 2 Sep 2025 02:33:59 -0400 Subject: [PATCH 35/41] Lint fix --- .../2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst index b676f6647e2ec2..e73be998f4be7b 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-21-06-31-42.gh-issue-138004.FH2Hre.rst @@ -1 +1 @@ -On Solaris/Illumos platforms, thread names are now encoded as ASCII to avoid errors on systems (e.g. OpenIndiana) that don't support non-ASCII names. \ No newline at end of file +On Solaris/Illumos platforms, thread names are now encoded as ASCII to avoid errors on systems (e.g. OpenIndiana) that don't support non-ASCII names. From 69796ffd21e2263bee1aca422c78a05031dfe02a Mon Sep 17 00:00:00 2001 From: jadonduff Date: Tue, 2 Sep 2025 08:42:04 -0400 Subject: [PATCH 36/41] Update replace to surrogateescape Co-authored-by: Serhiy Storchaka --- Lib/test/test_threading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 06a8a41e1643d6..6e612b06281ca2 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2340,7 +2340,7 @@ def work(): if truncate is not None: encoded = encoded[:truncate] if sys.platform.startswith("sunos"): - expected = encoded.decode("ascii", "replace") + expected = encoded.decode("ascii", "surrogateescape") else: expected = os.fsdecode(encoded) else: From 15c84813bbd9046f778fe49534851a7de2c87ab7 Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Tue, 2 Sep 2025 08:51:59 -0400 Subject: [PATCH 37/41] fix comment length --- Modules/_threadmodule.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 655a15c4b76c2c..07d41de7e2f9b6 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2523,7 +2523,8 @@ _thread__get_name_impl(PyObject *module) } #ifdef __sun - // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII since OpenIndiana only supports ASCII names. + // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII + // since OpenIndiana only supports ASCII names. return PyUnicode_DecodeASCII(name, strlen(name), "surrogateescape"); #else return PyUnicode_DecodeFSDefault(name); @@ -2562,7 +2563,8 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) { #ifndef MS_WINDOWS #ifdef __sun - // Encode Solaris/Illumos thread names as ASCII since OpenIndiana does not support non-ASCII names + // Encode Solaris/Illumos thread names as ASCII + // since OpenIndiana does not support non-ASCII names. const char *encoding = "ascii"; #else // Encode the thread name to the filesystem encoding using the "replace" From 8fc613b4191447c89b22d7c7990f3e98dd4c1dab Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Tue, 2 Sep 2025 10:37:07 -0400 Subject: [PATCH 38/41] fix comments and move thread name reset in test --- Lib/test/test_threading.py | 6 +++--- Modules/_threadmodule.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 6e612b06281ca2..d19ab884e4ccbd 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2281,9 +2281,6 @@ def test__all__(self): @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") def test_set_name(self): - # Ensure main thread name is restored after test - self.addCleanup(_thread.set_name, _thread._get_name()) - # set_name() limit in bytes truncate = getattr(_thread, "_NAME_MAXLEN", None) limit = truncate or 100 @@ -2359,6 +2356,9 @@ def work(): if '\0' in expected: expected = expected.split('\0', 1)[0] + # Ensure main thread name is restored after test + self.addCleanup(_thread.set_name, _thread._get_name()) + with self.subTest(name=name, expected=expected, thread="main"): _thread.set_name(name) self.assertEqual(_thread._get_name(), expected) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 07d41de7e2f9b6..2e0c41e3085f7e 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2523,8 +2523,8 @@ _thread__get_name_impl(PyObject *module) } #ifdef __sun - // Decode Solaris/Illumos (e.g. OpenIndiana) thread names as ASCII - // since OpenIndiana only supports ASCII names. + // gh-138004: Decode Solaris/Illumos (e.g. OpenIndiana) thread names + // from ASCII, since OpenIndiana only supports ASCII names. return PyUnicode_DecodeASCII(name, strlen(name), "surrogateescape"); #else return PyUnicode_DecodeFSDefault(name); @@ -2563,7 +2563,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj) { #ifndef MS_WINDOWS #ifdef __sun - // Encode Solaris/Illumos thread names as ASCII + // gh-138004: Encode Solaris/Illumos thread names to ASCII, // since OpenIndiana does not support non-ASCII names. const char *encoding = "ascii"; #else From 5e5effb58b907ff3c321f1d5f1e2bf2100cae0de Mon Sep 17 00:00:00 2001 From: Jadon Duff Date: Tue, 2 Sep 2025 10:39:58 -0400 Subject: [PATCH 39/41] revert name reset to original position --- Lib/test/test_threading.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index d19ab884e4ccbd..6e612b06281ca2 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -2281,6 +2281,9 @@ def test__all__(self): @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name") @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name") def test_set_name(self): + # Ensure main thread name is restored after test + self.addCleanup(_thread.set_name, _thread._get_name()) + # set_name() limit in bytes truncate = getattr(_thread, "_NAME_MAXLEN", None) limit = truncate or 100 @@ -2356,9 +2359,6 @@ def work(): if '\0' in expected: expected = expected.split('\0', 1)[0] - # Ensure main thread name is restored after test - self.addCleanup(_thread.set_name, _thread._get_name()) - with self.subTest(name=name, expected=expected, thread="main"): _thread.set_name(name) self.assertEqual(_thread._get_name(), expected) From 338593ceabc28ee4176e96fd726c3adcafe7cef7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 2 Sep 2025 17:51:32 +0300 Subject: [PATCH 40/41] Update Modules/_threadmodule.c --- Modules/_threadmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 2e0c41e3085f7e..6ad28f8acf1181 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2524,7 +2524,7 @@ _thread__get_name_impl(PyObject *module) #ifdef __sun // gh-138004: Decode Solaris/Illumos (e.g. OpenIndiana) thread names - // from ASCII, since OpenIndiana only supports ASCII names. + // from ASCII, since OpenIndiana only supports ASCII names. return PyUnicode_DecodeASCII(name, strlen(name), "surrogateescape"); #else return PyUnicode_DecodeFSDefault(name); From e64226467118cd031e5327afbdea2b29429f0f92 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 2 Sep 2025 17:52:25 +0300 Subject: [PATCH 41/41] Fix EOL. --- Modules/_threadmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 6ad28f8acf1181..a436a553db9802 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2876,4 +2876,4 @@ PyMODINIT_FUNC PyInit__thread(void) { return PyModuleDef_Init(&thread_module); -} \ No newline at end of file +}