Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions Doc/library/threading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
16 changes: 11 additions & 5 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
3 changes: 1 addition & 2 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading