File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -298,11 +298,19 @@ def __exit__(self, *exc_details):
298298 # we were actually nesting multiple with statements
299299 frame_exc = sys .exc_info ()[1 ]
300300 def _fix_exception_context (new_exc , old_exc ):
301+ # Context isn't what we want, so find the end of the chain
301302 while 1 :
302303 exc_context = new_exc .__context__
303- if exc_context in (None , frame_exc ):
304+ if exc_context is old_exc :
305+ # Context is already set correctly (see issue 20317)
306+ return
307+ if exc_context is None or exc_context is frame_exc :
304308 break
309+ details = id (new_exc ), id (old_exc ), id (exc_context )
310+ raise Exception (str (details ))
305311 new_exc = exc_context
312+ # Change the end of the chain to point to the exception
313+ # we expect it to reference
306314 new_exc .__context__ = old_exc
307315
308316 # Callbacks are invoked in LIFO order to match the behaviour of
Original file line number Diff line number Diff line change @@ -626,6 +626,29 @@ def suppress_exc(*exc_details):
626626 else :
627627 self .fail ("Expected KeyError, but no exception was raised" )
628628
629+ def test_exit_exception_with_correct_context (self ):
630+ # http://bugs.python.org/issue20317
631+ @contextmanager
632+ def gets_the_context_right ():
633+ try :
634+ yield 6
635+ finally :
636+ 1 / 0
637+
638+ # The contextmanager already fixes the context, so prior to the
639+ # fix, ExitStack would try to fix it *again* and get into an
640+ # infinite self-referential loop
641+ try :
642+ with ExitStack () as stack :
643+ stack .enter_context (gets_the_context_right ())
644+ stack .enter_context (gets_the_context_right ())
645+ stack .enter_context (gets_the_context_right ())
646+ except ZeroDivisionError as exc :
647+ self .assertIsInstance (exc .__context__ , ZeroDivisionError )
648+ self .assertIsInstance (exc .__context__ .__context__ , ZeroDivisionError )
649+ self .assertIsNone (exc .__context__ .__context__ .__context__ )
650+
651+
629652 def test_body_exception_suppress (self ):
630653 def suppress_exc (* exc_details ):
631654 return True
Original file line number Diff line number Diff line change @@ -37,6 +37,12 @@ Library
3737 microsecond is now rounded to one millisecond, instead of being rounded to
3838 zero.
3939
40+ - Issue #20317: ExitStack.__exit__ could create a self-referential loop if an
41+ exception raised by a cleanup operation already had its context set
42+ correctly (for example, by the @contextmanager decorator). The infinite
43+ loop this caused is now avoided by checking if the expected context is
44+ already set before trying to fix it.
45+
4046- Issue #20311: select.epoll.poll() now rounds the timeout away from zero,
4147 instead of rounding towards zero. For example, a timeout of one microsecond
4248 is now rounded to one millisecond, instead of being rounded to zero.
You can’t perform that action at this time.
0 commit comments