Skip to content

Commit d191639

Browse files
authored
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).
1 parent fb68791 commit d191639

File tree

5 files changed

+70
-14
lines changed

5 files changed

+70
-14
lines changed

Doc/library/faulthandler.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ Fault handler state
7676
.. versionchanged:: 3.6
7777
On Windows, a handler for Windows exception is also installed.
7878

79+
.. versionchanged:: 3.10
80+
The dump now mentions if a garbage collector collection is running
81+
if *all_threads* is true.
82+
7983
.. function:: disable()
8084

8185
Disable the fault handler: uninstall the signal handlers installed by

Doc/whatsnew/3.10.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,13 @@ Add *encoding* and *errors* parameters in :func:`fileinput.input` and
10031003
when *mode* is "r" and file is compressed, like uncompressed files.
10041004
(Contributed by Inada Naoki in :issue:`5758`.)
10051005
1006+
faulthandler
1007+
------------
1008+
1009+
The :mod:`faulthandler` module now detects if a fatal error occurs during a
1010+
garbage collector collection.
1011+
(Contributed by Victor Stinner in :issue:`44466`.)
1012+
10061013
gc
10071014
--
10081015

Lib/test/test_faulthandler.py

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,12 @@ def get_output(self, code, filename=None, fd=None):
8989
output = output.decode('ascii', 'backslashreplace')
9090
return output.splitlines(), exitcode
9191

92-
def check_error(self, code, line_number, fatal_error, *,
92+
def check_error(self, code, lineno, fatal_error, *,
9393
filename=None, all_threads=True, other_regex=None,
9494
fd=None, know_current_thread=True,
95-
py_fatal_error=False):
95+
py_fatal_error=False,
96+
garbage_collecting=False,
97+
function='<module>'):
9698
"""
9799
Check that the fault handler for fatal errors is enabled and check the
98100
traceback from the child process output.
@@ -106,20 +108,21 @@ def check_error(self, code, line_number, fatal_error, *,
106108
header = 'Thread 0x[0-9a-f]+'
107109
else:
108110
header = 'Stack'
109-
regex = r"""
110-
(?m)^{fatal_error}
111-
112-
{header} \(most recent call first\):
113-
File "<string>", line {lineno} in <module>
114-
"""
111+
regex = [f'^{fatal_error}']
115112
if py_fatal_error:
116-
fatal_error += "\nPython runtime state: initialized"
117-
regex = dedent(regex).format(
118-
lineno=line_number,
119-
fatal_error=fatal_error,
120-
header=header).strip()
113+
regex.append("Python runtime state: initialized")
114+
regex.append('')
115+
regex.append(fr'{header} \(most recent call first\):')
116+
if garbage_collecting:
117+
regex.append(' Garbage-collecting')
118+
regex.append(fr' File "<string>", line {lineno} in {function}')
119+
regex = '\n'.join(regex)
120+
121121
if other_regex:
122-
regex += '|' + other_regex
122+
regex = f'(?:{regex}|{other_regex})'
123+
124+
# Enable MULTILINE flag
125+
regex = f'(?m){regex}'
123126
output, exitcode = self.get_output(code, filename=filename, fd=fd)
124127
output = '\n'.join(output)
125128
self.assertRegex(output, regex)
@@ -168,6 +171,42 @@ def test_sigsegv(self):
168171
3,
169172
'Segmentation fault')
170173

174+
@skip_segfault_on_android
175+
def test_gc(self):
176+
# bpo-44466: Detect if the GC is running
177+
self.check_fatal_error("""
178+
import faulthandler
179+
import gc
180+
import sys
181+
182+
faulthandler.enable()
183+
184+
class RefCycle:
185+
def __del__(self):
186+
faulthandler._sigsegv()
187+
188+
# create a reference cycle which triggers a fatal
189+
# error in a destructor
190+
a = RefCycle()
191+
b = RefCycle()
192+
a.b = b
193+
b.a = a
194+
195+
# Delete the objects, not the cycle
196+
a = None
197+
b = None
198+
199+
# Break the reference cycle: call __del__()
200+
gc.collect()
201+
202+
# Should not reach this line
203+
print("exit", file=sys.stderr)
204+
""",
205+
9,
206+
'Segmentation fault',
207+
function='__del__',
208+
garbage_collecting=True)
209+
171210
def test_fatal_error_c_thread(self):
172211
self.check_fatal_error("""
173212
import faulthandler
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :mod:`faulthandler` module now detects if a fatal error occurs during a
2+
garbage collector collection. Patch by Victor Stinner.

Python/traceback.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Python.h"
55

66
#include "code.h"
7+
#include "pycore_interp.h" // PyInterpreterState.gc
78
#include "frameobject.h" // PyFrame_GetBack()
89
#include "structmember.h" // PyMemberDef
910
#include "osdefs.h" // SEP
@@ -914,6 +915,9 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
914915
break;
915916
}
916917
write_thread_id(fd, tstate, tstate == current_tstate);
918+
if (tstate == current_tstate && tstate->interp->gc.collecting) {
919+
PUTS(fd, " Garbage-collecting\n");
920+
}
917921
dump_traceback(fd, tstate, 0);
918922
tstate = PyThreadState_Next(tstate);
919923
nthreads++;

0 commit comments

Comments
 (0)