Fixed #29393 -- Prevented infinite loop in ExceptionReporter.get_traceback_frames().
This commit is contained in:
parent
199025fa88
commit
3634560fa9
|
@ -397,6 +397,9 @@ class ExceptionReporter:
|
||||||
while exc_value:
|
while exc_value:
|
||||||
exceptions.append(exc_value)
|
exceptions.append(exc_value)
|
||||||
exc_value = explicit_or_implicit_cause(exc_value)
|
exc_value = explicit_or_implicit_cause(exc_value)
|
||||||
|
if exc_value in exceptions:
|
||||||
|
# Avoid infinite loop if there's a cyclic reference (#29393).
|
||||||
|
break
|
||||||
|
|
||||||
frames = []
|
frames = []
|
||||||
# No exceptions were supplied to ExceptionReporter
|
# No exceptions were supplied to ExceptionReporter
|
||||||
|
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import threading
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
@ -403,6 +404,44 @@ class ExceptionReporterTests(SimpleTestCase):
|
||||||
text = reporter.get_traceback_text()
|
text = reporter.get_traceback_text()
|
||||||
self.assertIn('"generated" in funcName', text)
|
self.assertIn('"generated" in funcName', text)
|
||||||
|
|
||||||
|
def test_reporting_frames_for_cyclic_reference(self):
|
||||||
|
try:
|
||||||
|
def test_func():
|
||||||
|
try:
|
||||||
|
raise RuntimeError('outer') from RuntimeError('inner')
|
||||||
|
except RuntimeError as exc:
|
||||||
|
raise exc.__cause__
|
||||||
|
test_func()
|
||||||
|
except Exception:
|
||||||
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
|
request = self.rf.get('/test_view/')
|
||||||
|
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
|
||||||
|
|
||||||
|
def generate_traceback_frames(*args, **kwargs):
|
||||||
|
nonlocal tb_frames
|
||||||
|
tb_frames = reporter.get_traceback_frames()
|
||||||
|
|
||||||
|
tb_frames = None
|
||||||
|
tb_generator = threading.Thread(target=generate_traceback_frames, daemon=True)
|
||||||
|
tb_generator.start()
|
||||||
|
tb_generator.join(timeout=5)
|
||||||
|
if tb_generator.is_alive():
|
||||||
|
# tb_generator is a daemon that runs until the main thread/process
|
||||||
|
# exits. This is resource heavy when running the full test suite.
|
||||||
|
# Setting the following values to None makes
|
||||||
|
# reporter.get_traceback_frames() exit early.
|
||||||
|
exc_value.__traceback__ = exc_value.__context__ = exc_value.__cause__ = None
|
||||||
|
tb_generator.join()
|
||||||
|
self.fail('Cyclic reference in Exception Reporter.get_traceback_frames()')
|
||||||
|
if tb_frames is None:
|
||||||
|
# can happen if the thread generating traceback got killed
|
||||||
|
# or exception while generating the traceback
|
||||||
|
self.fail('Traceback generation failed')
|
||||||
|
last_frame = tb_frames[-1]
|
||||||
|
self.assertIn('raise exc.__cause__', last_frame['context_line'])
|
||||||
|
self.assertEqual(last_frame['filename'], __file__)
|
||||||
|
self.assertEqual(last_frame['function'], 'test_func')
|
||||||
|
|
||||||
def test_request_and_message(self):
|
def test_request_and_message(self):
|
||||||
"A message can be provided in addition to a request"
|
"A message can be provided in addition to a request"
|
||||||
request = self.rf.get('/test_view/')
|
request = self.rf.get('/test_view/')
|
||||||
|
|
Loading…
Reference in New Issue