Skip to content

gh-125434: Display thread name in faulthandler #132016

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

Merged
merged 7 commits into from
Apr 4, 2025
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
37 changes: 24 additions & 13 deletions Lib/test/test_faulthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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>'
Expand Down Expand Up @@ -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>'
]
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Display thread name in :mod:`faulthandler`. Patch by Victor Stinner.
23 changes: 22 additions & 1 deletion Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
}

Expand Down
Loading