Skip to content

Commit c91ccbe

Browse files
vstinnerencukou
andauthored
gh-59705: Set OS thread name when Thread.name is changed (#127702)
Co-authored-by: Petr Viktorin <[email protected]>
1 parent 9af96f4 commit c91ccbe

File tree

4 files changed

+43
-7
lines changed

4 files changed

+43
-7
lines changed

Doc/library/threading.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,18 @@ since it is impossible to detect the termination of alien threads.
434434
Multiple threads may be given the same name. The initial name is set by
435435
the constructor.
436436

437+
On some platforms, the thread name is set at the operating system level
438+
when the thread starts, so that it is visible in task managers.
439+
This name may be truncated to fit in a system-specific limit (for example,
440+
15 bytes on Linux or 63 bytes on macOS).
441+
442+
Changes to *name* are only reflected at the OS level when the currently
443+
running thread is renamed. (Setting the *name* attribute of a
444+
different thread only updates the Python Thread object.)
445+
446+
.. versionchanged:: 3.14
447+
Set the operating system thread name.
448+
437449
.. method:: getName()
438450
setName()
439451

Lib/test/test_threading.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,25 @@ def work():
21642164
self.assertEqual(work_name, expected,
21652165
f"{len(work_name)=} and {len(expected)=}")
21662166

2167+
@unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name")
2168+
@unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name")
2169+
def test_change_name(self):
2170+
# Change the name of a thread while the thread is running
2171+
2172+
name1 = None
2173+
name2 = None
2174+
def work():
2175+
nonlocal name1, name2
2176+
name1 = _thread._get_name()
2177+
threading.current_thread().name = "new name"
2178+
name2 = _thread._get_name()
2179+
2180+
thread = threading.Thread(target=work, name="name")
2181+
thread.start()
2182+
thread.join()
2183+
self.assertEqual(name1, "name")
2184+
self.assertEqual(name2, "new name")
2185+
21672186

21682187
class InterruptMainTests(unittest.TestCase):
21692188
def check_interrupt_main_with_signal_handler(self, signum):

Lib/threading.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,16 +1026,20 @@ def _set_ident(self):
10261026
def _set_native_id(self):
10271027
self._native_id = get_native_id()
10281028

1029+
def _set_os_name(self):
1030+
if _set_name is None or not self._name:
1031+
return
1032+
try:
1033+
_set_name(self._name)
1034+
except OSError:
1035+
pass
1036+
10291037
def _bootstrap_inner(self):
10301038
try:
10311039
self._set_ident()
10321040
if _HAVE_THREAD_NATIVE_ID:
10331041
self._set_native_id()
1034-
if _set_name is not None and self._name:
1035-
try:
1036-
_set_name(self._name)
1037-
except OSError:
1038-
pass
1042+
self._set_os_name()
10391043
self._started.set()
10401044
with _active_limbo_lock:
10411045
_active[self._ident] = self
@@ -1115,6 +1119,8 @@ def name(self):
11151119
def name(self, name):
11161120
assert self._initialized, "Thread.__init__() not called"
11171121
self._name = str(name)
1122+
if get_ident() == self._ident:
1123+
self._set_os_name()
11181124

11191125
@property
11201126
def ident(self):

Modules/_threadmodule.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,8 +2423,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj)
24232423

24242424
#ifdef PYTHREAD_NAME_MAXLEN
24252425
// Truncate to PYTHREAD_NAME_MAXLEN bytes + the NUL byte if needed
2426-
size_t len = PyBytes_GET_SIZE(name_encoded);
2427-
if (len > PYTHREAD_NAME_MAXLEN) {
2426+
if (PyBytes_GET_SIZE(name_encoded) > PYTHREAD_NAME_MAXLEN) {
24282427
PyObject *truncated;
24292428
truncated = PyBytes_FromStringAndSize(PyBytes_AS_STRING(name_encoded),
24302429
PYTHREAD_NAME_MAXLEN);

0 commit comments

Comments
 (0)