Skip to content

Commit 2b3a418

Browse files
miss-islingtonserhiy-storchakaambv
authored
[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 96fed66) Co-authored-by: Serhiy Storchaka <[email protected]> Co-authored-by: Łukasz Langa <[email protected]>
1 parent d099def commit 2b3a418

File tree

3 files changed

+37
-7
lines changed

3 files changed

+37
-7
lines changed

Lib/contextlib.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,10 @@ def __exit__(self, typ, value, traceback):
145145
except StopIteration:
146146
return False
147147
else:
148-
raise RuntimeError("generator didn't stop")
148+
try:
149+
raise RuntimeError("generator didn't stop")
150+
finally:
151+
self.gen.close()
149152
else:
150153
if value is None:
151154
# Need to force instantiation so we can reliably
@@ -187,7 +190,10 @@ def __exit__(self, typ, value, traceback):
187190
raise
188191
exc.__traceback__ = traceback
189192
return False
190-
raise RuntimeError("generator didn't stop after throw()")
193+
try:
194+
raise RuntimeError("generator didn't stop after throw()")
195+
finally:
196+
self.gen.close()
191197

192198
class _AsyncGeneratorContextManager(
193199
_GeneratorContextManagerBase,
@@ -212,7 +218,10 @@ async def __aexit__(self, typ, value, traceback):
212218
except StopAsyncIteration:
213219
return False
214220
else:
215-
raise RuntimeError("generator didn't stop")
221+
try:
222+
raise RuntimeError("generator didn't stop")
223+
finally:
224+
await self.gen.aclose()
216225
else:
217226
if value is None:
218227
# Need to force instantiation so we can reliably
@@ -254,7 +263,10 @@ async def __aexit__(self, typ, value, traceback):
254263
raise
255264
exc.__traceback__ = traceback
256265
return False
257-
raise RuntimeError("generator didn't stop after athrow()")
266+
try:
267+
raise RuntimeError("generator didn't stop after athrow()")
268+
finally:
269+
await self.gen.aclose()
258270

259271

260272
def contextmanager(func):

Lib/test/test_contextlib.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,24 @@ def whoo():
156156
yield
157157
ctx = whoo()
158158
ctx.__enter__()
159-
self.assertRaises(
160-
RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None
161-
)
159+
with self.assertRaises(RuntimeError):
160+
ctx.__exit__(TypeError, TypeError("foo"), None)
161+
if support.check_impl_detail(cpython=True):
162+
# The "gen" attribute is an implementation detail.
163+
self.assertFalse(ctx.gen.gi_suspended)
164+
165+
def test_contextmanager_trap_second_yield(self):
166+
@contextmanager
167+
def whoo():
168+
yield
169+
yield
170+
ctx = whoo()
171+
ctx.__enter__()
172+
with self.assertRaises(RuntimeError):
173+
ctx.__exit__(None, None, None)
174+
if support.check_impl_detail(cpython=True):
175+
# The "gen" attribute is an implementation detail.
176+
self.assertFalse(ctx.gen.gi_suspended)
162177

163178
def test_contextmanager_except(self):
164179
state = []
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`~contextlib.contextmanager` and
2+
:func:`~contextlib.asynccontextmanager` context managers now close an invalid
3+
underlying generator object that yields more then one value.

0 commit comments

Comments
 (0)