From e30ec7b049c8cec73d5b3f07823246459e2b8fa0 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Wed, 17 May 2023 08:07:13 +0000 Subject: [PATCH 1/4] implement --- .../pycore_global_objects_fini_generated.h | 3 ++ Include/internal/pycore_global_strings.h | 3 ++ .../internal/pycore_runtime_init_generated.h | 3 ++ .../internal/pycore_unicodeobject_generated.h | 9 +++++ Include/internal/pycore_warnings.h | 1 + Lib/test/test_asyncgen.py | 36 +++++++++++++++++-- Objects/genobject.c | 26 ++++++++++++++ Python/_warnings.c | 18 ++++++++-- 8 files changed, 95 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 24a268ac8c43ec..5e1917e3abb458 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -781,6 +781,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(a)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(abs_tol)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(access)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(aclose)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(add)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(add_done_callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_child)); @@ -794,7 +795,9 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(arguments)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(argv)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(as_integer_ratio)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(asend)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ast)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(athrow)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(attribute)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(authorizer_callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(autocommit)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index c1005d05155271..c2209937bf3f20 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -269,6 +269,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(a) STRUCT_FOR_ID(abs_tol) STRUCT_FOR_ID(access) + STRUCT_FOR_ID(aclose) STRUCT_FOR_ID(add) STRUCT_FOR_ID(add_done_callback) STRUCT_FOR_ID(after_in_child) @@ -282,7 +283,9 @@ struct _Py_global_strings { STRUCT_FOR_ID(arguments) STRUCT_FOR_ID(argv) STRUCT_FOR_ID(as_integer_ratio) + STRUCT_FOR_ID(asend) STRUCT_FOR_ID(ast) + STRUCT_FOR_ID(athrow) STRUCT_FOR_ID(attribute) STRUCT_FOR_ID(authorizer_callback) STRUCT_FOR_ID(autocommit) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index ff1dee6eacfe5d..377a4488bbb308 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -775,6 +775,7 @@ extern "C" { INIT_ID(a), \ INIT_ID(abs_tol), \ INIT_ID(access), \ + INIT_ID(aclose), \ INIT_ID(add), \ INIT_ID(add_done_callback), \ INIT_ID(after_in_child), \ @@ -788,7 +789,9 @@ extern "C" { INIT_ID(arguments), \ INIT_ID(argv), \ INIT_ID(as_integer_ratio), \ + INIT_ID(asend), \ INIT_ID(ast), \ + INIT_ID(athrow), \ INIT_ID(attribute), \ INIT_ID(authorizer_callback), \ INIT_ID(autocommit), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index ba6b37f1bf55b3..c4a6d8017da080 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -648,6 +648,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(access); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(aclose); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(add); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); @@ -687,9 +690,15 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(as_integer_ratio); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(asend); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(ast); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(athrow); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(attribute); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Include/internal/pycore_warnings.h b/Include/internal/pycore_warnings.h index efb4f1cd7eac80..452d6b96ce4f1c 100644 --- a/Include/internal/pycore_warnings.h +++ b/Include/internal/pycore_warnings.h @@ -22,6 +22,7 @@ extern int _PyWarnings_InitState(PyInterpreterState *interp); PyAPI_FUNC(PyObject*) _PyWarnings_Init(void); extern void _PyErr_WarnUnawaitedCoroutine(PyObject *coro); +extern void _PyErr_WarnUnawaitedAgenMethod(PyAsyncGenObject *agen, PyObject *method); #ifdef __cplusplus } diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index 09e4010b0e53d6..de39bbc2e3c199 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -415,8 +415,9 @@ async def gen(): self.assertIsInstance(g.ag_frame, types.FrameType) self.assertFalse(g.ag_running) self.assertIsInstance(g.ag_code, types.CodeType) - - self.assertTrue(inspect.isawaitable(g.aclose())) + aclose = g.aclose() + self.assertTrue(inspect.isawaitable(aclose)) + aclose.close() class AsyncGenAsyncioTest(unittest.TestCase): @@ -1612,6 +1613,7 @@ async def main(): break asyncio.run(main()) + asyncio.run(asyncio.sleep(1)) self.assertEqual([], messages) @@ -1693,5 +1695,35 @@ async def run(): self.loop.run_until_complete(run()) +class TestUnawaitedWarnings: + def test_asend(self): + async def gen(): + yield 1 + + msg = f"coroutine method 'asend' of 'gen' was never awaited" + with self.assertWarnsRegex(RuntimeWarning, msg): + g = gen() + g.asend(None) + + def test_athrow(self): + async def gen(): + yield 1 + + msg = f"coroutine method 'athrow' of 'gen' was never awaited" + with self.assertWarnsRegex(RuntimeWarning, msg): + g = gen() + g.athrow(RuntimeError) + + def test_aclose(self): + async def gen(): + yield 1 + + msg = f"coroutine method 'aclose' of 'gen' was never awaited" + with self.assertWarnsRegex(RuntimeWarning, msg): + g = gen() + g.aclose() + + + if __name__ == "__main__": unittest.main() diff --git a/Objects/genobject.c b/Objects/genobject.c index 9252c654934565..0eca932e1d3094 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1730,6 +1730,10 @@ async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result) static void async_gen_asend_dealloc(PyAsyncGenASend *o) { + if (PyObject_CallFinalizerFromDealloc((PyObject *)o)) { + return; + } + _PyObject_GC_UNTRACK((PyObject *)o); Py_CLEAR(o->ags_gen); Py_CLEAR(o->ags_sendval); @@ -1834,6 +1838,13 @@ async_gen_asend_close(PyAsyncGenASend *o, PyObject *args) Py_RETURN_NONE; } +static void +async_gen_asend_finalize(PyAsyncGenASend *o) +{ + if (o->ags_state == AWAITABLE_STATE_INIT) { + _PyErr_WarnUnawaitedAgenMethod(o->ags_gen, &_Py_ID(asend)); + } +} static PyMethodDef async_gen_asend_methods[] = { {"send", (PyCFunction)async_gen_asend_send, METH_O, send_doc}, @@ -1891,6 +1902,7 @@ PyTypeObject _PyAsyncGenASend_Type = { 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ + .tp_finalize = (destructor)async_gen_asend_finalize, }; @@ -2048,6 +2060,10 @@ _PyAsyncGenValueWrapperNew(PyThreadState *tstate, PyObject *val) static void async_gen_athrow_dealloc(PyAsyncGenAThrow *o) { + if (PyObject_CallFinalizerFromDealloc((PyObject *)o)) { + return; + } + _PyObject_GC_UNTRACK((PyObject *)o); Py_CLEAR(o->agt_gen); Py_CLEAR(o->agt_args); @@ -2251,6 +2267,15 @@ async_gen_athrow_close(PyAsyncGenAThrow *o, PyObject *args) } +static void +async_gen_athrow_finalize(PyAsyncGenAThrow *o) +{ + if (o->agt_state == AWAITABLE_STATE_INIT) { + PyObject *method = o->agt_args ? &_Py_ID(athrow) : &_Py_ID(aclose); + _PyErr_WarnUnawaitedAgenMethod(o->agt_gen, method); + } +} + static PyMethodDef async_gen_athrow_methods[] = { {"send", (PyCFunction)async_gen_athrow_send, METH_O, send_doc}, {"throw", _PyCFunction_CAST(async_gen_athrow_throw), @@ -2308,6 +2333,7 @@ PyTypeObject _PyAsyncGenAThrow_Type = { 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ + .tp_finalize = (destructor)async_gen_athrow_finalize, }; diff --git a/Python/_warnings.c b/Python/_warnings.c index dec658680241ed..c832b1609812ae 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1365,6 +1365,20 @@ PyErr_WarnExplicitFormat(PyObject *category, return ret; } +void +_PyErr_WarnUnawaitedAgenMethod(PyAsyncGenObject *agen, PyObject *method) +{ + PyObject *exc = PyErr_GetRaisedException(); + if (_PyErr_WarnFormat((PyObject *)agen, PyExc_RuntimeWarning, 1, + "coroutine method %R of %R was never awaited", + method, agen->ag_qualname) < 0) + { + PyErr_WriteUnraisable((PyObject *)agen); + } + PyErr_SetRaisedException(exc); +} + + void _PyErr_WarnUnawaitedCoroutine(PyObject *coro) { @@ -1405,8 +1419,8 @@ _PyErr_WarnUnawaitedCoroutine(PyObject *coro) } if (!warned) { if (_PyErr_WarnFormat(coro, PyExc_RuntimeWarning, 1, - "coroutine '%S' was never awaited", - ((PyCoroObject *)coro)->cr_qualname) < 0) + "coroutine '%R' was never awaited", + coro) < 0) { PyErr_WriteUnraisable(coro); } From 5597573f64267c63ac34c4fe9208212dde6f4a66 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 18 May 2023 18:06:08 +0530 Subject: [PATCH 2/4] revert unrelated change --- Python/_warnings.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/_warnings.c b/Python/_warnings.c index c832b1609812ae..54fa5c56572f4d 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -1419,8 +1419,8 @@ _PyErr_WarnUnawaitedCoroutine(PyObject *coro) } if (!warned) { if (_PyErr_WarnFormat(coro, PyExc_RuntimeWarning, 1, - "coroutine '%R' was never awaited", - coro) < 0) + "coroutine '%S' was never awaited", + ((PyCoroObject *)coro)->cr_qualname) < 0) { PyErr_WriteUnraisable(coro); } From eea9a772e12170bde974795d2b8af4932896beb0 Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Thu, 18 May 2023 12:40:29 +0000 Subject: [PATCH 3/4] fix tests --- Lib/test/test_asyncgen.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index de39bbc2e3c199..4f00558770dafd 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -1613,7 +1613,6 @@ async def main(): break asyncio.run(main()) - asyncio.run(asyncio.sleep(1)) self.assertEqual([], messages) @@ -1695,33 +1694,36 @@ async def run(): self.loop.run_until_complete(run()) -class TestUnawaitedWarnings: +class TestUnawaitedWarnings(unittest.TestCase): def test_asend(self): async def gen(): yield 1 - msg = f"coroutine method 'asend' of 'gen' was never awaited" + msg = f"coroutine method 'asend' of '{gen.__qualname__}' was never awaited" with self.assertWarnsRegex(RuntimeWarning, msg): g = gen() g.asend(None) + gc_collect() def test_athrow(self): async def gen(): yield 1 - msg = f"coroutine method 'athrow' of 'gen' was never awaited" + msg = f"coroutine method 'athrow' of '{gen.__qualname__}' was never awaited" with self.assertWarnsRegex(RuntimeWarning, msg): g = gen() g.athrow(RuntimeError) + gc_collect() def test_aclose(self): async def gen(): yield 1 - msg = f"coroutine method 'aclose' of 'gen' was never awaited" + msg = f"coroutine method 'aclose' of '{gen.__qualname__}' was never awaited" with self.assertWarnsRegex(RuntimeWarning, msg): g = gen() g.aclose() + gc_collect() From f99c9889a6cde0cafd98612be0a82a87b009bbde Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 12:48:41 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2023-05-18-12-48-39.gh-issue-89091.FDzRcW.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-05-18-12-48-39.gh-issue-89091.FDzRcW.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-18-12-48-39.gh-issue-89091.FDzRcW.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-18-12-48-39.gh-issue-89091.FDzRcW.rst new file mode 100644 index 00000000000000..084ea708997ef3 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-18-12-48-39.gh-issue-89091.FDzRcW.rst @@ -0,0 +1 @@ +Raise :exc:`RuntimeWarning` for unawaited async generator methods like :meth:`~agen.asend`, :meth:`~agen.athrow` and :meth:`~agen.aclose`. Patch by Kumar Aditya.