-
-
Notifications
You must be signed in to change notification settings - Fork 947
Switch to using anyio for async #1291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
3ac9f28
to
878bcd0
Compare
Hey there! Thanks a lot for being understanding :) |
@rodrigogiraoserrao My understanding is that AnyIO is an abstraction over asyncio and other async libs, which would otherwise be mutually incompatible. It means that we could mix components built for these other libs. @sirg3 Very interested in this. I was planning to look at it myself at some point. Let me know if you have any questions. |
@rodrigogiraoserrao There are multiple different async libraries for Python, though the big two are asyncio (in the Python standard library) and trio. I want to use Textual in a program that is already using trio but that’s currently not possible because Textual uses asyncio directly.
@willmcgugan Thanks for the interest. I’m unlikely to be able to get back to this until Christmas, but I would like your thoughts on the MR as-is, along with:
|
@willmcgugan Ping? |
Textual is zero-ver, so API is subject to change. However, I'd prefer to keep most API calls async agnostic.
I suspect it is too tightly coupled to Textual to make it an independent concern.
Afraid not. The project has moved on quite a bit. Not sure if these are relevant. |
Unfortunately I cannot keep up with the pace of Textual development and won’t be able to get back to this MR in the foreseeable future. Hopefully all of my notes will be helpful in case anyone else tries this. |
This is a draft that switches from asyncio to anyio, which is necessary for
trio compatibility. I've tested it with a version of
calculator.py
thatruns under Trio:
Here's a short list of design decisions and changes:
all background tasks (e.g. the driver, timers, and the message pump) run in
it. The app will not finish running until all of these tasks stop, which is
part of the shutdown process. Any hangs in exiting an app are likely due to
a task that is still living, indicating an issue in the shutdown process.
makes
App.run_test
unable to be used in a pytest fixture.async context. This breaks the common idiom of
MyApp().run()
being calledoutside of an async context. To work around this, we force anyio to believe
it's running under
asyncio
if there is no async context. This doesintroduce the possibility for a mismatch and
App.run_async
checks for this.Trio users should create the app inside of the Trio context and perhaps the
Textual can be reworked a bit to make this better (e.g. lazily creating locks
and other
anyio
objects once we know what backend we're running under).analagous to
asyncio
's queue class but has a built-in notion of whetherthe stream is closed or not.
This was necessary because send / receive streams can't be used once they're
closed, but message pumps can be re-used multiple times. It also addresses some
potential race conditions between starting the pump and waiting for it to close.
asyncio
backend becauseaiohttp
requiresasyncio
.uvloop
with the thought being that users canexplicitly opt into that. An alternate approach would be to do that in the
_spoof_asyncio_if_needed
when we default toasyncio
.anyio.Event
lacksclear()
, so a new class,Flag
, was created to providethat functionality.
Timer
implementation got a bit messy due to a race condition betweenstart
enqueueing the task,stop
getting called, and the task actuallyexecuting. Making
Timer.start
async would allow this to be implementedmore cleanly but is an API breakage.
Timer.start
no longer returns aTask
object.asyncio
.Almost all tests pass, with some exceptions:
_remove_nodes
needs to schedule a task to run in its task group. Mightneed to restructure this test to use
App.run_test
.stems from
Screen.find_widget
being unable to find the widget. Addingawait anyio.wait_all_tasks_blocked()
right after theasync with
makes thetest pass, which makes me think there's some async race conditions going
on.
Outstanding tasks:
do based off of the Linux driver.