python-trio / trio-asyncio

a re-implementation of the asyncio mainloop on top of Trio
Other
189 stars 38 forks source link

aiohttp raises "Creating a client session outside of coroutine" #7

Closed lilydjwg closed 6 years ago

lilydjwg commented 6 years ago

Code:

import aiohttp
import trio
import trio_asyncio

@trio_asyncio.trio2aio
async def create_session():
  aiohttp.ClientSession()

async def main():
  async with trio_asyncio.open_loop():
    await create_session()

if __name__ == '__main__':
  trio.run(main)

Result:

Exception in default exception handler
Traceback (most recent call last):
  File "/usr/lib/python3.6/asyncio/base_events.py", line 1290, in call_exception_handler
    self.default_exception_handler(context)
  File ".../venv/lib/python3.6/site-packages/trio_asyncio/async_.py", line 49, in default_exception_handler
    raise RuntimeError(message)
RuntimeError: Creating a client session outside of coroutine
Exception in default exception handler
Traceback (most recent call last):
  File "/usr/lib/python3.6/asyncio/base_events.py", line 1290, in call_exception_handler
    self.default_exception_handler(context)
  File ".../venv/lib/python3.6/site-packages/trio_asyncio/async_.py", line 49, in default_exception_handler
    raise RuntimeError(message)
RuntimeError: Unclosed client session

aiohttp is checking for loop.is_running() to be True, but it's False because it's not run by loop.run_forever().

I tried to use the compatibility mode but didn't figure out how to use it.

lilydjwg commented 6 years ago

I ended up with patching the loop:

def patch_loop(loop):
  def is_running(self):
    return self._stopped is not None and not self._stopped.is_set()
  loop.__class__.is_running = is_running

And it seems to work. (I don't know why self._stopped can be None.)

njsmith commented 6 years ago

I think it might even just be def is_running(self): return True. It depends on whether self._stopped means "we're trying to shut down" or "we've finished shutting down".

lilydjwg commented 6 years ago

Always returning True will cause a RuntimeError when calling .close():

...
  File ".../venv/lib/python3.6/site-packages/trio_asyncio/async_.py", line 122, in open_loop
    loop.close()
  File .../venv/lib/python3.6/site-packages/trio_asyncio/base.py", line 752, in close
    super().close()
  File "/usr/lib/python3.6/asyncio/unix_events.py", line 63, in close
    super().close()
  File "/usr/lib/python3.6/asyncio/selector_events.py", line 107, in close
    raise RuntimeError("Cannot close a running event loop")
RuntimeError: Cannot close a running event loop
smurfix commented 6 years ago

I stumbled on that problem yesterday. The problem was that asyncio uses run_forever to set the loop's thread ID as a marker for whether the loop is running, but he native run doesn't use that method.

Please try the current version.

smurfix commented 6 years ago

NB: you start compatibility mode simply by importing trio_asyncio before starting asyncio as usual; it installs its event loop policy as a side effect. However, I'll probably remove that for the next version.

The solution that's guaranteed to work is asyncio.set_event_loop_policy(trio_asyncio.TrioPolicy()).

smurfix commented 6 years ago

@njsmith It also depends on whether you're using sync/compatibility or async/native mode. In sync mode you can actually stop the loop by calling loop.stop or using run_until_complete as usual. In async mode that's obviously not possible.

lilydjwg commented 6 years ago

The current version works, thank you!