How can I do this in Rust with async/await?

I wanna make a process supervisor app.
It has two basic components: a control api service and the main supervisor service.
And I wanna roll it on a single-threaded model with async/await approach.
I can easily model the main sketch in python or any other PL with GC like this:

import asyncio
import random


class App:
    def __init__(self):
        self.api = ControlService(self)
        self.supervisor = SupervisorService()

    async def serve(self):
        await asyncio.wait([
            self.api.serve(),
            self.supervisor.serve()
        ])


class ControlService:
    def __init__(self, app):
        self.app = app

    async def serve(self):
        while True:
            print('[ControlService   ] Waiting user request...')
            cmd, arg = await simulate_user_request()
            print("[ControlService   ] Received user cmd: {}".format([cmd, arg]))
            if cmd == "set":
                self.app.supervisor.set_value(arg)
            else:
                pass


class SupervisorService:
    def __init__(self):
        self._value = 0

    def set_value(self, value):
        self._value = value

    async def serve(self):
        while True:
            print('[SupervisorService] Business value: {}'.format(self._value))
            await asyncio.sleep(1)


async def simulate_user_request():
    await asyncio.sleep(2)
    return "set", random.randint(0, 10)


if __name__ == '__main__':
    app = App()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(app.serve())
    loop.close()

But with Rust I have problem:
If the control service and supervisor serve both serving, then the supervisor must be borrowed and be held in the async fn serve(self: &SupervisorService). So, The control service is impossible to mutate the supervisor service’s parameter.
So, How can I solve the problem?
What’s the best practice of making this kind of app in Rust?

Have you looked into using a data structure with shared ownership and interior mutability like Rc<RefCell<T>>? There’s a lot of useful info in the book if you need a quick reference to the what-and-why:

@parasyte Thanks for your suggestion.

I have considered Rc<RefCell<T>>. But there are still some worries.

async fn serve(self: Rc<RefCell<SupervisorService>>) {
    loop {
        await!(do_something_async(self));
        await!(do_another_thing_async(self.borrow().member)); // may cause runtime panic here.
        // ...
    }
}
  1. I have to implement all these async functions with parameter Rc<RefCell<T>>, I consider it is a hell.
  2. If awaiting a member’s async operation inside, I think it will be easy to trigger a runtime panic.

You can model this with futures channels - ControlService sends a Message over a channel to the SupervisorService that’s reading the channel, and it can then handle the update. This means the control and supervisor don’t have direct references, but rather communicate over a channel.

@vitalyd
But then the same problem will appear inside the SuperiorService again. Sounds like it just moved the ControlService into the SupervisorService.

You probably will still need the RefCell (or Cell, depending on what the mutable state is), but you shouldn’t need the Rc. There may be a way to avoid it with async/await + Pin, but I’m not fully up to speed on that yet.

Maybe you can sketch out some Rust code that we can talk about in particular?

I was thinking more along the lines of each of ControlService and SupervisorService sharing ownership of a third struct that contains the mutable state. Using custom self types for this seems very strange to me. Using channels would be a better model for mutating state between the two services, though.

1 Like

Ok, I’ll sketch out some Rust code once the a new version of futures-preview being landed.
It is lacking stuffs to make codes compilable right now.