Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d97417d
fix threadmodule ascii and make test more lenient
jadonduff Aug 21, 2025
1db08a7
📜🤖 Added by blurb_it.
blurb-it[bot] Aug 21, 2025
0606968
implement requested changes
jadonduff Aug 22, 2025
b556774
reformat and remove test news
jadonduff Aug 22, 2025
38a75d3
patch for windows & non-posix compliant platforms
jadonduff Aug 22, 2025
31731b1
patch
jadonduff Aug 22, 2025
83fe205
Check if generated files are up to date patch
jadonduff Aug 22, 2025
aadf7f3
test
jadonduff Aug 22, 2025
612a0a4
test2
jadonduff Aug 22, 2025
1fa51f8
test3
jadonduff Aug 22, 2025
d24d0bb
attempt fix
jadonduff Aug 22, 2025
95d289f
Merge branch 'main' into thread_name_fix
jadonduff Aug 22, 2025
d7a47bf
test
jadonduff Aug 22, 2025
1970a00
fix stray newline
jadonduff Aug 22, 2025
6395323
Merge branch 'main' into thread_name_fix
jadonduff Aug 22, 2025
241e097
Apply suggestions from code review
jadonduff Aug 22, 2025
66c058b
fixes
jadonduff Aug 22, 2025
f817412
apply fixes (built)
jadonduff Aug 22, 2025
3349464
remove dead code, fix err var, revert newline, fix comment
jadonduff Aug 23, 2025
ac3d3dd
redundant err var replaced
jadonduff Aug 23, 2025
1ab9ecd
replaced functions with inline code, moved into set_thread_name_with_…
jadonduff Aug 23, 2025
d9ba9e8
revert
jadonduff Aug 23, 2025
d7025f5
inline test
jadonduff Aug 23, 2025
3689044
test fix for "Check if generated files are up to date"
jadonduff Aug 23, 2025
246f880
fix linker error
jadonduff Aug 23, 2025
407b1e9
make regen
jadonduff Aug 23, 2025
dc43fc4
move functions and add inline for functions used once
jadonduff Aug 24, 2025
7aa256c
remove duplicated __sun enforcing utf-8 from encoder
jadonduff Aug 24, 2025
b4934a2
consolidate functions, remove unrelated changes
jadonduff Aug 24, 2025
ae5ac4b
clinic regen fix
jadonduff Aug 24, 2025
4fc368d
fix error/fallback (implement changes)
jadonduff Aug 24, 2025
392aa5a
fix whitespace lint issue in test_threading.py
jadonduff Aug 24, 2025
84daea4
Merge branch 'main' into thread_name_fix
jadonduff Aug 25, 2025
d5e9399
make _threadmodule.c clearer, remove unrelated change, add specific t…
jadonduff Aug 25, 2025
105d4e6
Merge branch 'main' into thread_name_fix
jadonduff Aug 26, 2025
c67e93d
Merge branch 'main' into thread_name_fix
jadonduff Aug 27, 2025
508f24d
add test for main and encode ascii
jadonduff Sep 2, 2025
25085bf
encode ascii on solaris/openindiana
jadonduff Sep 2, 2025
cd26833
Merge branch 'main' into thread_name_fix
jadonduff Sep 2, 2025
dc86a29
test_threading main name fixed, minor changes, news
jadonduff Sep 2, 2025
c9f554d
Merge branch 'main' into thread_name_fix
jadonduff Sep 2, 2025
c0f672e
Lint fix
jadonduff Sep 2, 2025
69796ff
Update replace to surrogateescape
jadonduff Sep 2, 2025
15c8481
fix comment length
jadonduff Sep 2, 2025
99f13ec
Merge branch 'main' into thread_name_fix
jadonduff Sep 2, 2025
8fc613b
fix comments and move thread name reset in test
jadonduff Sep 2, 2025
5e5effb
revert name reset to original position
jadonduff Sep 2, 2025
9690eee
Merge branch 'main' into thread_name_fix
jadonduff Sep 2, 2025
338593c
Update Modules/_threadmodule.c
serhiy-storchaka Sep 2, 2025
e642264
Fix EOL.
serhiy-storchaka Sep 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,7 @@ def __init__(self, a, *, b) -> None:

with warnings.catch_warnings(record=True) as warnings_log:
CustomRLock(1, b=2)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray newline change:

Suggested change

self.assertEqual(warnings_log, [])

class EventTests(lock_tests.EventTests):
Expand Down Expand Up @@ -2358,10 +2359,20 @@ 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()
# 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is OSError even raised here? The test failure was different -- that work_name was unexpectedly empty.

Copy link
Contributor Author

@jadonduff jadonduff Aug 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added:

                    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)=}")

Kept OSError fallback because it was shown in original issue, but can remove if needed.

>>> import _thread
>>> _thread.set_name('€')
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    _thread.set_name('€')
    ~~~~~~~~~~~~~~~~^^^^^
OSError: [Errno 22] Invalid argument

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was shown that _thread.set_name() fails, but that OSError is not leaked from the Thread code.

# 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")
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ Weilin Du
John DuBois
Paul Dubois
Jacques Ducasse
Jadon Duff
Andrei Dorian Duma
Graham Dumpleton
Quinn Dunkan
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_thread.set_name() now retries with an ASCII fallback if pthread_setname_np() rejects UTF-8 names on some POSIX-compliant platforms.
84 changes: 61 additions & 23 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,22 @@ 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);
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
Expand Down Expand Up @@ -2576,55 +2584,85 @@ _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
// 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
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;
}
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
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);
return NULL;
}
Py_SETREF(name_encoded, truncated);
}
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, please try to avoid duplicating code. This should be factored out into its own function. Here's an outline:

static PyObject *
get_truncated(PyObject *name_encoded /* stolen */)
{
#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;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or simply include the encoding and truncating code in _set_thread_name(). BTW, there is no need to use underscored names here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created function encode_thread_name and used to replace duplicated code

name = PyBytes_AS_STRING(name_encoded);
rc = _set_thread_name(name);
Py_DECREF(name_encoded);
}

if (rc) {
errno = rc;
return PyErr_SetFromErrno(PyExc_OSError);
Expand Down
Loading