From c9356feef28e6dfc4dc32830d3427a5ae0e426e2 Mon Sep 17 00:00:00 2001
From: Peter Bierma <zintensitydev@gmail.com>
Date: Thu, 2 Jan 2025 13:56:01 -0500
Subject: gh-128400: Stop-the-world when manually calling `faulthandler`
 (GH-128422)

---
 Lib/test/test_faulthandler.py                      | 30 +++++++++++++++++++++-
 .../2025-01-02-13-05-16.gh-issue-128400.5N43fF.rst |  2 ++
 Modules/faulthandler.c                             |  5 ++++
 3 files changed, 36 insertions(+), 1 deletion(-)
 create mode 100644 Misc/NEWS.d/next/Library/2025-01-02-13-05-16.gh-issue-128400.5N43fF.rst

diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index 60815be..fd56dee 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -7,7 +7,7 @@ import signal
 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 @@ class FaultHandlerTests(unittest.TestCase):
         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 0000000..4033dea
--- /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 b62362f..2d16028 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;
-- 
cgit v0.12