Skip to content
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

Use consistent terminology to distinguish functions that run sync callables vs. async callables #68

Closed
njsmith opened this issue Mar 1, 2017 · 7 comments

Comments

@njsmith
Copy link
Member

njsmith commented Mar 1, 2017

We have a bunch of functions that take a callable and run it:

  • trio.run
  • trio.run_in_worker_thread
  • run_in_trio_thread
  • await_in_trio_thread

The first and last take an async function, and the middle two take a sync function. Our terminology here is really confusing. And unfortunately the obvious solution of renaming it to trio.await is impossible because await will soon be a keyword. So not sure what the best solution is.

(There's also call_soon_thread_and_signal_safe, but it's kind of an oddball because it schedules the call for later rather than running it right there.)

Brainstorming some options (format: run-an-async-fn / run-a-sync-fn / call soon):

  • arun / run / call_soon
  • run / call / call_soon
  • run / run_sync / (call_soon or run_sync_soon)
  • run_async / run_sync / ...
@njsmith
Copy link
Member Author

njsmith commented Aug 17, 2017

I'm leaning towards run / run_sync, but this has the downside that run_in_trio_thread is used both before and after, with different meanings, so a normal deprecation doesn't work. We could just rip off the band-aid, but maybe we should take this opportunity to make another change. I've noticed it's kind of weird and annoying to have to call both current_run_in_trio_thread and current_run_sync_in_trio_thread and keep track of both returned values; if you want to call trio APIs from a thread you'll probably need both. So maybe we should define a concept of a ReentryVehicle: an object that provides run and run_sync methods that are appropriate to where-ever you're launching from. So it'd be like:

in_trio = trio.current_blocking_reentry_vehicle()
# then in your thread
in_trio.run(...)

And you can imagine providing similar vehicles to reenter from asyncio or twisted or whatever.

...for asyncio / twisted / etc., it also makes sense to have trio→them reentry vehicles. Might want to keep that in mind when naming things: an asyncio→trio vehicle is no the same as a trio→asyncio vehicle.

(If an API design allows for an awesomely punny name then it's probably a good idea. I'm pretty sure that's in the zen of python somewhere.)

@njsmith
Copy link
Member Author

njsmith commented Aug 17, 2017

Oh, googling "reentry vehicles" doesn't give me spaceships like I was thinking of, it's all nukes. Maybe that's not as cool as I thought.

njsmith added a commit to njsmith/trio that referenced this issue Aug 19, 2017
Part of the changes for python-triogh-68

Also keeps run_in_worker_thread around as a deprecated alias.
@njsmith
Copy link
Member Author

njsmith commented Aug 20, 2017

We could call them "portals" maybe, retreating from the grim science fictional present into a pastoral fantasy past. Or, you know, Aperture Science 🍰.

When we get to implementing these interop mechanisms with other event loops (e.g. this already came up with kivy in #267) then there are two natural approaches. We could have one object that acts as a portal from trio→asyncio and a second object that acts as a portal from asyncio→trio, so something like in_trio.run(...) and in_asyncio.run(...). Or we could have a single object that acts as a bidirectional portal, portal.run_in_trio(...), portal.run_in_asyncio(...).

The subtlety here is that internally, a bidirectional portal probably wants to capture a trio run and an asyncio loop, and use them in both directions. OTOH a unidirectional portal can use the ambient loop on the sending side, and only has to capture some representation for the receiving side. In principle this makes the unidirectional version a little more elegant, and it's more generic in some sense. But I'm dubious that this is enough to make up for the extra conceptual cost of having two objects to initialize and keep track of.

The only way to capture a trio loop is by running something in trio. This could end up being a bit awkward if any other libraries have the same constraint, b/c then how do you create a bidirectional loop? OTOH I don't think any other libraries have the same constraint. (And it wouldn't be hard to work around in trio either; really it's just that it would want to call trio.hazmat.current_call_soon_thread_and_signal_safe for you.)

I guess a kind of elegant design would be to have a generic Portal and then you plug together two "endpoint" classes (nothing to do with twisted endpoints, this would basically just be some kind of generic abstraction over different library's ideas of call_soon). The challenge here would be if one library has a different idiomatic API though, like maybe the twisted run_in_trio method should return a deferred instead of being an async method? (GUI libraries in particular mostly don't have async/await integration at all.)

We don't actually have to decide any of this now. I'm just trying to think it through a bit because maybe it effects what we want to call the methods for entering trio from a generic blocking thread. This is inherently unidirectional because you can't assign work to a random generic thread that doesn't have an event loop running in it, but maybe for consistency the names should still be run_in_trio, run_sync_in_trio? Though, eh, I guess consistency here is not really that important anyway. Most people are going to be using a blocking "portal" or an asyncio "portal", not both, and not together.

@njsmith njsmith mentioned this issue Aug 21, 2017
17 tasks
@njsmith
Copy link
Member Author

njsmith commented Aug 22, 2017

Also wondering if we should replace current_call_soon_thread_and_signal_safe with something like a RunToken class that has a run_sync_soon_thread_and_signal_safe method on it. Or something like that. Basically reifying the "I want to capture this trio run for reentry" as a dedicated object, instead of just an anonymous function. Seems like it might be easier to understand. And easier to explain things like:

class AsyncioTrioPortal
    def __init__(self, asyncio_loop, trio_run_token=None):
        ...

if we can say "for trio_run_token pass a trio.hazmat.RunToken", as opposed to

class AsyncioTrioPortal
    def __init__(self, asyncio_loop, trio_call_soon_thread_and_signal_safe=None):
        ...

where it's just... harder to explain what that thing is.

@njsmith
Copy link
Member Author

njsmith commented Aug 27, 2017

Another use for a TrioRunToken kind of object is that it could provide == or similar (or even just is) as a way to check if two runs were the same. Example use case: catching when someone tries to use a pytest-trio fixture with scope="session".

It could potentially provide some other niceties too, like a way to check if the loop is running without entering it. Though... meh, this is racey, not sure it's really interesting. You can always call_soon(lambda: None) if you really want to know :-).

@alexeymuranov
Copy link

arun / run

@oremanj
Copy link
Member

oremanj commented Mar 3, 2021

@alexeymuranov Trio uses run for async callables and run_sync for sync callables. I doubt that convention is going to change at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants