diff --git a/django/views/debug.py b/django/views/debug.py index bc95cbf6b0..c85b86c913 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -2,6 +2,7 @@ import functools import re import sys import types +import warnings from pathlib import Path from django.conf import settings @@ -28,6 +29,10 @@ DEBUG_ENGINE = Engine( CURRENT_DIR = Path(__file__).parent +class ExceptionCycleWarning(UserWarning): + pass + + class CallableSettingWrapper: """ Object to wrap callable appearing in settings. @@ -401,6 +406,11 @@ class ExceptionReporter: exceptions.append(exc_value) exc_value = explicit_or_implicit_cause(exc_value) if exc_value in exceptions: + warnings.warn( + "Cycle in the exception chain detected: exception '%s' " + "encountered again." % exc_value, + ExceptionCycleWarning, + ) # Avoid infinite loop if there's a cyclic reference (#29393). break diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index a305b77a1b..c3ae2cc600 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -23,8 +23,8 @@ from django.utils.functional import SimpleLazyObject from django.utils.regex_helper import _lazy_re_compile from django.utils.safestring import mark_safe from django.views.debug import ( - CallableSettingWrapper, ExceptionReporter, Path as DebugPath, - SafeExceptionReporterFilter, default_urlconf, + CallableSettingWrapper, ExceptionCycleWarning, ExceptionReporter, + Path as DebugPath, SafeExceptionReporterFilter, default_urlconf, get_default_exception_reporter_filter, technical_404_response, technical_500_response, ) @@ -518,7 +518,12 @@ class ExceptionReporterTests(SimpleTestCase): tb_frames = None tb_generator = threading.Thread(target=generate_traceback_frames, daemon=True) - tb_generator.start() + msg = ( + "Cycle in the exception chain detected: exception 'inner' " + "encountered again." + ) + with self.assertWarnsMessage(ExceptionCycleWarning, msg): + 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