mirror of
https://github.com/python/cpython.git
synced 2025-09-11 19:27:07 +00:00
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:
parent
fb68791a26
commit
d19163912b
5 changed files with 70 additions and 14 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
--
|
--
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,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++;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue