Skip to content

Commit 37d47d4

Browse files
authored
gh-125434: Display thread name in faulthandler (#132016)
1 parent 2ccd6aa commit 37d47d4

File tree

3 files changed

+47
-14
lines changed

3 files changed

+47
-14
lines changed

Lib/test/test_faulthandler.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222

2323
TIMEOUT = 0.5
2424

25+
STACK_HEADER_STR = r'Stack (most recent call first):'
26+
27+
# Regular expressions
28+
STACK_HEADER = re.escape(STACK_HEADER_STR)
29+
THREAD_NAME = r'( \[.*\])?'
30+
THREAD_ID = fr'Thread 0x[0-9a-f]+{THREAD_NAME}'
31+
THREAD_HEADER = fr'{THREAD_ID} \(most recent call first\):'
32+
CURRENT_THREAD_ID = fr'Current thread 0x[0-9a-f]+{THREAD_NAME}'
33+
CURRENT_THREAD_HEADER = fr'{CURRENT_THREAD_ID} \(most recent call first\):'
34+
2535

2636
def expected_traceback(lineno1, lineno2, header, min_count=1):
2737
regex = header
@@ -106,18 +116,18 @@ def check_error(self, code, lineno, fatal_error, *,
106116
)
107117
if all_threads and not all_threads_disabled:
108118
if know_current_thread:
109-
header = 'Current thread 0x[0-9a-f]+'
119+
header = CURRENT_THREAD_HEADER
110120
else:
111-
header = 'Thread 0x[0-9a-f]+'
121+
header = THREAD_HEADER
112122
else:
113-
header = 'Stack'
123+
header = STACK_HEADER
114124
regex = [f'^{fatal_error}']
115125
if py_fatal_error:
116126
regex.append("Python runtime state: initialized")
117127
regex.append('')
118128
if all_threads_disabled and not py_fatal_error:
119129
regex.append("<Cannot show all threads while the GIL is disabled>")
120-
regex.append(fr'{header} \(most recent call first\):')
130+
regex.append(fr'{header}')
121131
if support.Py_GIL_DISABLED and py_fatal_error and not know_current_thread:
122132
regex.append(" <tstate is freed>")
123133
else:
@@ -498,7 +508,7 @@ def funcA():
498508
else:
499509
lineno = 14
500510
expected = [
501-
'Stack (most recent call first):',
511+
f'{STACK_HEADER_STR}',
502512
' File "<string>", line %s in funcB' % lineno,
503513
' File "<string>", line 17 in funcA',
504514
' File "<string>", line 19 in <module>'
@@ -536,7 +546,7 @@ def {func_name}():
536546
func_name=func_name,
537547
)
538548
expected = [
539-
'Stack (most recent call first):',
549+
f'{STACK_HEADER_STR}',
540550
' File "<string>", line 4 in %s' % truncated,
541551
' File "<string>", line 6 in <module>'
542552
]
@@ -590,18 +600,18 @@ def run(self):
590600
lineno = 10
591601
# When the traceback is dumped, the waiter thread may be in the
592602
# `self.running.set()` call or in `self.stop.wait()`.
593-
regex = r"""
594-
^Thread 0x[0-9a-f]+ \(most recent call first\):
603+
regex = fr"""
604+
^{THREAD_HEADER}
595605
(?: File ".*threading.py", line [0-9]+ in [_a-z]+
596606
){{1,3}} File "<string>", line (?:22|23) in run
597607
File ".*threading.py", line [0-9]+ in _bootstrap_inner
598608
File ".*threading.py", line [0-9]+ in _bootstrap
599609
600-
Current thread 0x[0-9a-f]+ \(most recent call first\):
610+
{CURRENT_THREAD_HEADER}
601611
File "<string>", line {lineno} in dump
602612
File "<string>", line 28 in <module>$
603613
"""
604-
regex = dedent(regex.format(lineno=lineno)).strip()
614+
regex = dedent(regex).strip()
605615
self.assertRegex(output, regex)
606616
self.assertEqual(exitcode, 0)
607617

@@ -667,7 +677,8 @@ def func(timeout, repeat, cancel, file, loops):
667677
count = loops
668678
if repeat:
669679
count *= 2
670-
header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call first\):\n' % timeout_str
680+
header = (fr'Timeout \({timeout_str}\)!\n'
681+
fr'{THREAD_HEADER}\n')
671682
regex = expected_traceback(17, 26, header, min_count=count)
672683
self.assertRegex(trace, regex)
673684
else:
@@ -768,9 +779,9 @@ def handler(signum, frame):
768779
trace = '\n'.join(trace)
769780
if not unregister:
770781
if all_threads:
771-
regex = r'Current thread 0x[0-9a-f]+ \(most recent call first\):\n'
782+
regex = fr'{CURRENT_THREAD_HEADER}\n'
772783
else:
773-
regex = r'Stack \(most recent call first\):\n'
784+
regex = fr'{STACK_HEADER}\n'
774785
regex = expected_traceback(14, 32, regex)
775786
self.assertRegex(trace, regex)
776787
else:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Display thread name in :mod:`faulthandler`. Patch by Victor Stinner.

Python/traceback.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222

2323
#define OFF(x) offsetof(PyTracebackObject, x)
24-
#define PUTS(fd, str) (void)_Py_write_noraise(fd, str, (int)strlen(str))
24+
#define PUTS(fd, str) (void)_Py_write_noraise(fd, str, strlen(str))
2525

2626
#define MAX_STRING_LENGTH 500
2727
#define MAX_FRAME_DEPTH 100
@@ -1054,6 +1054,27 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
10541054
_Py_DumpHexadecimal(fd,
10551055
tstate->thread_id,
10561056
sizeof(unsigned long) * 2);
1057+
1058+
// Write the thread name
1059+
#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)
1060+
char name[100];
1061+
pthread_t thread = (pthread_t)tstate->thread_id;
1062+
#ifdef HAVE_PTHREAD_GETNAME_NP
1063+
int rc = pthread_getname_np(thread, name, Py_ARRAY_LENGTH(name));
1064+
#else /* defined(HAVE_PTHREAD_GET_NAME_NP) */
1065+
int rc = 0; /* pthread_get_name_np() returns void */
1066+
pthread_get_name_np(thread, name, Py_ARRAY_LENGTH(name));
1067+
#endif
1068+
if (!rc) {
1069+
size_t len = strlen(name);
1070+
if (len) {
1071+
PUTS(fd, " [");
1072+
(void)_Py_write_noraise(fd, name, len);
1073+
PUTS(fd, "]");
1074+
}
1075+
}
1076+
#endif
1077+
10571078
PUTS(fd, " (most recent call first):\n");
10581079
}
10591080

0 commit comments

Comments
 (0)