summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2024-12-06 16:27:12 (GMT)
committerGitHub <noreply@github.com>2024-12-06 16:27:12 (GMT)
commit67b18a18b66b89e253f38895057ef9f6bae92e7b (patch)
tree86d8e41e6935361b3821b7e2769553ca2123bf0d /Lib
parent12680ec5bd45c85b6daebe0739d30ef45f089efa (diff)
downloadcpython-67b18a18b66b89e253f38895057ef9f6bae92e7b.zip
cpython-67b18a18b66b89e253f38895057ef9f6bae92e7b.tar.gz
cpython-67b18a18b66b89e253f38895057ef9f6bae92e7b.tar.bz2
gh-59705: Add _thread.set_name() function (#127338)
On Linux, threading.Thread now sets the thread name to the operating system. * configure now checks if pthread_getname_np() and pthread_setname_np() functions are available. * Add PYTHREAD_NAME_MAXLEN macro. * Add _thread._NAME_MAXLEN constant for test_threading. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_threading.py60
-rw-r--r--Lib/threading.py9
2 files changed, 69 insertions, 0 deletions
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index fe22555..d05161f 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -2104,6 +2104,66 @@ class MiscTestCase(unittest.TestCase):
support.check__all__(self, threading, ('threading', '_thread'),
extra=extra, not_exported=not_exported)
+ @unittest.skipUnless(hasattr(_thread, 'set_name'), "missing _thread.set_name")
+ @unittest.skipUnless(hasattr(_thread, '_get_name'), "missing _thread._get_name")
+ def test_set_name(self):
+ # set_name() limit in bytes
+ truncate = getattr(_thread, "_NAME_MAXLEN", None)
+ limit = truncate or 100
+
+ tests = [
+ # test short ASCII name
+ "CustomName",
+
+ # test short non-ASCII name
+ "namé€",
+
+ # embedded null character: name is truncated
+ # at the first null character
+ "embed\0null",
+
+ # Test long ASCII names (not truncated)
+ "x" * limit,
+
+ # Test long ASCII names (truncated)
+ "x" * (limit + 10),
+
+ # Test long non-ASCII name (truncated)
+ "x" * (limit - 1) + "é€",
+ ]
+ if os_helper.FS_NONASCII:
+ tests.append(f"nonascii:{os_helper.FS_NONASCII}")
+ if os_helper.TESTFN_UNENCODABLE:
+ tests.append(os_helper.TESTFN_UNENCODABLE)
+
+ if sys.platform.startswith("solaris"):
+ encoding = "utf-8"
+ else:
+ encoding = sys.getfilesystemencoding()
+
+ def work():
+ nonlocal work_name
+ work_name = _thread._get_name()
+
+ for name in tests:
+ encoded = name.encode(encoding, "replace")
+ if b'\0' in encoded:
+ encoded = encoded.split(b'\0', 1)[0]
+ if truncate is not None:
+ encoded = encoded[:truncate]
+ if sys.platform.startswith("solaris"):
+ expected = encoded.decode("utf-8", "surrogateescape")
+ else:
+ expected = os.fsdecode(encoded)
+
+ 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)=}")
+
class InterruptMainTests(unittest.TestCase):
def check_interrupt_main_with_signal_handler(self, signum):
diff --git a/Lib/threading.py b/Lib/threading.py
index 94ea2f0..3abd22a 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -48,6 +48,10 @@ try:
__all__.append('get_native_id')
except AttributeError:
_HAVE_THREAD_NATIVE_ID = False
+try:
+ _set_name = _thread.set_name
+except AttributeError:
+ _set_name = None
ThreadError = _thread.error
try:
_CRLock = _thread.RLock
@@ -1027,6 +1031,11 @@ class Thread:
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._started.set()
with _active_limbo_lock:
_active[self._ident] = self