Hi.

As people know I prefer explicit asyncio loop and pass it everywhere.
That's how I build libraries like aiohttp and aiopg, do writing private 
source code.
I promote this way of code writing on conferences etc.
Push `asyncio.set_event_loop(None)` at very begin of your code to avoid 
mysterious bugs!

But I feel temptation of using implicit loop -- and users of my libraries 
do it very often.

Now, after four years of working with asyncio I almost agree with it -- if 
implicit loop is used *from coroutine*.

But outside of *task context* (every asyncio coroutine is executed by a 
task, you know) it's still dangerous sometimes.

Let's take a close look on the following example:

    import motor.motor_asyncio

    class A:
        client = motor.motor_asyncio.AsyncIOMotorClient()

What's wrong with it? The code is untestable. AsyncIOMotorClient accepts 
is_loop parameter which should be event loop instance or None. If the 
parameter is None (as in our case) default event loop will be given.

Then we run a test suite. Every test should have own loop for sake of test 
isolation. In practice it means that every test tool creates an event loop, 
runs test with it and closes the loop after test finishing. It's very true 
strategy for testing async code, I know no alternatives.
But our client was coupled with *default* loop created on module import 
stage.
As a consequence `await client['db'].find(...)` call will be never finished 
because client's loop is not iterated by test runner.

It's not specific Motor's (async MongoDB client for Torrnado and Asyncio) 
problem -- aioes.Client, aiohttp.ClientSession and many others does the 
same.

In opposite aiopg, asyncpg and aiomysql have no this problem -- their DB 
connections are created by `await connect(params)` coroutine call. `await` 
cannot be used in global/class namespace, thus user will always call it 
from task with proper loop.

Sure, I could forbid aiohttp.ClientSession creation without current task 
and make usage of this particular class safe (it will break backward 
compatibility for inaccurate users but better to break code early, right).

Asyncio could introduce `asyncio.get_current_loop()` function which will 
raise exception if implicit loop exists but there is no running task. After 
that third-party libraries may switch to this function in any case where 
the loop is required. asyncio itself could do the same.

At the end.
Accessing implicit event loop is very handy but it's safe only inside 
running task.
Getting the loop in global namespace may lead to very strange and cryptic 
errors (most likely the next usage will just hang).

Thoughts?

Reply via email to