Skip to content

Commit

Permalink
bpo-32703: Fix coroutine resource warning in case where there's an er…
Browse files Browse the repository at this point in the history
…ror (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.
  • Loading branch information
1st1 authored Jan 29, 2018
1 parent b647d70 commit 2a2270d
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 39 deletions.
96 changes: 63 additions & 33 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,34 +520,38 @@ def test_func_3(self):
async def foo():
raise StopIteration

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

def test_func_4(self):
async def foo():
raise StopIteration
coro = foo()

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

with check():
list(foo())
list(coro)

with check():
tuple(foo())
tuple(coro)

with check():
sum(foo())
sum(coro)

with check():
iter(foo())
iter(coro)

with silence_coro_gc(), check():
for i in foo():
with check():
for i in coro:
pass

with silence_coro_gc(), check():
[i for i in foo()]
with check():
[i for i in coro]

coro.close()

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

coro = foo()
with check():
for el in foo(): pass
for el in coro:
pass
coro.close()

# the following should pass without an error
for el in bar():
Expand All @@ -588,35 +595,53 @@ async def foo():
def test_func_7(self):
async def bar():
return 10
coro = bar()

def foo():
yield from bar()

with silence_coro_gc(), self.assertRaisesRegex(
TypeError,
"cannot 'yield from' a coroutine object in a non-coroutine generator"):
yield from coro

with self.assertRaisesRegex(
TypeError,
"cannot 'yield from' a coroutine object in "
"a non-coroutine generator"):
list(foo())

coro.close()

def test_func_8(self):
@types.coroutine
def bar():
return (yield from foo())
return (yield from coro)

async def foo():
return 'spam'

self.assertEqual(run_async(bar()), ([], 'spam') )
coro = foo()
self.assertEqual(run_async(bar()), ([], 'spam'))
coro.close()

def test_func_9(self):
async def foo(): pass
async def foo():
pass

with self.assertWarnsRegex(
RuntimeWarning, "coroutine '.*test_func_9.*foo' was never awaited"):
RuntimeWarning,
r"coroutine '.*test_func_9.*foo' was never awaited"):

foo()
support.gc_collect()

with self.assertWarnsRegex(
RuntimeWarning,
r"coroutine '.*test_func_9.*foo' was never awaited"):

with self.assertRaises(TypeError):
# See bpo-32703.
for _ in foo():
pass

support.gc_collect()

def test_func_10(self):
N = 0

Expand Down Expand Up @@ -674,11 +699,14 @@ async def g():
def test_func_13(self):
async def g():
pass

coro = g()
with self.assertRaisesRegex(
TypeError,
"can't send non-None value to a just-started coroutine"):
TypeError,
"can't send non-None value to a just-started coroutine"):
coro.send('spam')

g().send('spam')
coro.close()

def test_func_14(self):
@types.coroutine
Expand Down Expand Up @@ -977,8 +1005,6 @@ async def bar():
return 42

async def foo():
b = bar()

db = {'b': lambda: wrap}

class DB:
Expand Down Expand Up @@ -1023,19 +1049,21 @@ async def foo2():
def test_await_12(self):
async def coro():
return 'spam'
c = coro()

class Awaitable:
def __await__(self):
return coro()
return c

async def foo():
return await Awaitable()

with self.assertRaisesRegex(
TypeError, r"__await__\(\) returned a coroutine"):

TypeError, r"__await__\(\) returned a coroutine"):
run_async(foo())

c.close()

def test_await_13(self):
class Awaitable:
def __await__(self):
Expand Down Expand Up @@ -1991,14 +2019,15 @@ def wrap(gen):
finally:
with self.assertWarns(DeprecationWarning):
sys.set_coroutine_wrapper(None)
f.close()

with self.assertWarns(DeprecationWarning):
self.assertIsNone(sys.get_coroutine_wrapper())

wrapped = None
with silence_coro_gc():
foo()
coro = foo()
self.assertFalse(wrapped)
coro.close()

def test_set_wrapper_2(self):
with self.assertWarns(DeprecationWarning):
Expand All @@ -2022,11 +2051,12 @@ async def wrap(coro):
sys.set_coroutine_wrapper(wrapper)
try:
with silence_coro_gc(), self.assertRaisesRegex(
RuntimeError,
r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
r"recursively wrap .* wrap .*"):
RuntimeError,
r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
r"recursively wrap .* wrap .*"):

foo()

finally:
with self.assertWarns(DeprecationWarning):
sys.set_coroutine_wrapper(None)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix coroutine's ResourceWarning when there's an active error set when it's
being finalized.
13 changes: 7 additions & 6 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ _PyGen_Finalize(PyObject *self)
PyObject *res = NULL;
PyObject *error_type, *error_value, *error_traceback;

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

if (PyAsyncGen_CheckExact(self)) {
PyAsyncGenObject *agen = (PyAsyncGenObject*)self;
Expand Down Expand Up @@ -75,18 +76,18 @@ _PyGen_Finalize(PyObject *self)
issue a RuntimeWarning. */
if (gen->gi_code != NULL &&
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
gen->gi_frame->f_lasti == -1) {
if (!error_value) {
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
}
gen->gi_frame->f_lasti == -1)
{
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
}
else {
res = gen_close(gen, NULL);
}

if (res == NULL) {
if (PyErr_Occurred())
if (PyErr_Occurred()) {
PyErr_WriteUnraisable(self);
}
}
else {
Py_DECREF(res);
Expand Down

0 comments on commit 2a2270d

Please sign in to comment.