Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

Commit f32067f

Browse files
gh-160837: Fix asyncio call graph truncation at async generators (#160839)
Co-authored-by: Timofey Ivankov <playboicartina@gmail.com>
1 parent 5ffbce7 commit f32067f

6 files changed

Lines changed: 116 additions & 2 deletions

File tree

‎Doc/library/inspect.rst‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
284284
| | | ``AGEN_SUSPENDED``, or |
285285
| | | ``AGEN_CLOSED`` |
286286
+-----------------+-------------------+---------------------------+
287+
| async generator | aga_gen | async generator driven by |
288+
| awaitable | | the awaitable returned |
289+
| | | from ``asend()``, |
290+
| | | ``athrow()``, or |
291+
| | | ``aclose()`` |
292+
+-----------------+-------------------+---------------------------+
287293
| coroutine | __name__ | name |
288294
+-----------------+-------------------+---------------------------+
289295
| | __qualname__ | qualified name |
@@ -360,6 +366,11 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
360366
Add ``gi_state`` attribute to generators, ``cr_state`` attribute to
361367
coroutines, and ``ag_state`` attribute to async generators.
362368

369+
.. versionchanged:: 3.16
370+
371+
Add ``aga_gen`` attribute to the awaitables returned by the ``asend()``,
372+
``athrow()`` and ``aclose()`` methods of async generators.
373+
363374
.. function:: getmembers(object[, predicate])
364375

365376
Return all the members of an object in a list of ``(name, value)``

‎Lib/asyncio/graph.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ def _build_graph_for_future(
7070
if coro.gi_frame is not None:
7171
st.append(FrameCallGraphEntry(coro.gi_frame))
7272
coro = coro.gi_yieldfrom
73+
elif hasattr(coro, 'aga_gen'):
74+
# An asend()/athrow() awaitable: step over it to its generator
75+
coro = coro.aga_gen
7376
else:
7477
break
7578

‎Lib/test/test_asyncgen.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,23 @@ async def gen():
399399
r"cannot reuse already awaited __anext__\(\)/asend\(\)"):
400400
an.send(None)
401401

402+
def test_async_gen_asend_athrow_aga_gen(self):
403+
# gh-160837: asend()/athrow() awaitables expose the generator they drive
404+
async def gen():
405+
yield 1
406+
407+
g = gen()
408+
asend = g.asend(None)
409+
athrow = g.athrow(ValueError)
410+
try:
411+
self.assertIs(asend.aga_gen, g)
412+
self.assertIs(athrow.aga_gen, g)
413+
with self.assertRaises(AttributeError):
414+
asend.aga_gen = None
415+
finally:
416+
asend.close()
417+
athrow.close()
418+
402419
def test_async_gen_asend_throw_concurrent_with_send(self):
403420
import types
404421

‎Lib/test/test_asyncio/test_graph.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,77 @@ async def probe(t):
430430
['T<probe>', ['a _cancel_and_wait', 'a wait_for', 'a probe'], []],
431431
])
432432

433+
async def test_stack_async_gen_asend(self):
434+
# gh-160837: an async_generator_asend must not truncate the stack
435+
fut = asyncio.Future()
436+
stack_for_consumer = None
437+
438+
async def deep():
439+
await fut
440+
441+
async def gen():
442+
await deep()
443+
yield 1
444+
445+
async def consumer():
446+
async for _ in gen():
447+
pass
448+
449+
async def main():
450+
nonlocal stack_for_consumer
451+
452+
async with asyncio.TaskGroup() as g:
453+
t = g.create_task(consumer(), name='consumer')
454+
for _ in range(5):
455+
await asyncio.sleep(0)
456+
457+
stack_for_consumer = capture_test_stack(fut=t)
458+
fut.set_result(None)
459+
460+
await main()
461+
462+
self.assertEqual(stack_for_consumer[0][:2], [
463+
'T<consumer>',
464+
['a deep', 'ag gen', 'a consumer'],
465+
])
466+
467+
async def test_stack_async_gen_aclose(self):
468+
# gh-160837: an async_generator_athrow must not truncate the stack
469+
fut = asyncio.Future()
470+
stack_for_consumer = None
471+
472+
async def deep():
473+
await fut
474+
475+
async def gen():
476+
try:
477+
yield 1
478+
finally:
479+
await deep()
480+
481+
async def consumer():
482+
agen = gen()
483+
await anext(agen)
484+
await agen.aclose()
485+
486+
async def main():
487+
nonlocal stack_for_consumer
488+
489+
async with asyncio.TaskGroup() as g:
490+
t = g.create_task(consumer(), name='consumer')
491+
for _ in range(5):
492+
await asyncio.sleep(0)
493+
494+
stack_for_consumer = capture_test_stack(fut=t)
495+
fut.set_result(None)
496+
497+
await main()
498+
499+
self.assertEqual(stack_for_consumer[0][:2], [
500+
'T<consumer>',
501+
['a deep', 'ag gen', 'a consumer'],
502+
])
503+
433504
async def test_stack_gather(self):
434505

435506
stack_for_deep = None
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`asyncio.print_call_graph` no longer truncates the call stack of a
2+
task suspended inside an asynchronous generator.

‎Objects/genobject.c‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,6 +2194,11 @@ async_gen_asend_finalize(PyObject *self)
21942194
}
21952195
}
21962196

2197+
static PyMemberDef async_gen_asend_memberlist[] = {
2198+
{"aga_gen", Py_T_OBJECT_EX, offsetof(PyAsyncGenASend, ags_gen), Py_READONLY},
2199+
{NULL} /* Sentinel */
2200+
};
2201+
21972202
static PyMethodDef async_gen_asend_methods[] = {
21982203
{"send", async_gen_asend_send, METH_O, send_doc},
21992204
{"throw", _PyCFunction_CAST(async_gen_asend_throw), METH_FASTCALL, throw_doc},
@@ -2238,7 +2243,7 @@ PyTypeObject _PyAsyncGenASend_Type = {
22382243
PyObject_SelfIter, /* tp_iter */
22392244
async_gen_asend_iternext, /* tp_iternext */
22402245
async_gen_asend_methods, /* tp_methods */
2241-
0, /* tp_members */
2246+
async_gen_asend_memberlist, /* tp_members */
22422247
0, /* tp_getset */
22432248
0, /* tp_base */
22442249
0, /* tp_dict */
@@ -2657,6 +2662,11 @@ async_gen_athrow_finalize(PyObject *op)
26572662
}
26582663
}
26592664

2665+
static PyMemberDef async_gen_athrow_memberlist[] = {
2666+
{"aga_gen", Py_T_OBJECT_EX, offsetof(PyAsyncGenAThrow, agt_gen), Py_READONLY},
2667+
{NULL} /* Sentinel */
2668+
};
2669+
26602670
static PyMethodDef async_gen_athrow_methods[] = {
26612671
{"send", async_gen_athrow_send, METH_O, send_doc},
26622672
{"throw", _PyCFunction_CAST(async_gen_athrow_throw),
@@ -2704,7 +2714,7 @@ PyTypeObject _PyAsyncGenAThrow_Type = {
27042714
PyObject_SelfIter, /* tp_iter */
27052715
async_gen_athrow_iternext, /* tp_iternext */
27062716
async_gen_athrow_methods, /* tp_methods */
2707-
0, /* tp_members */
2717+
async_gen_athrow_memberlist, /* tp_members */
27082718
0, /* tp_getset */
27092719
0, /* tp_base */
27102720
0, /* tp_dict */

0 commit comments

Comments
 (0)