Discussion or tracking tickets for move to asyncio.get_running_loop()

Hi.

Comment from @asvetlov on bpo-36373: Fix deprecation warnings by asvetlov · Pull Request #15889 · python/cpython · GitHub, which is ref the deprecation of the explicit loop argument in asyncio’s public API:

In Python 3.9 I have a plan to add another deprecation warning if not self._loop.is_running() for queues and locks and eventually replace get_event_loop() with get_running_loop() as a final fix.

I’m interested in this, with a possible eye to getting my feet wet. Is there a discussion outlining the intended direction, or tracking tickets covering the work to be done, or…? (I couldn’t find anything looking, but I may just not know the place.) (Did search the issue tracker without luck…)

Thanks!

I don’t recall where I wrote this but the current design can produce a weird result.
Consider the following:

import asyncio

lock = asyncio.Lock()

async def main():
    ...

asyncio.run(main())

This example is very dangerous and potentially leads to a hang because lock and main() use different loop instances.

Hi Andrew, Thanks for the reply…

Yes, that’s a good example. From resolving the previous issue with tracking the event loop across threads, we’re seeing reports of users hitting almost exactly this issue, whereby a asyncio.Lock() is using the default rather than the running loop. (The tl;dr seems to be that we need to call set_event_loop() to set the correct loop.)

The discussion pointed to this issue aiohttp#3331 — where you’re using get_event_loop inside the get_running_loop() helper. Is the plan to drop that helper once Python 3.7 is the minimum supported version?

Looking at the source of asyncio.Lock and friends, it looks as if a deferred call to get_running_loop() when self._loop is first accessed would allow this problem to be avoided. BUT, and hence my query here, I’m not at all sure if that’s the right/considered approach, so I was looking for any design/roadmap docs that point the way forward.

The thought of how the deprecation path looks is a bit :thinking: — It’s only from discussion here and then digging that I’m gaining clarity, so I thought helping with the docs might be good.

I thought I could get setup and see if I could potter away a bit there. It’s small enough in scope for me to get started without feeling too out of depth too quickly — but I still feel like I’m missing the top-level perspective.

Thanks!

(Sorry for the lack of proper links: the editor won’t let me post. I guess that’s a trust/spam thing. Update, got promoted.)

With get_event_loop() I can support existing code but raise a warning. After exhausting the deprecation period it can be replaced with strong get_running_loop(), sure.

In aiohttp on the master the situation is a little different: the project runs out the deprecation for this particular warning already; the warning is converted to error. I need to keep get_event_loop() until dropping Python 3.6 support, you are 100% correct.

I’m not sure of the specific practices followed for aiohttp development, but would it be reasonable to include a version check that sets a global constant at the start, and only using get_running_loop() when using Python 3.7 or higher? For example:

(based on the aiohttp get_running_loop() mentioned in Encourage creation of aiohttp public objects inside a coroutine · Issue #3331 · aio-libs/aiohttp · GitHub, not the one in asyncio)

PYTHON_37 = sys.version_info.major >= 3 and sys.version_info.minor >= 7
# [snip]
def get_running_loop(loop=None):
    if loop is None:
        if PYTHON_37:
            loop = asyncio.get_running_loop()
        else:
            loop = asyncio.get_event_loop()
    if not loop.is_running():
        warnings.warn("The object should be created from async function",
                                 DeprecationWarning,
                                 stacklevel=3)```

Edit: Improved example

Ah, I see. The one I was referring to was a bit outdated.

Now that I think about it though, I actually prefer the aiohttp error message over the one we currently have for asyncio.get_running_loop(). It’s much more clear to users how to fix the error.

If you try to get the event loop through asyncio.get_running_loop() outside of a coroutine function, you just get “RuntimeError: no running event loop”, which isn’t nearly as obvious as
“RuntimeError: The object should be created from async function” (although I’d suggest a grammar fix: “from async function” -> “within an async function”).

I’ve fixed the error text in aiohttp, thanks for the suggestion.

I doubt if such change is correct for asyncio.
In aiohttp helpers.get_running_loop() is always used for new object creation, but in asyncio the usage is much wider. It can be fetched for creating a future instance or calling await loop.sock_*() functions for example. There is no object creation in this context.

1 Like

No problem.

Ah, good point. For some reason that hadn’t crossed my mind when I initially made the suggestion, but that makes sense.

I use:

import asyncio

if sys.version_info >= (3, 7):
    from asyncio import get_running_loop
else:
    def get_running_loop() -> asyncio.AbstractEventLoop:
        loop = asyncio._get_running_loop()
        if loop is not None:
            return loop
        else:
            raise RuntimeError('no running event loop')