From 30eba2c565277bfd9cf35013d829064ffcf83e54 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 2 Apr 2025 15:59:22 +0200 Subject: [PATCH 1/7] gh-125434: Display thread name in faulthandler --- Lib/test/test_faulthandler.py | 37 ++++++++++++------- ...-04-02-16-01-12.gh-issue-125434.EjPc7g.rst | 1 + Modules/_threadmodule.c | 3 +- Python/traceback.c | 23 +++++++++++- 4 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-04-02-16-01-12.gh-issue-125434.EjPc7g.rst 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("") - 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(" ") else: @@ -498,7 +508,7 @@ def funcA(): else: lineno = 14 expected = [ - 'Stack (most recent call first):', + f'{STACK_HEADER_STR}', ' File "", line %s in funcB' % lineno, ' File "", line 17 in funcA', ' File "", line 19 in ' @@ -536,7 +546,7 @@ def {func_name}(): func_name=func_name, ) expected = [ - 'Stack (most recent call first):', + f'{STACK_HEADER_STR}', ' File "", line 4 in %s' % truncated, ' File "", line 6 in ' ] @@ -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 "", 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 "", line {lineno} in dump File "", line 28 in $ """ - 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/Modules/_threadmodule.c b/Modules/_threadmodule.c index f4c98ca39f6ee6..1d5b6122115922 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2415,8 +2415,7 @@ _thread__get_name_impl(PyObject *module) /*[clinic end generated code: output=20026e7ee3da3dd7 input=35cec676833d04c8]*/ { #ifndef MS_WINDOWS - // Linux and macOS are limited to respectively 16 and 64 bytes - char name[100]; + char name[_PYTHREAD_NAME_MAXLEN+1]; pthread_t thread = pthread_self(); #ifdef HAVE_PTHREAD_GETNAME_NP int rc = pthread_getname_np(thread, name, Py_ARRAY_LENGTH(name)); diff --git a/Python/traceback.c b/Python/traceback.c index ff6f9b9a6abd37..ab64c028069d52 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 +#ifndef MS_WINDOWS + char name[_PYTHREAD_NAME_MAXLEN+1]; + pthread_t thread = 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"); } From c7b91b0f6ff688755e3f9739a1cfac34585eb79f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 2 Apr 2025 16:27:59 +0200 Subject: [PATCH 2/7] Fix build on WASI --- Python/traceback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/traceback.c b/Python/traceback.c index ab64c028069d52..875b696b8b7a85 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1056,7 +1056,7 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) sizeof(unsigned long) * 2); // Write the thread name -#ifndef MS_WINDOWS +#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) char name[_PYTHREAD_NAME_MAXLEN+1]; pthread_t thread = tstate->thread_id; #ifdef HAVE_PTHREAD_GETNAME_NP From 5b019305a268dfc4204ebe7bd4c28f8ae8eb3f8e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 2 Apr 2025 16:29:18 +0200 Subject: [PATCH 3/7] Fix compiler warning on macOS --- Python/traceback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/traceback.c b/Python/traceback.c index 875b696b8b7a85..f760f3b6275ce9 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1058,7 +1058,7 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) // Write the thread name #if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) char name[_PYTHREAD_NAME_MAXLEN+1]; - pthread_t thread = tstate->thread_id; + 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) */ From 1466340f5c6ccb2c0a2eae732c5bceeba57b587c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 3 Apr 2025 16:57:57 +0200 Subject: [PATCH 4/7] Require _PYTHREAD_NAME_MAXLEN macro --- Modules/_threadmodule.c | 5 ++++- Python/traceback.c | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 1d5b6122115922..e06aca6b79532c 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2403,7 +2403,10 @@ of the main interpreter."); # include #endif -#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS) + +#if (((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) \ + && defined(_PYTHREAD_NAME_MAXLEN)) \ + || defined(MS_WINDOWS)) /*[clinic input] _thread._get_name diff --git a/Python/traceback.c b/Python/traceback.c index f760f3b6275ce9..2fd9ea4019cbe3 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1056,7 +1056,8 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) sizeof(unsigned long) * 2); // Write the thread name -#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) +#if ((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) \ + && defined(_PYTHREAD_NAME_MAXLEN)) char name[_PYTHREAD_NAME_MAXLEN+1]; pthread_t thread = (pthread_t)tstate->thread_id; #ifdef HAVE_PTHREAD_GETNAME_NP From 7abca6fdab77f0d6846da65b840767e24792bd6d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 3 Apr 2025 17:04:36 +0200 Subject: [PATCH 5/7] Run make clinic --- Modules/clinic/_threadmodule.c.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h index 8930e54170caf4..118ccd4177549b 100644 --- a/Modules/clinic/_threadmodule.c.h +++ b/Modules/clinic/_threadmodule.c.h @@ -8,7 +8,7 @@ preserve #endif #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() -#if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) +#if ((((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) && defined(_PYTHREAD_NAME_MAXLEN)) || defined(MS_WINDOWS))) PyDoc_STRVAR(_thread__get_name__doc__, "_get_name($module, /)\n" @@ -28,7 +28,7 @@ _thread__get_name(PyObject *module, PyObject *Py_UNUSED(ignored)) return _thread__get_name_impl(module); } -#endif /* (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) */ +#endif /* ((((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) && defined(_PYTHREAD_NAME_MAXLEN)) || defined(MS_WINDOWS))) */ #if (defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) || defined(MS_WINDOWS)) @@ -103,4 +103,4 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb #ifndef _THREAD_SET_NAME_METHODDEF #define _THREAD_SET_NAME_METHODDEF #endif /* !defined(_THREAD_SET_NAME_METHODDEF) */ -/*[clinic end generated code: output=e978dc4615b9bc35 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=513be36c7ed8f449 input=a9049054013a1b77]*/ From 2db8badeaa8c96c4be375a31eb58172ed080ff66 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 4 Apr 2025 13:26:16 +0200 Subject: [PATCH 6/7] Use a buffer of 100 bytes to get thread name --- Modules/_threadmodule.c | 7 ++----- Modules/clinic/_threadmodule.c.h | 6 +++--- Python/traceback.c | 5 ++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index e06aca6b79532c..99107e13cedf87 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2403,10 +2403,7 @@ of the main interpreter."); # include #endif - -#if (((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) \ - && defined(_PYTHREAD_NAME_MAXLEN)) \ - || defined(MS_WINDOWS)) +#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS) /*[clinic input] _thread._get_name @@ -2418,7 +2415,7 @@ _thread__get_name_impl(PyObject *module) /*[clinic end generated code: output=20026e7ee3da3dd7 input=35cec676833d04c8]*/ { #ifndef MS_WINDOWS - char name[_PYTHREAD_NAME_MAXLEN+1]; + char name[100]; pthread_t thread = pthread_self(); #ifdef HAVE_PTHREAD_GETNAME_NP int rc = pthread_getname_np(thread, name, Py_ARRAY_LENGTH(name)); diff --git a/Modules/clinic/_threadmodule.c.h b/Modules/clinic/_threadmodule.c.h index 118ccd4177549b..8930e54170caf4 100644 --- a/Modules/clinic/_threadmodule.c.h +++ b/Modules/clinic/_threadmodule.c.h @@ -8,7 +8,7 @@ preserve #endif #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() -#if ((((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) && defined(_PYTHREAD_NAME_MAXLEN)) || defined(MS_WINDOWS))) +#if (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) PyDoc_STRVAR(_thread__get_name__doc__, "_get_name($module, /)\n" @@ -28,7 +28,7 @@ _thread__get_name(PyObject *module, PyObject *Py_UNUSED(ignored)) return _thread__get_name_impl(module); } -#endif /* ((((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) && defined(_PYTHREAD_NAME_MAXLEN)) || defined(MS_WINDOWS))) */ +#endif /* (defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) || defined(MS_WINDOWS)) */ #if (defined(HAVE_PTHREAD_SETNAME_NP) || defined(HAVE_PTHREAD_SET_NAME_NP) || defined(MS_WINDOWS)) @@ -103,4 +103,4 @@ _thread_set_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb #ifndef _THREAD_SET_NAME_METHODDEF #define _THREAD_SET_NAME_METHODDEF #endif /* !defined(_THREAD_SET_NAME_METHODDEF) */ -/*[clinic end generated code: output=513be36c7ed8f449 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e978dc4615b9bc35 input=a9049054013a1b77]*/ diff --git a/Python/traceback.c b/Python/traceback.c index 2fd9ea4019cbe3..6a6a3c29859fbc 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1056,9 +1056,8 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current) sizeof(unsigned long) * 2); // Write the thread name -#if ((defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)) \ - && defined(_PYTHREAD_NAME_MAXLEN)) - char name[_PYTHREAD_NAME_MAXLEN+1]; +#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)); From 5339e019ace1f3b80dcc6476ee2d928ae45d5334 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 4 Apr 2025 14:00:16 +0200 Subject: [PATCH 7/7] Restore comment --- Modules/_threadmodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 99107e13cedf87..f4c98ca39f6ee6 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -2415,6 +2415,7 @@ _thread__get_name_impl(PyObject *module) /*[clinic end generated code: output=20026e7ee3da3dd7 input=35cec676833d04c8]*/ { #ifndef MS_WINDOWS + // Linux and macOS are limited to respectively 16 and 64 bytes char name[100]; pthread_t thread = pthread_self(); #ifdef HAVE_PTHREAD_GETNAME_NP