https://github.com/python/cpython/commit/ebadfd1039a321a142e0e09e040bf8e724b... commit: ebadfd1039a321a142e0e09e040bf8e724bb5905 branch: 3.13 author: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> committer: colesbury <colesbury@gmail.com> date: 2025-01-02T19:21:16Z summary: [3.13] gh-128400: Stop-the-world when manually calling `faulthandler` (GH-128422) (GH-128423) (cherry picked from commit c9356feef28e6dfc4dc32830d3427a5ae0e426e2) Co-authored-by: Peter Bierma <zintensitydev@gmail.com> files: A Misc/NEWS.d/next/Library/2025-01-02-13-05-16.gh-issue-128400.5N43fF.rst M Lib/test/test_faulthandler.py M Modules/faulthandler.c diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 60815be96e14eb..fd56dee5d842ac 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -7,7 +7,7 @@ import subprocess import sys from test import support -from test.support import os_helper, script_helper, is_android, MS_WINDOWS +from test.support import os_helper, script_helper, is_android, MS_WINDOWS, threading_helper import tempfile import unittest from textwrap import dedent @@ -896,6 +896,34 @@ def test_cancel_later_without_dump_traceback_later(self): self.assertEqual(output, []) self.assertEqual(exitcode, 0) + @threading_helper.requires_working_threading() + @unittest.skipUnless(support.Py_GIL_DISABLED, "only meaningful if the GIL is disabled") + def test_free_threaded_dump_traceback(self): + # gh-128400: Other threads need to be paused to invoke faulthandler + code = dedent(""" + import faulthandler + from threading import Thread, Event + + class Waiter(Thread): + def __init__(self): + Thread.__init__(self) + self.running = Event() + self.stop = Event() + + def run(self): + self.running.set() + self.stop.wait() + + for _ in range(100): + waiter = Waiter() + waiter.start() + waiter.running.wait() + faulthandler.dump_traceback(all_threads=True) + waiter.stop.set() + waiter.join() + """) + _, exitcode = self.get_output(code) + self.assertEqual(exitcode, 0) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-01-02-13-05-16.gh-issue-128400.5N43fF.rst b/Misc/NEWS.d/next/Library/2025-01-02-13-05-16.gh-issue-128400.5N43fF.rst new file mode 100644 index 00000000000000..4033dea4eaf7bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-02-13-05-16.gh-issue-128400.5N43fF.rst @@ -0,0 +1,2 @@ +Fix crash when using :func:`faulthandler.dump_traceback` while other threads +are active on the :term:`free threaded <free threading>` build. diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index b62362f277797e..2d16028a5232d0 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -237,7 +237,12 @@ faulthandler_dump_traceback_py(PyObject *self, return NULL; if (all_threads) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + /* gh-128400: Accessing other thread states while they're running + * isn't safe if those threads are running. */ + _PyEval_StopTheWorld(interp); errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate); + _PyEval_StartTheWorld(interp); if (errmsg != NULL) { PyErr_SetString(PyExc_RuntimeError, errmsg); return NULL;