Skip to content

Commit

Permalink
bpo-44466: Faulthandler now detects the GC (GH-26823) (GH-26826)
Browse files Browse the repository at this point in the history
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 d191639)
  • Loading branch information
vstinner authored Jun 21, 2021
1 parent 7674c83 commit 9b0bbb9
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 14 deletions.
4 changes: 4 additions & 0 deletions Doc/library/faulthandler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--
Expand Down
67 changes: 53 additions & 14 deletions Lib/test/test_faulthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The :mod:`faulthandler` module now detects if a fatal error occurs during a
garbage collector collection. Patch by Victor Stinner.
4 changes: 4 additions & 0 deletions Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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++;
Expand Down

0 comments on commit 9b0bbb9

Please sign in to comment.