Skip to content

bpo-32703: Fix coroutine resource warning in case where there's an error #5410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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