]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.11] gh-110378: Close invalid generators in contextmanager and asynccontextmanager...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 10 Oct 2023 09:12:52 +0000 (11:12 +0200)
committerGitHub <noreply@github.com>
Tue, 10 Oct 2023 09:12:52 +0000 (11:12 +0200)
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 <storchaka@gmail.com>
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
Lib/contextlib.py
Lib/test/test_contextlib.py
Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst [new file with mode: 0644]

index 58e9a498878d01b2eaaf18fd5d497248720e43d9..4a338f5c637db5f6cac09892cb0f63bd505d0293 100644 (file)
@@ -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):
index ec06785b5667a6c1d3e9cc5a7fa7f5a8e4726b88..093f2593a4a464b0e89ab634c4842b0a8c8f8ccc 100644 (file)
@@ -156,9 +156,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/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 (file)
index 0000000..ef5395f
--- /dev/null
@@ -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.