Support for server-side asynchronous functions

As I’ve mentioned at the end of Modern C++ lightweight binary RPC framework without code generation, one of the features I had in my previous RPC solution was to allow the server-side functions to be asynchronous, and let the framework take care of the details, since from the client perspective, nothing changes.

This last weekend, I’ve added experimental support for that to czrpc.

Lets see the problem. Consider these two classes that we want to use for RPC calls:

// Example of class with synchronous API.
class Calculator {
public:
    double add(double a, double b) {
        return a + b;
    }
};

// Example of class with asynchronous API, since it needs to
// do some IO on a database
class Database {
public:
    // This returns a future, and not a readily available value
    std::future<bool> checkLogin(const std::string& name,
                                 const std::string& pass);
};

From czrpc’s perspective, sending a result back to the client is easy enough for servers of Calculator. The add method returns the value right away (it’s synchronous). All czrpc has to do when the server-side code gets the RPC request from the client is to write the result of the method call to the output stream and send that output stream back to the client. Everything is linear. Akin to:

outStream << obj.add(a, b);
// send the contents of outStream to the client...

Now, for the Database case, the checkLogin method returns a std::future<bool>, and not a readily available value (it’s asynchronous).
Therefore, czrpc can’t return the result right away to the client when it receives the RPC request. Internally, it needs to know that checkLogin is asynchronous. When a client calls that RPC, it adds the resulting std::future<bool> to a list of pending results, and whenever that future is ready, it retrieves the result and sends it back to the client.

From the user code perspective, nothing needs to change. Example (ignoring any setup code):

// Define the RPC table for Calculator
#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    REGISTERRPC(add)
#include "crazygaze/rpc/RPCGenerate.h"

// Define the RPC table for Database. The framework automatically detects that
// "checkLogin" returns std::future
#define RPCTABLE_CLASS Database
#define RPCTABLE_CONTENTS \
    REGISTERRPC(checkLogin)
#include "crazygaze/rpc/RPCGenerate.h"

//////////////////////////////////////////////////////////////////////////
void TestClients() {
    // Initialize asio io_service here ...

    // Connect to the Calculator server
    auto calcCon =
        AsioTransport<void, Calculator>::create(io, "127.0.0.1", 9000).get();
    // Call an RPC on Calculator
    CZRPC_CALL(*calcCon, add, 1, 2)
        .async([](Result<double> res) {
            printf("Result=%f\n", res.get());  // Prints 3.0
        });

    // Connect to the database server.
    auto dbCon =
        AsioTransport<void, Database>::create(io, "127.0.0.1", 9001).get();
    // For the client, it doesn't matter if the server side API is asynchronous.
    // It still only has to deal with a Result<T>
    CZRPC_CALL(*dbCon, checkLogin, "Rui", "Meow")
        .async([](Result<bool> res) {
            printf("Result=%s\n", res.get() ? "true" : "false");
        });

    // Shutdown here ...
}
,

Leave a Reply

Be the First to Comment!

Notify of
avatar
wpDiscuz