python-trio / trio-asyncio

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

Importing trio-asyncio from within IPython crashes IPython #132

Closed DRMacIver closed 5 months ago

DRMacIver commented 6 months ago

When running IPython 8.20.0 and trio-asyncio 0.13.0, importing trio_asyncio immediately crashes IPython:

In [1]: %config Application.verbose_crash=True

In [2]: import trio_asyncio

/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/trio_asyncio/_loop.py:289: TrioAsyncioDeprecationWarning: Using trio-asyncio outside of a Trio event loop is deprecated since trio-asyncio 0.10.0 with no replacement
  return _trio_policy.new_event_loop()
Error in sys.excepthook:
Traceback (most recent call last):
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/pathlib.py", line 1250, in is_dir
    return S_ISDIR(self.stat().st_mode)
                   ^^^^^^^^^
AttributeError: 'str' object has no attribute 'stat'

Original exception was:
Traceback (most recent call last):
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/bin/ipython", line 8, in <module>
    sys.exit(start_ipython())
             ^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/IPython/__init__.py", line 130, in start_ipython
    return launch_new_instance(argv=argv, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/traitlets/config/application.py", line 1043, in launch_instance
    app.start()
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/IPython/terminal/ipapp.py", line 317, in start
    self.shell.mainloop()
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py", line 911, in mainloop
    self.interact()
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py", line 896, in interact
    code = self.prompt_for_code()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/IPython/terminal/interactiveshell.py", line 839, in prompt_for_code
    text = self.pt_app.prompt(
           ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/prompt_toolkit/shortcuts/prompt.py", line 1026, in prompt
    return self.app.run(
           ^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/prompt_toolkit/application/application.py", line 1002, in run
    return asyncio.run(coro)
           ^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/trio_asyncio/_sync.py", line 125, in run_until_complete
    return self.__run_in_thread(self._run_coroutine, future)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/trio_asyncio/_sync.py", line 172, in __run_in_thread
    return res.unwrap()
           ^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/outcome/_impl.py", line 138, in unwrap
    raise captured_error
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/trio_asyncio/_sync.py", line 160, in _run_coroutine
    return result.unwrap()
           ^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/outcome/_impl.py", line 138, in unwrap
    raise captured_error
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/prompt_toolkit/application/application.py", line 875, in run_async
    loop = stack.enter_context(set_loop())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/contextlib.py", line 517, in enter_context
    result = _enter(cm)
             ^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/contextlib.py", line 137, in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
  File "/opt/homebrew/Caskroom/miniforge/base/envs/py311/lib/python3.11/site-packages/prompt_toolkit/application/application.py", line 789, in set_loop
    loop = get_running_loop()
           ^^^^^^^^^^^^^^^^^^
RuntimeError: no running event loop

This also happens if I import trio_asyncio indirectly (e.g. inside a function).

I cannot reproduce this crash outside of IPython - importing trio-asyncio doesn't crash in the same way, even if I do it for the first time inside a running asyncio event loop.

I was on the fence as to whether to file this with IPython or trio-asyncio - it's IPython exhibiting the problem, but I suspect it's due to shenanigans that importing trio-asyncio is doing to the running asyncio event loop. If you'd rather I file this with IPython, please let me know and I'll do so.

oremanj commented 6 months ago

I've narrowed this down to a change in prompt-toolkit between version 3.0.36 and 3.0.37. If you downgrade your prompt-toolkit to 3.0.36, that will likely be sufficient to work around this issue for now.

trio-asyncio is doing some evil things here that it shouldn't be doing; it's not prompt-toolkit's fault (or IPython's). I'll see if I can fix it, but I don't totally understand how the existing code works.

oremanj commented 6 months ago

Also, if you immediately reset the event loop policy (in the same cell), that seems to be a sufficient workaround also:

In [1]: import trio_asyncio, asyncio; asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())

In [2]: