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.
This commit is contained in:
Yury Selivanov 2018-01-29 14:31:47 -05:00 committed by GitHub
parent b647d7039d
commit 2a2270db9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 40 deletions

View File

@ -520,34 +520,38 @@ class CoroutineTest(unittest.TestCase):
async def foo(): async def foo():
raise StopIteration raise StopIteration
with silence_coro_gc(): coro = foo()
self.assertRegex(repr(foo()), '^<coroutine object.* at 0x.*>$') self.assertRegex(repr(coro), '^<coroutine object.* at 0x.*>$')
coro.close()
def test_func_4(self): def test_func_4(self):
async def foo(): async def foo():
raise StopIteration raise StopIteration
coro = foo()
check = lambda: self.assertRaisesRegex( check = lambda: self.assertRaisesRegex(
TypeError, "'coroutine' object is not iterable") TypeError, "'coroutine' object is not iterable")
with check(): with check():
list(foo()) list(coro)
with check(): with check():
tuple(foo()) tuple(coro)
with check(): with check():
sum(foo()) sum(coro)
with check(): with check():
iter(foo()) iter(coro)
with silence_coro_gc(), check(): with check():
for i in foo(): for i in coro:
pass pass
with silence_coro_gc(), check(): with check():
[i for i in foo()] [i for i in coro]
coro.close()
def test_func_5(self): def test_func_5(self):
@types.coroutine @types.coroutine
@ -560,8 +564,11 @@ class CoroutineTest(unittest.TestCase):
check = lambda: self.assertRaisesRegex( check = lambda: self.assertRaisesRegex(
TypeError, "'coroutine' object is not iterable") TypeError, "'coroutine' object is not iterable")
coro = foo()
with check(): with check():
for el in foo(): pass for el in coro:
pass
coro.close()
# the following should pass without an error # the following should pass without an error
for el in bar(): for el in bar():
@ -588,35 +595,53 @@ class CoroutineTest(unittest.TestCase):
def test_func_7(self): def test_func_7(self):
async def bar(): async def bar():
return 10 return 10
coro = bar()
def foo(): def foo():
yield from bar() yield from coro
with silence_coro_gc(), self.assertRaisesRegex(
TypeError,
"cannot 'yield from' a coroutine object in a non-coroutine generator"):
with self.assertRaisesRegex(
TypeError,
"cannot 'yield from' a coroutine object in "
"a non-coroutine generator"):
list(foo()) list(foo())
coro.close()
def test_func_8(self): def test_func_8(self):
@types.coroutine @types.coroutine
def bar(): def bar():
return (yield from foo()) return (yield from coro)
async def foo(): async def foo():
return 'spam' return 'spam'
self.assertEqual(run_async(bar()), ([], 'spam') ) coro = foo()
self.assertEqual(run_async(bar()), ([], 'spam'))
coro.close()
def test_func_9(self): def test_func_9(self):
async def foo(): pass async def foo():
pass
with self.assertWarnsRegex( with self.assertWarnsRegex(
RuntimeWarning, "coroutine '.*test_func_9.*foo' was never awaited"): RuntimeWarning,
r"coroutine '.*test_func_9.*foo' was never awaited"):
foo() foo()
support.gc_collect() 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): def test_func_10(self):
N = 0 N = 0
@ -674,11 +699,14 @@ class CoroutineTest(unittest.TestCase):
def test_func_13(self): def test_func_13(self):
async def g(): async def g():
pass pass
with self.assertRaisesRegex(
TypeError,
"can't send non-None value to a just-started coroutine"):
g().send('spam') coro = g()
with self.assertRaisesRegex(
TypeError,
"can't send non-None value to a just-started coroutine"):
coro.send('spam')
coro.close()
def test_func_14(self): def test_func_14(self):
@types.coroutine @types.coroutine
@ -977,8 +1005,6 @@ class CoroutineTest(unittest.TestCase):
return 42 return 42
async def foo(): async def foo():
b = bar()
db = {'b': lambda: wrap} db = {'b': lambda: wrap}
class DB: class DB:
@ -1023,19 +1049,21 @@ class CoroutineTest(unittest.TestCase):
def test_await_12(self): def test_await_12(self):
async def coro(): async def coro():
return 'spam' return 'spam'
c = coro()
class Awaitable: class Awaitable:
def __await__(self): def __await__(self):
return coro() return c
async def foo(): async def foo():
return await Awaitable() return await Awaitable()
with self.assertRaisesRegex( with self.assertRaisesRegex(
TypeError, r"__await__\(\) returned a coroutine"): TypeError, r"__await__\(\) returned a coroutine"):
run_async(foo()) run_async(foo())
c.close()
def test_await_13(self): def test_await_13(self):
class Awaitable: class Awaitable:
def __await__(self): def __await__(self):
@ -1991,14 +2019,15 @@ class SysSetCoroWrapperTest(unittest.TestCase):
finally: finally:
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
sys.set_coroutine_wrapper(None) sys.set_coroutine_wrapper(None)
f.close()
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
self.assertIsNone(sys.get_coroutine_wrapper()) self.assertIsNone(sys.get_coroutine_wrapper())
wrapped = None wrapped = None
with silence_coro_gc(): coro = foo()
foo()
self.assertFalse(wrapped) self.assertFalse(wrapped)
coro.close()
def test_set_wrapper_2(self): def test_set_wrapper_2(self):
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
@ -2022,11 +2051,12 @@ class SysSetCoroWrapperTest(unittest.TestCase):
sys.set_coroutine_wrapper(wrapper) sys.set_coroutine_wrapper(wrapper)
try: try:
with silence_coro_gc(), self.assertRaisesRegex( with silence_coro_gc(), self.assertRaisesRegex(
RuntimeError, RuntimeError,
r"coroutine wrapper.*\.wrapper at 0x.*attempted to " r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
r"recursively wrap .* wrap .*"): r"recursively wrap .* wrap .*"):
foo() foo()
finally: finally:
with self.assertWarns(DeprecationWarning): with self.assertWarns(DeprecationWarning):
sys.set_coroutine_wrapper(None) sys.set_coroutine_wrapper(None)

View File

@ -0,0 +1,2 @@
Fix coroutine's ResourceWarning when there's an active error set when it's
being finalized.

View File

@ -44,9 +44,10 @@ _PyGen_Finalize(PyObject *self)
PyObject *res = NULL; PyObject *res = NULL;
PyObject *error_type, *error_value, *error_traceback; 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 */ /* Generator isn't paused, so no need to close */
return; return;
}
if (PyAsyncGen_CheckExact(self)) { if (PyAsyncGen_CheckExact(self)) {
PyAsyncGenObject *agen = (PyAsyncGenObject*)self; PyAsyncGenObject *agen = (PyAsyncGenObject*)self;
@ -75,18 +76,18 @@ _PyGen_Finalize(PyObject *self)
issue a RuntimeWarning. */ issue a RuntimeWarning. */
if (gen->gi_code != NULL && if (gen->gi_code != NULL &&
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE && ((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
gen->gi_frame->f_lasti == -1) { gen->gi_frame->f_lasti == -1)
if (!error_value) { {
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen); _PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
}
} }
else { else {
res = gen_close(gen, NULL); res = gen_close(gen, NULL);
} }
if (res == NULL) { if (res == NULL) {
if (PyErr_Occurred()) if (PyErr_Occurred()) {
PyErr_WriteUnraisable(self); PyErr_WriteUnraisable(self);
}
} }
else { else {
Py_DECREF(res); Py_DECREF(res);