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