Skip to content

Commit 7fc542c

Browse files
GH-89091: raise RuntimeWarning for unawaited async generator methods (#104611)
1 parent 46857d0 commit 7fc542c

9 files changed

+96
-2
lines changed

Include/internal/pycore_global_objects_fini_generated.h

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

+3
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ struct _Py_global_strings {
269269
STRUCT_FOR_ID(a)
270270
STRUCT_FOR_ID(abs_tol)
271271
STRUCT_FOR_ID(access)
272+
STRUCT_FOR_ID(aclose)
272273
STRUCT_FOR_ID(add)
273274
STRUCT_FOR_ID(add_done_callback)
274275
STRUCT_FOR_ID(after_in_child)
@@ -282,7 +283,9 @@ struct _Py_global_strings {
282283
STRUCT_FOR_ID(arguments)
283284
STRUCT_FOR_ID(argv)
284285
STRUCT_FOR_ID(as_integer_ratio)
286+
STRUCT_FOR_ID(asend)
285287
STRUCT_FOR_ID(ast)
288+
STRUCT_FOR_ID(athrow)
286289
STRUCT_FOR_ID(attribute)
287290
STRUCT_FOR_ID(authorizer_callback)
288291
STRUCT_FOR_ID(autocommit)

Include/internal/pycore_runtime_init_generated.h

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_warnings.h

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern int _PyWarnings_InitState(PyInterpreterState *interp);
2222
PyAPI_FUNC(PyObject*) _PyWarnings_Init(void);
2323

2424
extern void _PyErr_WarnUnawaitedCoroutine(PyObject *coro);
25+
extern void _PyErr_WarnUnawaitedAgenMethod(PyAsyncGenObject *agen, PyObject *method);
2526

2627
#ifdef __cplusplus
2728
}

Lib/test/test_asyncgen.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,9 @@ async def gen():
415415
self.assertIsInstance(g.ag_frame, types.FrameType)
416416
self.assertFalse(g.ag_running)
417417
self.assertIsInstance(g.ag_code, types.CodeType)
418-
419-
self.assertTrue(inspect.isawaitable(g.aclose()))
418+
aclose = g.aclose()
419+
self.assertTrue(inspect.isawaitable(aclose))
420+
aclose.close()
420421

421422

422423
class AsyncGenAsyncioTest(unittest.TestCase):
@@ -1693,5 +1694,38 @@ async def run():
16931694
self.loop.run_until_complete(run())
16941695

16951696

1697+
class TestUnawaitedWarnings(unittest.TestCase):
1698+
def test_asend(self):
1699+
async def gen():
1700+
yield 1
1701+
1702+
msg = f"coroutine method 'asend' of '{gen.__qualname__}' was never awaited"
1703+
with self.assertWarnsRegex(RuntimeWarning, msg):
1704+
g = gen()
1705+
g.asend(None)
1706+
gc_collect()
1707+
1708+
def test_athrow(self):
1709+
async def gen():
1710+
yield 1
1711+
1712+
msg = f"coroutine method 'athrow' of '{gen.__qualname__}' was never awaited"
1713+
with self.assertWarnsRegex(RuntimeWarning, msg):
1714+
g = gen()
1715+
g.athrow(RuntimeError)
1716+
gc_collect()
1717+
1718+
def test_aclose(self):
1719+
async def gen():
1720+
yield 1
1721+
1722+
msg = f"coroutine method 'aclose' of '{gen.__qualname__}' was never awaited"
1723+
with self.assertWarnsRegex(RuntimeWarning, msg):
1724+
g = gen()
1725+
g.aclose()
1726+
gc_collect()
1727+
1728+
1729+
16961730
if __name__ == "__main__":
16971731
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Raise :exc:`RuntimeWarning` for unawaited async generator methods like :meth:`~agen.asend`, :meth:`~agen.athrow` and :meth:`~agen.aclose`. Patch by Kumar Aditya.

Objects/genobject.c

+26
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,10 @@ async_gen_unwrap_value(PyAsyncGenObject *gen, PyObject *result)
17351735
static void
17361736
async_gen_asend_dealloc(PyAsyncGenASend *o)
17371737
{
1738+
if (PyObject_CallFinalizerFromDealloc((PyObject *)o)) {
1739+
return;
1740+
}
1741+
17381742
_PyObject_GC_UNTRACK((PyObject *)o);
17391743
Py_CLEAR(o->ags_gen);
17401744
Py_CLEAR(o->ags_sendval);
@@ -1839,6 +1843,13 @@ async_gen_asend_close(PyAsyncGenASend *o, PyObject *args)
18391843
Py_RETURN_NONE;
18401844
}
18411845

1846+
static void
1847+
async_gen_asend_finalize(PyAsyncGenASend *o)
1848+
{
1849+
if (o->ags_state == AWAITABLE_STATE_INIT) {
1850+
_PyErr_WarnUnawaitedAgenMethod(o->ags_gen, &_Py_ID(asend));
1851+
}
1852+
}
18421853

18431854
static PyMethodDef async_gen_asend_methods[] = {
18441855
{"send", (PyCFunction)async_gen_asend_send, METH_O, send_doc},
@@ -1896,6 +1907,7 @@ PyTypeObject _PyAsyncGenASend_Type = {
18961907
0, /* tp_init */
18971908
0, /* tp_alloc */
18981909
0, /* tp_new */
1910+
.tp_finalize = (destructor)async_gen_asend_finalize,
18991911
};
19001912

19011913

@@ -2053,6 +2065,10 @@ _PyAsyncGenValueWrapperNew(PyThreadState *tstate, PyObject *val)
20532065
static void
20542066
async_gen_athrow_dealloc(PyAsyncGenAThrow *o)
20552067
{
2068+
if (PyObject_CallFinalizerFromDealloc((PyObject *)o)) {
2069+
return;
2070+
}
2071+
20562072
_PyObject_GC_UNTRACK((PyObject *)o);
20572073
Py_CLEAR(o->agt_gen);
20582074
Py_CLEAR(o->agt_args);
@@ -2256,6 +2272,15 @@ async_gen_athrow_close(PyAsyncGenAThrow *o, PyObject *args)
22562272
}
22572273

22582274

2275+
static void
2276+
async_gen_athrow_finalize(PyAsyncGenAThrow *o)
2277+
{
2278+
if (o->agt_state == AWAITABLE_STATE_INIT) {
2279+
PyObject *method = o->agt_args ? &_Py_ID(athrow) : &_Py_ID(aclose);
2280+
_PyErr_WarnUnawaitedAgenMethod(o->agt_gen, method);
2281+
}
2282+
}
2283+
22592284
static PyMethodDef async_gen_athrow_methods[] = {
22602285
{"send", (PyCFunction)async_gen_athrow_send, METH_O, send_doc},
22612286
{"throw", _PyCFunction_CAST(async_gen_athrow_throw),
@@ -2313,6 +2338,7 @@ PyTypeObject _PyAsyncGenAThrow_Type = {
23132338
0, /* tp_init */
23142339
0, /* tp_alloc */
23152340
0, /* tp_new */
2341+
.tp_finalize = (destructor)async_gen_athrow_finalize,
23162342
};
23172343

23182344

Python/_warnings.c

+14
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,20 @@ PyErr_WarnExplicitFormat(PyObject *category,
13651365
return ret;
13661366
}
13671367

1368+
void
1369+
_PyErr_WarnUnawaitedAgenMethod(PyAsyncGenObject *agen, PyObject *method)
1370+
{
1371+
PyObject *exc = PyErr_GetRaisedException();
1372+
if (_PyErr_WarnFormat((PyObject *)agen, PyExc_RuntimeWarning, 1,
1373+
"coroutine method %R of %R was never awaited",
1374+
method, agen->ag_qualname) < 0)
1375+
{
1376+
PyErr_WriteUnraisable((PyObject *)agen);
1377+
}
1378+
PyErr_SetRaisedException(exc);
1379+
}
1380+
1381+
13681382
void
13691383
_PyErr_WarnUnawaitedCoroutine(PyObject *coro)
13701384
{

0 commit comments

Comments
 (0)