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

Async/await interop suggestion #378

Open
Diggsey opened this issue Apr 14, 2020 · 3 comments
Open

Async/await interop suggestion #378

Diggsey opened this issue Apr 14, 2020 · 3 comments

Comments

@Diggsey
Copy link
Contributor

Diggsey commented Apr 14, 2020

I wrote a small crate for using async/await syntax with actix. It also makes the "AtomicResponse" type obsolete by providing a more ergonomic mechanism to achieve the same thing. I think it would be nice to have as part of actix itself.

The basic idea is to use scoped TLS (at least until custom future contexts are supported) to store the actor and its context.

There are three functions:

  • FutureInterop::interop_actor()

    This is equivalent to the into_actor() method except that it populates the TLS slots with the actor and the context before polling the inner future. There's also a boxed variant of this function.

  • with_ctx

    This takes a closure and immediately calls it, passing in mutable references to the actor and context obtained from the TLS slots to that closure.

  • critical_section

    This takes a future/async block and uses ctx.wait() to make sure it has exclusive access to the actor state while it is running. It returns a future itself so you can seamlessly demarcate blocks of code within an async/await block as being critical sections.

Example:

impl Handler<Request> for MyActor {
    type Result = ResponseActFuture<Self, Result<Response, ()>>;

    fn handle(&mut self, msg: Request, _ctx: &mut Context<Self>) -> Self::Result {
        async move {
            // Modify the actor state in some way
            with_ctx(|a: &mut Self, _| a.mutate());

            ... <some async code> ...

            // Run some code with exclusive access to the actor
            let result = critical_section::<Self, _>(async {
                // Put the actor into an inconsistent state.
                // We don't want this visible outside the critical section.
                with_ctx(|a: &mut Self, _| a.begin_mutation());

                ... <some async code> ...

                // Make everything consistent again
                Ok(with_ctx(|a: &mut Self, _| a.end_mutation()))
            }).await;

            ... <some async code> ...

            // Return a result
            result
        }.interop_actor_boxed(self)
    }
}

Some things which are very hard to do right now are made much easier in this pattern (for example, when only half of a message handler needs to run atomically). Also, all of the normal futures combinators can be used, rather than the more limited set exposed under actix::fut.

Currently the crate relies on a patch to scoped_thread_local which hasn't been merged yet, which is why I haven't published it. However, except for scoped-tls this crate itself does not require any unsafe code at all.

@Diggsey
Copy link
Contributor Author

Diggsey commented Apr 18, 2020

This crate is now published to crates.io.

@david-mcgillicuddy-moixa
Copy link
Contributor

Could the new Generator Resume Arguments feature be used to replace with_ctx? At moixa we make heavy use of ActorFutures and it would be really nice to get the benefits of async/await syntax.

@Diggsey
Copy link
Contributor Author

Diggsey commented Jun 11, 2020

Potentially, but the type bounds will be very complicated, so even if generator resume arguments are added to the language, it doesn't necessarily mean that they'll be able to express the required lifetimes.

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

No branches or pull requests

2 participants