[Python-checkins] bpo-44466: Faulthandler now detects the GC (GH-26823) (GH-26826)

vstinner webhook-mailer at python.org
Mon Jun 21 08:23:18 EDT 2021


https://github.com/python/cpython/commit/9b0bbb9143d15507aae0ff3afeb05969178b306c
commit: 9b0bbb9143d15507aae0ff3afeb05969178b306c
branch: 3.10
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2021-06-21T14:23:13+02:00
summary:

bpo-44466: Faulthandler now detects the GC (GH-26823) (GH-26826)

The faulthandler module now detects if a fatal error occurs during a
garbage collector collection (only if all_threads is true).

(cherry picked from commit d19163912bfc790283724f05328bd31e4e65003d)

files:
A Misc/NEWS.d/next/Library/2021-06-21-12-43-04.bpo-44466.NSm6mv.rst
M Doc/library/faulthandler.rst
M Doc/whatsnew/3.10.rst
M Lib/test/test_faulthandler.py
M Python/traceback.c

diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst
index 59274c1dd7ec3..be0912376bd8e 100644
--- a/Doc/library/faulthandler.rst
+++ b/Doc/library/faulthandler.rst
@@ -76,6 +76,10 @@ Fault handler state
    .. versionchanged:: 3.6
       On Windows, a handler for Windows exception is also installed.
 
+   .. versionchanged:: 3.10
+      The dump now mentions if a garbage collector collection is running
+      if *all_threads* is true.
+
 .. function:: disable()
 
    Disable the fault handler: uninstall the signal handlers installed by
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index 249f0e98e06ac..987d125a0114c 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -1003,6 +1003,13 @@ Add *encoding* and *errors* parameters in :func:`fileinput.input` and
 when *mode* is "r" and file is compressed, like uncompressed files.
 (Contributed by Inada Naoki in :issue:`5758`.)
 
+faulthandler
+------------
+
+The :mod:`faulthandler` module now detects if a fatal error occurs during a
+garbage collector collection.
+(Contributed by Victor Stinner in :issue:`44466`.)
+
 gc
 --
 
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index 29a70857930c1..ee3f41a108a14 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -89,10 +89,12 @@ def get_output(self, code, filename=None, fd=None):
             output = output.decode('ascii', 'backslashreplace')
         return output.splitlines(), exitcode
 
-    def check_error(self, code, line_number, fatal_error, *,
+    def check_error(self, code, lineno, fatal_error, *,
                     filename=None, all_threads=True, other_regex=None,
                     fd=None, know_current_thread=True,
-                    py_fatal_error=False):
+                    py_fatal_error=False,
+                    garbage_collecting=False,
+                    function='<module>'):
         """
         Check that the fault handler for fatal errors is enabled and check the
         traceback from the child process output.
@@ -106,20 +108,21 @@ def check_error(self, code, line_number, fatal_error, *,
                 header = 'Thread 0x[0-9a-f]+'
         else:
             header = 'Stack'
-        regex = r"""
-            (?m)^{fatal_error}
-
-            {header} \(most recent call first\):
-              File "<string>", line {lineno} in <module>
-            """
+        regex = [f'^{fatal_error}']
         if py_fatal_error:
-            fatal_error += "\nPython runtime state: initialized"
-        regex = dedent(regex).format(
-            lineno=line_number,
-            fatal_error=fatal_error,
-            header=header).strip()
+            regex.append("Python runtime state: initialized")
+        regex.append('')
+        regex.append(fr'{header} \(most recent call first\):')
+        if garbage_collecting:
+            regex.append('  Garbage-collecting')
+        regex.append(fr'  File "<string>", line {lineno} in {function}')
+        regex = '\n'.join(regex)
+
         if other_regex:
-            regex += '|' + other_regex
+            regex = f'(?:{regex}|{other_regex})'
+
+        # Enable MULTILINE flag
+        regex = f'(?m){regex}'
         output, exitcode = self.get_output(code, filename=filename, fd=fd)
         output = '\n'.join(output)
         self.assertRegex(output, regex)
@@ -168,6 +171,42 @@ def test_sigsegv(self):
             3,
             'Segmentation fault')
 
+    @skip_segfault_on_android
+    def test_gc(self):
+        # bpo-44466: Detect if the GC is running
+        self.check_fatal_error("""
+            import faulthandler
+            import gc
+            import sys
+
+            faulthandler.enable()
+
+            class RefCycle:
+                def __del__(self):
+                    faulthandler._sigsegv()
+
+            # create a reference cycle which triggers a fatal
+            # error in a destructor
+            a = RefCycle()
+            b = RefCycle()
+            a.b = b
+            b.a = a
+
+            # Delete the objects, not the cycle
+            a = None
+            b = None
+
+            # Break the reference cycle: call __del__()
+            gc.collect()
+
+            # Should not reach this line
+            print("exit", file=sys.stderr)
+            """,
+            9,
+            'Segmentation fault',
+            function='__del__',
+            garbage_collecting=True)
+
     def test_fatal_error_c_thread(self):
         self.check_fatal_error("""
             import faulthandler
diff --git a/Misc/NEWS.d/next/Library/2021-06-21-12-43-04.bpo-44466.NSm6mv.rst b/Misc/NEWS.d/next/Library/2021-06-21-12-43-04.bpo-44466.NSm6mv.rst
new file mode 100644
index 0000000000000..69de3edb5a7f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-06-21-12-43-04.bpo-44466.NSm6mv.rst
@@ -0,0 +1,2 @@
+The :mod:`faulthandler` module now detects if a fatal error occurs during a
+garbage collector collection. Patch by Victor Stinner.
diff --git a/Python/traceback.c b/Python/traceback.c
index 470324b1afd83..f7dc5ad686476 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -4,6 +4,7 @@
 #include "Python.h"
 
 #include "code.h"
+#include "pycore_interp.h"        // PyInterpreterState.gc
 #include "frameobject.h"          // PyFrame_GetBack()
 #include "structmember.h"         // PyMemberDef
 #include "osdefs.h"               // SEP
@@ -914,6 +915,9 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
             break;
         }
         write_thread_id(fd, tstate, tstate == current_tstate);
+        if (tstate == current_tstate && tstate->interp->gc.collecting) {
+            PUTS(fd, "  Garbage-collecting\n");
+        }
         dump_traceback(fd, tstate, 0);
         tstate = PyThreadState_Next(tstate);
         nthreads++;



More information about the Python-checkins mailing list