This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Asyncio documentation for recursive coroutines is lacking
Type: enhancement Stage: patch review
Components: asyncio, Documentation Versions: Python 3.7, Python 3.6, Python 3.4, Python 3.5
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: docs@python Nosy List: asvetlov, azaria.zornberg, docs@python, yselivanov
Priority: normal Keywords: patch

Created on 2018-09-16 03:54 by azaria.zornberg, last changed 2022-04-11 14:59 by admin.

Files
File name Uploaded Description Edit
asyncio_await_from_self_example.py azaria.zornberg, 2018-09-16 03:54 Example of undocumented behavior
Pull Requests
URL Status Linked Edit
PR 9339 closed azaria.zornberg, 2018-09-16 03:59
Messages (3)
msg325468 - (view) Author: Azaria Zornberg (azaria.zornberg) * Date: 2018-09-16 03:54
When an asynchronous coroutine in asyncio awaits or yields from itself, any call to the function is executed somewhat synchronously.

Once the recursive coroutine begins, if it never awaits any other coroutines besides itself, nothing else will be scheduled to run until it has completely finished recursively calling itself and returning.
However, if it ever awaits a different coroutine (even something as small as asyncio.sleep(0)) then other coroutines will be scheduled to run.

It seems, from other documentation, that this is intentional. Other documentation sort of dances around the specifics of how coroutines work with recursion, and only examples of coroutines yielding from each other recursively are provided.

However, this behavior is never explicitly called out. This is confusing for people who write a recursive asyncio coroutine and are perplexed by why it seems to execute synchronously, assuming they ever notice.

I've attached a short script that can be run to exhibit the behavior.
A PR is going to be filed shortly against the python 3.7 branch (as the documentation page for asyncio in 3.8 does not fully exist right now).
msg325469 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2018-09-16 04:09
The issue here is not the recursion,  but rather about the fact that coroutines should actually await on IO or other activity in order for the event loop to run them cooperatively.  E.g.

   async def foo():
        await foo()

doesn't really do anything expect calling itself, whereas

   async def foo():
        await sleep(0)
        await foo()

is asking the event loop to sleep for a moment and then recurses into itself.

I'm OK with better clarifying this in the asyncio-dev.rst file.
msg325470 - (view) Author: Azaria Zornberg (azaria.zornberg) * Date: 2018-09-16 04:15
Ah, thanks for the clarification!

I first encountered this when having some issues with converting large objects to json. json.dumps happens synchronously, and when executed on an object that was dozens of MB in size, it held up everything for a fair bit of time.
I tried to solve it by recursively running json.dumps on smaller pieces of the thing being converted to json. And that was when I realized that this still wasn't letting other things get scheduled.

When I looked for examples online, I didn't see any of a recursive asyncio coroutine, which is why I assumed the recursion was the issue.


Any advice on better ways to phrase the documentation are greatly appreciated! Alternatively, it sounds like you have a much better understanding of this than I do, so I'm happy to defer to whatever you believe is the correct way to document this. Thanks for the help!
History
Date User Action Args
2022-04-11 14:59:06adminsetgithub: 78882
2018-09-16 04:15:11azaria.zornbergsetmessages: + msg325470
2018-09-16 04:09:05yselivanovsetmessages: + msg325469
2018-09-16 03:59:43azaria.zornbergsetkeywords: + patch
stage: patch review
pull_requests: + pull_request8762
2018-09-16 03:54:15azaria.zornbergcreate