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

The faulthandler module now detects if a fatal error occurs during a
garbage collector collection (only if all_threads is true).
This commit is contained in:
Victor Stinner 2021-06-21 13:15:40 +02:00 committed by GitHub
parent fb68791a26
commit d19163912b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 14 deletions

View file

@ -76,6 +76,10 @@ Fault handler state
.. versionchanged:: 3.6 .. versionchanged:: 3.6
On Windows, a handler for Windows exception is also installed. 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() .. function:: disable()
Disable the fault handler: uninstall the signal handlers installed by Disable the fault handler: uninstall the signal handlers installed by

View 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. when *mode* is "r" and file is compressed, like uncompressed files.
(Contributed by Inada Naoki in :issue:`5758`.) (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 gc
-- --

View file

@ -89,10 +89,12 @@ class FaultHandlerTests(unittest.TestCase):
output = output.decode('ascii', 'backslashreplace') output = output.decode('ascii', 'backslashreplace')
return output.splitlines(), exitcode 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, filename=None, all_threads=True, other_regex=None,
fd=None, know_current_thread=True, 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 Check that the fault handler for fatal errors is enabled and check the
traceback from the child process output. traceback from the child process output.
@ -106,20 +108,21 @@ class FaultHandlerTests(unittest.TestCase):
header = 'Thread 0x[0-9a-f]+' header = 'Thread 0x[0-9a-f]+'
else: else:
header = 'Stack' header = 'Stack'
regex = r""" regex = [f'^{fatal_error}']
(?m)^{fatal_error}
{header} \(most recent call first\):
File "<string>", line {lineno} in <module>
"""
if py_fatal_error: if py_fatal_error:
fatal_error += "\nPython runtime state: initialized" regex.append("Python runtime state: initialized")
regex = dedent(regex).format( regex.append('')
lineno=line_number, regex.append(fr'{header} \(most recent call first\):')
fatal_error=fatal_error, if garbage_collecting:
header=header).strip() regex.append(' Garbage-collecting')
regex.append(fr' File "<string>", line {lineno} in {function}')
regex = '\n'.join(regex)
if other_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, exitcode = self.get_output(code, filename=filename, fd=fd)
output = '\n'.join(output) output = '\n'.join(output)
self.assertRegex(output, regex) self.assertRegex(output, regex)
@ -168,6 +171,42 @@ class FaultHandlerTests(unittest.TestCase):
3, 3,
'Segmentation fault') '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): def test_fatal_error_c_thread(self):
self.check_fatal_error(""" self.check_fatal_error("""
import faulthandler import faulthandler

View file

@ -0,0 +1,2 @@
The :mod:`faulthandler` module now detects if a fatal error occurs during a
garbage collector collection. Patch by Victor Stinner.

View file

@ -4,6 +4,7 @@
#include "Python.h" #include "Python.h"
#include "code.h" #include "code.h"
#include "pycore_interp.h" // PyInterpreterState.gc
#include "frameobject.h" // PyFrame_GetBack() #include "frameobject.h" // PyFrame_GetBack()
#include "structmember.h" // PyMemberDef #include "structmember.h" // PyMemberDef
#include "osdefs.h" // SEP #include "osdefs.h" // SEP
@ -914,6 +915,9 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
break; break;
} }
write_thread_id(fd, tstate, tstate == current_tstate); 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); dump_traceback(fd, tstate, 0);
tstate = PyThreadState_Next(tstate); tstate = PyThreadState_Next(tstate);
nthreads++; nthreads++;