Skip to content

Commit 2a2270d

Browse files
authored
bpo-32703: Fix coroutine resource warning in case where there's an error (GH-5410)
The commit removes one unnecessary "if" clause in genobject.c. That "if" clause was masking un-awaited coroutines warnings just to make writing unittests more convenient.
1 parent b647d70 commit 2a2270d

File tree

3 files changed

+72
-39
lines changed

3 files changed

+72
-39
lines changed

Lib/test/test_coroutines.py

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -520,34 +520,38 @@ def test_func_3(self):
520520
async def foo():
521521
raise StopIteration
522522

523-
with silence_coro_gc():
524-
self.assertRegex(repr(foo()), '^<coroutine object.* at 0x.*>$')
523+
coro = foo()
524+
self.assertRegex(repr(coro), '^<coroutine object.* at 0x.*>$')
525+
coro.close()
525526

526527
def test_func_4(self):
527528
async def foo():
528529
raise StopIteration
530+
coro = foo()
529531

530532
check = lambda: self.assertRaisesRegex(
531533
TypeError, "'coroutine' object is not iterable")
532534

533535
with check():
534-
list(foo())
536+
list(coro)
535537

536538
with check():
537-
tuple(foo())
539+
tuple(coro)
538540

539541
with check():
540-
sum(foo())
542+
sum(coro)
541543

542544
with check():
543-
iter(foo())
545+
iter(coro)
544546

545-
with silence_coro_gc(), check():
546-
for i in foo():
547+
with check():
548+
for i in coro:
547549
pass
548550

549-
with silence_coro_gc(), check():
550-
[i for i in foo()]
551+
with check():
552+
[i for i in coro]
553+
554+
coro.close()
551555

552556
def test_func_5(self):
553557
@types.coroutine
@@ -560,8 +564,11 @@ async def foo():
560564
check = lambda: self.assertRaisesRegex(
561565
TypeError, "'coroutine' object is not iterable")
562566

567+
coro = foo()
563568
with check():
564-
for el in foo(): pass
569+
for el in coro:
570+
pass
571+
coro.close()
565572

566573
# the following should pass without an error
567574
for el in bar():
@@ -588,35 +595,53 @@ async def foo():
588595
def test_func_7(self):
589596
async def bar():
590597
return 10
598+
coro = bar()
591599

592600
def foo():
593-
yield from bar()
594-
595-
with silence_coro_gc(), self.assertRaisesRegex(
596-
TypeError,
597-
"cannot 'yield from' a coroutine object in a non-coroutine generator"):
601+
yield from coro
598602

603+
with self.assertRaisesRegex(
604+
TypeError,
605+
"cannot 'yield from' a coroutine object in "
606+
"a non-coroutine generator"):
599607
list(foo())
600608

609+
coro.close()
610+
601611
def test_func_8(self):
602612
@types.coroutine
603613
def bar():
604-
return (yield from foo())
614+
return (yield from coro)
605615

606616
async def foo():
607617
return 'spam'
608618

609-
self.assertEqual(run_async(bar()), ([], 'spam') )
619+
coro = foo()
620+
self.assertEqual(run_async(bar()), ([], 'spam'))
621+
coro.close()
610622

611623
def test_func_9(self):
612-
async def foo(): pass
624+
async def foo():
625+
pass
613626

614627
with self.assertWarnsRegex(
615-
RuntimeWarning, "coroutine '.*test_func_9.*foo' was never awaited"):
628+
RuntimeWarning,
629+
r"coroutine '.*test_func_9.*foo' was never awaited"):
616630

617631
foo()
618632
support.gc_collect()
619633

634+
with self.assertWarnsRegex(
635+
RuntimeWarning,
636+
r"coroutine '.*test_func_9.*foo' was never awaited"):
637+
638+
with self.assertRaises(TypeError):
639+
# See bpo-32703.
640+
for _ in foo():
641+
pass
642+
643+
support.gc_collect()
644+
620645
def test_func_10(self):
621646
N = 0
622647

@@ -674,11 +699,14 @@ async def g():
674699
def test_func_13(self):
675700
async def g():
676701
pass
702+
703+
coro = g()
677704
with self.assertRaisesRegex(
678-
TypeError,
679-
"can't send non-None value to a just-started coroutine"):
705+
TypeError,
706+
"can't send non-None value to a just-started coroutine"):
707+
coro.send('spam')
680708

681-
g().send('spam')
709+
coro.close()
682710

683711
def test_func_14(self):
684712
@types.coroutine
@@ -977,8 +1005,6 @@ async def bar():
9771005
return 42
9781006

9791007
async def foo():
980-
b = bar()
981-
9821008
db = {'b': lambda: wrap}
9831009

9841010
class DB:
@@ -1023,19 +1049,21 @@ async def foo2():
10231049
def test_await_12(self):
10241050
async def coro():
10251051
return 'spam'
1052+
c = coro()
10261053

10271054
class Awaitable:
10281055
def __await__(self):
1029-
return coro()
1056+
return c
10301057

10311058
async def foo():
10321059
return await Awaitable()
10331060

10341061
with self.assertRaisesRegex(
1035-
TypeError, r"__await__\(\) returned a coroutine"):
1036-
1062+
TypeError, r"__await__\(\) returned a coroutine"):
10371063
run_async(foo())
10381064

1065+
c.close()
1066+
10391067
def test_await_13(self):
10401068
class Awaitable:
10411069
def __await__(self):
@@ -1991,14 +2019,15 @@ def wrap(gen):
19912019
finally:
19922020
with self.assertWarns(DeprecationWarning):
19932021
sys.set_coroutine_wrapper(None)
2022+
f.close()
19942023

19952024
with self.assertWarns(DeprecationWarning):
19962025
self.assertIsNone(sys.get_coroutine_wrapper())
19972026

19982027
wrapped = None
1999-
with silence_coro_gc():
2000-
foo()
2028+
coro = foo()
20012029
self.assertFalse(wrapped)
2030+
coro.close()
20022031

20032032
def test_set_wrapper_2(self):
20042033
with self.assertWarns(DeprecationWarning):
@@ -2022,11 +2051,12 @@ async def wrap(coro):
20222051
sys.set_coroutine_wrapper(wrapper)
20232052
try:
20242053
with silence_coro_gc(), self.assertRaisesRegex(
2025-
RuntimeError,
2026-
r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
2027-
r"recursively wrap .* wrap .*"):
2054+
RuntimeError,
2055+
r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
2056+
r"recursively wrap .* wrap .*"):
20282057

20292058
foo()
2059+
20302060
finally:
20312061
with self.assertWarns(DeprecationWarning):
20322062
sys.set_coroutine_wrapper(None)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix coroutine's ResourceWarning when there's an active error set when it's
2+
being finalized.

Objects/genobject.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ _PyGen_Finalize(PyObject *self)
4444
PyObject *res = NULL;
4545
PyObject *error_type, *error_value, *error_traceback;
4646

47-
if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL)
47+
if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL) {
4848
/* Generator isn't paused, so no need to close */
4949
return;
50+
}
5051

5152
if (PyAsyncGen_CheckExact(self)) {
5253
PyAsyncGenObject *agen = (PyAsyncGenObject*)self;
@@ -75,18 +76,18 @@ _PyGen_Finalize(PyObject *self)
7576
issue a RuntimeWarning. */
7677
if (gen->gi_code != NULL &&
7778
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
78-
gen->gi_frame->f_lasti == -1) {
79-
if (!error_value) {
80-
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
81-
}
79+
gen->gi_frame->f_lasti == -1)
80+
{
81+
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
8282
}
8383
else {
8484
res = gen_close(gen, NULL);
8585
}
8686

8787
if (res == NULL) {
88-
if (PyErr_Occurred())
88+
if (PyErr_Occurred()) {
8989
PyErr_WriteUnraisable(self);
90+
}
9091
}
9192
else {
9293
Py_DECREF(res);

0 commit comments

Comments
 (0)