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:
parent
b647d7039d
commit
2a2270db9b
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix coroutine's ResourceWarning when there's an active error set when it's
|
||||||
|
being finalized.
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue