Is our flavor of RPC compatible with futures & async/await?

At SomeCompany we use an internally developed RPC protocol (originally made for Python, but now used in Rust and a to a limited extent in JS) which connects various network services together.

The protocol isn't stricly RPC, it allows some somewhat unusual stuff that make matters complicated, most importantly:

  • RPC calls can nest & interleave and they can go in both ways. For example: A client makes an RPC request to a server. The server might first perform another RPC request back to the client, wait for a reply, and only then reply to the original request from the client.
  • Besides RPC calls, there is also simple message passing (independent of any outstanding RPC calls), used for example for heartbeat messages etc.

The code that operates the ends of each connection is organized into "tasks" which are single-threaded but asynchronous in the sense that if the task is performing CPU-bound work it's ok for the input requests to accumulate, but otherwise it should generally service requests without delay (ie. low latency is desired).

In a nutshell, right now in the Rust implementation the task is a Tokio task which internally basically only consists of sync code, it takes requests/messages out of an incomming queue and dispatches them onto a (sync) handler object, which has methods for each possible request/message type. Handler can call methods on the task to send RPC replies back when it's appropriate.

The problem with this is that the handler object is awfully stateful.

I was wondering if this could be helped with futures/async/await.
A function performing an RPC request could return a future and the task could .await it, but then the problem is that the task also needs be able to answer other RPC calls and process messages while awaiting the original call (or even call the same RPC method as part of handling other requests/messages). I guess this could be done with Tokio's LocalSet if I understand right what it does?

There's also the issue of state - the handlers need to access the task's state (or some subsets of it, which may be overlapping). I guess this could be helped with interior mutability?

I'm generally pretty unsure as to how the API implementing this with futures & awaiting would be structured so I'll be grateful for any advice.

Thanks!

This should definitely be possible with Tokio. I can't give the full details right now as I'm on a phone, but check out our mini-redis example here, as it has an example of the actor pattern.

The actor pattern is the idea of having a single task that manages the actual connection, using message passing to talk to it e.g. from where you want to initialize the RPC calls without caring about them being interleaved with other stuff.

Consider joining us on our chat too.

Well, I think what we have is the actor model and it works.
I'm basically looking into if we can do better than that for when an implementation of a client for example (it's not going to happen for servers as much probably) is more sequentiel, ie. goes through a series of states...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.