From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:12:28 +0000 (+0200) Subject: [3.12] gh-110378: Close invalid generators in contextmanager and asynccontextmanager... X-Git-Tag: v3.12.1~336 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2fc80814bf55445fb2cc56b25ec54d8e308e3408;p=thirdparty%2FPython%2Fcpython.git [3.12] gh-110378: Close invalid generators in contextmanager and asynccontextmanager (GH-110499) (#110588) contextmanager and asynccontextmanager context managers now close an invalid underlying generator object that yields more then one value. (cherry picked from commit 96fed66a65097eac2dc528ce29c9ba676bb07689) Co-authored-by: Serhiy Storchaka --- diff --git a/Lib/contextlib.py b/Lib/contextlib.py index b5acbcb9e6d7..2efed2d9ec47 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -145,7 +145,10 @@ class _GeneratorContextManager( except StopIteration: return False else: - raise RuntimeError("generator didn't stop") + try: + raise RuntimeError("generator didn't stop") + finally: + self.gen.close() else: if value is None: # Need to force instantiation so we can reliably @@ -187,7 +190,10 @@ class _GeneratorContextManager( raise exc.__traceback__ = traceback 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( _GeneratorContextManagerBase, @@ -212,7 +218,10 @@ class _AsyncGeneratorContextManager( except StopAsyncIteration: return False else: - raise RuntimeError("generator didn't stop") + try: + raise RuntimeError("generator didn't stop") + finally: + await self.gen.aclose() else: if value is None: # Need to force instantiation so we can reliably @@ -254,7 +263,10 @@ class _AsyncGeneratorContextManager( raise exc.__traceback__ = traceback 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): diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 0f8351ab8108..b15829414e5d 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -157,9 +157,24 @@ class ContextManagerTestCase(unittest.TestCase): yield ctx = whoo() ctx.__enter__() - self.assertRaises( - RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None - ) + with self.assertRaises(RuntimeError): + 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): state = [] diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 3d43ed0fcab1..e519dffee78b 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -204,6 +204,9 @@ class AsyncContextManagerTestCase(unittest.TestCase): await ctx.__aenter__() with self.assertRaises(RuntimeError): await ctx.__aexit__(TypeError, TypeError('foo'), None) + if support.check_impl_detail(cpython=True): + # The "gen" attribute is an implementation detail. + self.assertFalse(ctx.gen.ag_suspended) @_async_test async def test_contextmanager_trap_no_yield(self): @@ -225,6 +228,9 @@ class AsyncContextManagerTestCase(unittest.TestCase): await ctx.__aenter__() with self.assertRaises(RuntimeError): await ctx.__aexit__(None, None, None) + if support.check_impl_detail(cpython=True): + # The "gen" attribute is an implementation detail. + self.assertFalse(ctx.gen.ag_suspended) @_async_test async def test_contextmanager_non_normalised(self): diff --git a/Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst b/Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst new file mode 100644 index 000000000000..ef5395fc3c64 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst @@ -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.