gh-59705: Set OS thread name when Thread.name is changed (#127702)

Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
Victor Stinner 2024-12-10 17:33:11 +01:00 committed by GitHub
parent 9af96f4406
commit c91ccbe4ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 7 deletions

View File

@ -434,6 +434,18 @@ since it is impossible to detect the termination of alien threads.
Multiple threads may be given the same name. The initial name is set by
the constructor.
On some platforms, the thread name is set at the operating system level
when the thread starts, so that it is visible in task managers.
This name may be truncated to fit in a system-specific limit (for example,
15 bytes on Linux or 63 bytes on macOS).
Changes to *name* are only reflected at the OS level when the currently
running thread is renamed. (Setting the *name* attribute of a
different thread only updates the Python Thread object.)
.. versionchanged:: 3.14
Set the operating system thread name.
.. method:: getName()
setName()

View File

@ -2164,6 +2164,25 @@ def work():
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")
def test_change_name(self):
# Change the name of a thread while the thread is running
name1 = None
name2 = None
def work():
nonlocal name1, name2
name1 = _thread._get_name()
threading.current_thread().name = "new name"
name2 = _thread._get_name()
thread = threading.Thread(target=work, name="name")
thread.start()
thread.join()
self.assertEqual(name1, "name")
self.assertEqual(name2, "new name")
class InterruptMainTests(unittest.TestCase):
def check_interrupt_main_with_signal_handler(self, signum):

View File

@ -1026,16 +1026,20 @@ def _set_ident(self):
def _set_native_id(self):
self._native_id = get_native_id()
def _set_os_name(self):
if _set_name is None or not self._name:
return
try:
_set_name(self._name)
except OSError:
pass
def _bootstrap_inner(self):
try:
self._set_ident()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
if _set_name is not None and self._name:
try:
_set_name(self._name)
except OSError:
pass
self._set_os_name()
self._started.set()
with _active_limbo_lock:
_active[self._ident] = self
@ -1115,6 +1119,8 @@ def name(self):
def name(self, name):
assert self._initialized, "Thread.__init__() not called"
self._name = str(name)
if get_ident() == self._ident:
self._set_os_name()
@property
def ident(self):

View File

@ -2423,8 +2423,7 @@ _thread_set_name_impl(PyObject *module, PyObject *name_obj)
#ifdef PYTHREAD_NAME_MAXLEN
// Truncate to PYTHREAD_NAME_MAXLEN bytes + the NUL byte if needed
size_t len = PyBytes_GET_SIZE(name_encoded);
if (len > 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);