]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-59705: Set OS thread name when Thread.name is changed (#127702)
authorVictor Stinner <vstinner@python.org>
Tue, 10 Dec 2024 16:33:11 +0000 (17:33 +0100)
committerGitHub <noreply@github.com>
Tue, 10 Dec 2024 16:33:11 +0000 (17:33 +0100)
Co-authored-by: Petr Viktorin <encukou@gmail.com>
Doc/library/threading.rst
Lib/test/test_threading.py
Lib/threading.py
Modules/_threadmodule.c

index d4b343db36efb302a93f76fb9e922ec7559d6326..f183f3f535c4cb1ff6b55dbb6aa5a4999090ca28 100644 (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()
 
index d05161f46f10348bbc9a156c2e70653fefee1358..3e164a12581dd13e2b12bd08686f98306f210353 100644 (file)
@@ -2164,6 +2164,25 @@ class MiscTestCase(unittest.TestCase):
                 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):
index 3abd22a2aa1b7214d02ddca95687067be0229250..78e591124278fc8bcc3752113fa05d349992e417 100644 (file)
@@ -1026,16 +1026,20 @@ class Thread:
         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 @@ class Thread:
     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):
index 35c032fbeaa94f78bf8c1dc6bf5891749a8e4dbd..75b34a8df7622c81a8850ca4d2046bc79bc11dd4 100644 (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);