From 2a2270db9be9bdac5ffd2d50929bf905e7391a06 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Mon, 29 Jan 2018 14:31:47 -0500 Subject: [PATCH] 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. --- Lib/test/test_coroutines.py | 98 ++++++++++++------- .../2018-01-29-01-15-17.bpo-32703.mwrF4-.rst | 2 + Objects/genobject.c | 13 +-- 3 files changed, 73 insertions(+), 40 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 19a3444b6ad..f3d77192ae7 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -520,34 +520,38 @@ class CoroutineTest(unittest.TestCase): async def foo(): raise StopIteration - with silence_coro_gc(): - self.assertRegex(repr(foo()), '^$') + coro = foo() + self.assertRegex(repr(coro), '^$') + 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 @@ -560,8 +564,11 @@ class CoroutineTest(unittest.TestCase): 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(): @@ -588,35 +595,53 @@ class CoroutineTest(unittest.TestCase): 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 @@ -674,11 +699,14 @@ class CoroutineTest(unittest.TestCase): def test_func_13(self): async def g(): 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): @types.coroutine @@ -977,8 +1005,6 @@ class CoroutineTest(unittest.TestCase): return 42 async def foo(): - b = bar() - db = {'b': lambda: wrap} class DB: @@ -1023,19 +1049,21 @@ class CoroutineTest(unittest.TestCase): 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): @@ -1991,14 +2019,15 @@ class SysSetCoroWrapperTest(unittest.TestCase): 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): @@ -2022,11 +2051,12 @@ class SysSetCoroWrapperTest(unittest.TestCase): 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) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst new file mode 100644 index 00000000000..cfd166b64c2 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst @@ -0,0 +1,2 @@ +Fix coroutine's ResourceWarning when there's an active error set when it's +being finalized. diff --git a/Objects/genobject.c b/Objects/genobject.c index 1fdb57c8ce8..88b03c5ef31 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -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; @@ -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);