https://github.com/python/cpython/commit/37d47d496525142d12a94fb234c8b8311292c349
commit: 37d47d496525142d12a94fb234c8b8311292c349
branch: main
author: Victor Stinner <vstin...@python.org>
committer: vstinner <vstin...@python.org>
date: 2025-04-04T12:24:41Z
summary:

gh-125434: Display thread name in faulthandler (#132016)

files:
A Misc/NEWS.d/next/Library/2025-04-02-16-01-12.gh-issue-125434.EjPc7g.rst
M Lib/test/test_faulthandler.py
M Python/traceback.c

diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index 998d8e3ce25285..2a8c96f049efd0 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -22,6 +22,16 @@
 
 TIMEOUT = 0.5
 
+STACK_HEADER_STR = r'Stack (most recent call first):'
+
+# Regular expressions
+STACK_HEADER = re.escape(STACK_HEADER_STR)
+THREAD_NAME = r'( \[.*\])?'
+THREAD_ID = fr'Thread 0x[0-9a-f]+{THREAD_NAME}'
+THREAD_HEADER = fr'{THREAD_ID} \(most recent call first\):'
+CURRENT_THREAD_ID = fr'Current thread 0x[0-9a-f]+{THREAD_NAME}'
+CURRENT_THREAD_HEADER = fr'{CURRENT_THREAD_ID} \(most recent call first\):'
+
 
 def expected_traceback(lineno1, lineno2, header, min_count=1):
     regex = header
@@ -106,18 +116,18 @@ def check_error(self, code, lineno, fatal_error, *,
         )
         if all_threads and not all_threads_disabled:
             if know_current_thread:
-                header = 'Current thread 0x[0-9a-f]+'
+                header = CURRENT_THREAD_HEADER
             else:
-                header = 'Thread 0x[0-9a-f]+'
+                header = THREAD_HEADER
         else:
-            header = 'Stack'
+            header = STACK_HEADER
         regex = [f'^{fatal_error}']
         if py_fatal_error:
             regex.append("Python runtime state: initialized")
         regex.append('')
         if all_threads_disabled and not py_fatal_error:
             regex.append("<Cannot show all threads while the GIL is disabled>")
-        regex.append(fr'{header} \(most recent call first\):')
+        regex.append(fr'{header}')
         if support.Py_GIL_DISABLED and py_fatal_error and not 
know_current_thread:
             regex.append("  <tstate is freed>")
         else:
@@ -498,7 +508,7 @@ def funcA():
         else:
             lineno = 14
         expected = [
-            'Stack (most recent call first):',
+            f'{STACK_HEADER_STR}',
             '  File "<string>", line %s in funcB' % lineno,
             '  File "<string>", line 17 in funcA',
             '  File "<string>", line 19 in <module>'
@@ -536,7 +546,7 @@ def {func_name}():
             func_name=func_name,
         )
         expected = [
-            'Stack (most recent call first):',
+            f'{STACK_HEADER_STR}',
             '  File "<string>", line 4 in %s' % truncated,
             '  File "<string>", line 6 in <module>'
         ]
@@ -590,18 +600,18 @@ def run(self):
             lineno = 10
         # When the traceback is dumped, the waiter thread may be in the
         # `self.running.set()` call or in `self.stop.wait()`.
-        regex = r"""
-            ^Thread 0x[0-9a-f]+ \(most recent call first\):
+        regex = fr"""
+            ^{THREAD_HEADER}
             (?:  File ".*threading.py", line [0-9]+ in [_a-z]+
             ){{1,3}}  File "<string>", line (?:22|23) in run
               File ".*threading.py", line [0-9]+ in _bootstrap_inner
               File ".*threading.py", line [0-9]+ in _bootstrap
 
-            Current thread 0x[0-9a-f]+ \(most recent call first\):
+            {CURRENT_THREAD_HEADER}
               File "<string>", line {lineno} in dump
               File "<string>", line 28 in <module>$
             """
-        regex = dedent(regex.format(lineno=lineno)).strip()
+        regex = dedent(regex).strip()
         self.assertRegex(output, regex)
         self.assertEqual(exitcode, 0)
 
@@ -667,7 +677,8 @@ def func(timeout, repeat, cancel, file, loops):
             count = loops
             if repeat:
                 count *= 2
-            header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call 
first\):\n' % timeout_str
+            header = (fr'Timeout \({timeout_str}\)!\n'
+                      fr'{THREAD_HEADER}\n')
             regex = expected_traceback(17, 26, header, min_count=count)
             self.assertRegex(trace, regex)
         else:
@@ -768,9 +779,9 @@ def handler(signum, frame):
         trace = '\n'.join(trace)
         if not unregister:
             if all_threads:
-                regex = r'Current thread 0x[0-9a-f]+ \(most recent call 
first\):\n'
+                regex = fr'{CURRENT_THREAD_HEADER}\n'
             else:
-                regex = r'Stack \(most recent call first\):\n'
+                regex = fr'{STACK_HEADER}\n'
             regex = expected_traceback(14, 32, regex)
             self.assertRegex(trace, regex)
         else:
diff --git 
a/Misc/NEWS.d/next/Library/2025-04-02-16-01-12.gh-issue-125434.EjPc7g.rst 
b/Misc/NEWS.d/next/Library/2025-04-02-16-01-12.gh-issue-125434.EjPc7g.rst
new file mode 100644
index 00000000000000..c630112ce2dd60
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-02-16-01-12.gh-issue-125434.EjPc7g.rst
@@ -0,0 +1 @@
+Display thread name in :mod:`faulthandler`. Patch by Victor Stinner.
diff --git a/Python/traceback.c b/Python/traceback.c
index ff6f9b9a6abd37..6a6a3c29859fbc 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -21,7 +21,7 @@
 
 
 #define OFF(x) offsetof(PyTracebackObject, x)
-#define PUTS(fd, str) (void)_Py_write_noraise(fd, str, (int)strlen(str))
+#define PUTS(fd, str) (void)_Py_write_noraise(fd, str, strlen(str))
 
 #define MAX_STRING_LENGTH 500
 #define MAX_FRAME_DEPTH 100
@@ -1054,6 +1054,27 @@ write_thread_id(int fd, PyThreadState *tstate, int 
is_current)
     _Py_DumpHexadecimal(fd,
                         tstate->thread_id,
                         sizeof(unsigned long) * 2);
+
+    // Write the thread name
+#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)
+    char name[100];
+    pthread_t thread = (pthread_t)tstate->thread_id;
+#ifdef HAVE_PTHREAD_GETNAME_NP
+    int rc = pthread_getname_np(thread, name, Py_ARRAY_LENGTH(name));
+#else /* defined(HAVE_PTHREAD_GET_NAME_NP) */
+    int rc = 0; /* pthread_get_name_np() returns void */
+    pthread_get_name_np(thread, name, Py_ARRAY_LENGTH(name));
+#endif
+    if (!rc) {
+        size_t len = strlen(name);
+        if (len) {
+            PUTS(fd, " [");
+            (void)_Py_write_noraise(fd, name, len);
+            PUTS(fd, "]");
+        }
+    }
+#endif
+
     PUTS(fd, " (most recent call first):\n");
 }
 

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to