Use a loop proxy for better flexibility
asvetlov opened this issue · 4 comments
aioloop-proxy provide a proxy instance that is fully compatible with asyncio.AbstractEventLoop but allows to cooperate of multiple nested proxies at the same time.
From pytest-asyncio point of view, a session-scope fixture can use a session-level loop proxy that can cooperate with module-level, class-level, and function-level ones providing good test isolation.
I have no proof of concept for pytest-asyncio yet, if somebody with deep pytest internals knowledge can help -- you are welcome!
This sounds interesting! As you mentioned in another comment the loop proxy could address many things related to loop teardown and orphaned tasks, because the context manager takes care of this when it exits. aioloop-proxy also includes an advance_time method, which has been proposed in #113.
I played around with aioloop-proxy a bit. A large number of tests already work when replacing a couple of lines in wrap_in_sync:
def wrap_in_sync(func, _loop):
…
if coro is not None:
try:
with aioloop_proxy.proxy(_loop) as loop_proxy:
task = asyncio.ensure_future(coro, loop=loop_proxy)
loop_proxy.run_until_complete(task)
except BaseException:
…
The failing tests were mostly related to subprocesses or setting a custom event loop or policy. Some special usage like the one in TestEventLoopStartedBeforeFixtures break, because the tasks in the test cases get attached to a different loop.
Anyway, the new information made me wonder if the event_loop fixture still makes sense when using aioloop-proxy.
That's all I got for now :)
From my point of view, the plugin should support two modes.
- In compatible mode the
event_loopfixture works as-is; it is crucial for the sake of backward compatibility. - The proxy mode has no
event_loopbut works differently.
An overridable session-scope fixture exists that returns a root loop instance. An user can override this fixture in conftest.py to start a non-standard event loop, e.g. uvloop or qasync. The root fixture maker should be named differently than event_loop to don't clash with compatible mode.
Each pytest node should have a loop proxy instance created from the proxy of the parent loop. node._store (or plugin-level cache if compatibility with older pytest versions is required) can be used to keep proxy loops tree.
I doubt if we need a fixture for getting the active event loop proxy instance since dropping Python 3.6 support. asyncio.get_running_loop() is always available; it follows loop argument removals from asyncio API.
Issue #91 is yet another problem that caused by the event_loop fixture not cleaning up existing tasks. It seems like a number of people are affected by this.
@asvetlov @Tinche Would it make sense to clean up open tasks before closing the current loop? This could serve as an intermediate solution until we integrate aioloop-proxy? It's not as nice, but it would be a smaller step in the right direction.
Good question!
My plan was to make 0.17.1 bugfix release and start integrating aioloop-proxy