]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-44466: Faulthandler now detects the GC (GH-26823) (GH-26826)
authorVictor Stinner <vstinner@python.org>
Mon, 21 Jun 2021 12:23:13 +0000 (14:23 +0200)
committerGitHub <noreply@github.com>
Mon, 21 Jun 2021 12:23:13 +0000 (14:23 +0200)
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)

Doc/library/faulthandler.rst
Doc/whatsnew/3.10.rst
Lib/test/test_faulthandler.py
Misc/NEWS.d/next/Library/2021-06-21-12-43-04.bpo-44466.NSm6mv.rst [new file with mode: 0644]
Python/traceback.c

index 59274c1dd7ec354a8b0094e71fcd281c1fa63173..be0912376bd8ef300a349a85bbdbc53d9fefd44c 100644 (file)
@@ -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
index 249f0e98e06ac0b1a859ce8157fb5b4afbed3786..987d125a0114c38208243d42ed0385b0ae72e9f4 100644 (file)
@@ -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
 --
 
index 29a70857930c1e414d6983914987a54070b9fcd0..ee3f41a108a14c8a75c4dad5ae88945f8e995ed2 100644 (file)
@@ -89,10 +89,12 @@ class FaultHandlerTests(unittest.TestCase):
             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 @@ class FaultHandlerTests(unittest.TestCase):
                 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 @@ class FaultHandlerTests(unittest.TestCase):
             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 (file)
index 0000000..69de3ed
--- /dev/null
@@ -0,0 +1,2 @@
+The :mod:`faulthandler` module now detects if a fatal error occurs during a
+garbage collector collection. Patch by Victor Stinner.
index 470324b1afd83f5ba9da4203b0fc49818ccf99ae..f7dc5ad6864762d6475dfb750a5537f2f5efc48c 100644 (file)
@@ -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++;