[3.11] gh-110378: Close invalid generators in contextmanager and asynccontextmanager (GH-110499) (#110589)

contextmanager and asynccontextmanager context managers now close an invalid
underlying generator object that yields more then one value.
(cherry picked from commit 96fed66a65)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
Miss Islington (bot) 2023-10-10 11:12:52 +02:00 committed by GitHub
parent d099defc63
commit 2b3a418279
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 7 deletions

View file

@ -145,7 +145,10 @@ class _GeneratorContextManager(
except StopIteration: except StopIteration:
return False return False
else: else:
raise RuntimeError("generator didn't stop") try:
raise RuntimeError("generator didn't stop")
finally:
self.gen.close()
else: else:
if value is None: if value is None:
# Need to force instantiation so we can reliably # Need to force instantiation so we can reliably
@ -187,7 +190,10 @@ class _GeneratorContextManager(
raise raise
exc.__traceback__ = traceback exc.__traceback__ = traceback
return False return False
raise RuntimeError("generator didn't stop after throw()") try:
raise RuntimeError("generator didn't stop after throw()")
finally:
self.gen.close()
class _AsyncGeneratorContextManager( class _AsyncGeneratorContextManager(
_GeneratorContextManagerBase, _GeneratorContextManagerBase,
@ -212,7 +218,10 @@ class _AsyncGeneratorContextManager(
except StopAsyncIteration: except StopAsyncIteration:
return False return False
else: else:
raise RuntimeError("generator didn't stop") try:
raise RuntimeError("generator didn't stop")
finally:
await self.gen.aclose()
else: else:
if value is None: if value is None:
# Need to force instantiation so we can reliably # Need to force instantiation so we can reliably
@ -254,7 +263,10 @@ class _AsyncGeneratorContextManager(
raise raise
exc.__traceback__ = traceback exc.__traceback__ = traceback
return False return False
raise RuntimeError("generator didn't stop after athrow()") try:
raise RuntimeError("generator didn't stop after athrow()")
finally:
await self.gen.aclose()
def contextmanager(func): def contextmanager(func):

View file

@ -156,9 +156,24 @@ class ContextManagerTestCase(unittest.TestCase):
yield yield
ctx = whoo() ctx = whoo()
ctx.__enter__() ctx.__enter__()
self.assertRaises( with self.assertRaises(RuntimeError):
RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None ctx.__exit__(TypeError, TypeError("foo"), None)
) if support.check_impl_detail(cpython=True):
# The "gen" attribute is an implementation detail.
self.assertFalse(ctx.gen.gi_suspended)
def test_contextmanager_trap_second_yield(self):
@contextmanager
def whoo():
yield
yield
ctx = whoo()
ctx.__enter__()
with self.assertRaises(RuntimeError):
ctx.__exit__(None, None, None)
if support.check_impl_detail(cpython=True):
# The "gen" attribute is an implementation detail.
self.assertFalse(ctx.gen.gi_suspended)
def test_contextmanager_except(self): def test_contextmanager_except(self):
state = [] state = []

View file

@ -0,0 +1,3 @@
:func:`~contextlib.contextmanager` and
:func:`~contextlib.asynccontextmanager` context managers now close an invalid
underlying generator object that yields more then one value.