Getting Started

Mercury follows a client/server model. In this tutorial we show how one can use the mercury interface to execute a simple RPC, i.e. ship an open function to a remote server, execute it, and get the result back. The open function has the following prototype:

int open(const char *pathname, int flags, mode_t mode);

Initializing and finalizing the library

Every client program that uses mercury for executing RPCs follows this architecture: it initializes the HG interface with a defined NA (network abstraction) class, looks up a server address by using a host string, sends an RPC request using the server address, frees the the server address, finalizes the NA/HG interfaces.

int
main(int argc, char *argv[])
{
    na_class_t *na_class;
    na_addr_t server_na_addr;

    /* Initialize a NA class (e.g., BMI) */
    na_class = NA_Initialize("tcp://127.0.0.1:22222", NA_FALSE);

    /* Initialize the HG interface */
    HG_Init(na_class);

    /* Lookup a server name with NA and get a na_addr_t */
    NA_Addr_lookup(na_class, "tcp://72.36.68.242:22222", &server_na_addr);

    /* Send RPC request to server_addr */
    [...]

    /* Free the NA address */
    NA_Addr_free(na_class, addr);

    /* Finalize the HG interface */
    HG_Finalize();

    /* Finalize the NA interface */
    NA_Finalize(na_class);

    return EXIT_SUCCESS;
}

On the remote end, every server must initialize the HG interface with a defined NA class and loop over/process incoming RPC requests for timeout ms. When server termination is requested, NA/HG interfaces must be finalized.

int
main(int argc, char *argv[])
{
    na_class_t *na_class;
    int timeout = HG_MAX_IDLE_TIME;

    /* Initialize a NA class (e.g., BMI) that listens on its port */
    na_class = NA_Initialize("tcp://127.0.0.1:22222", NA_TRUE);

    /* Initialize the HG interface */
    HG_Init(na_class);

    /* Process RPC calls */
    while (!finalized) {
        HG_Handler_process(timeout, HG_STATUS_IGNORE);
    }

    /* Finalize the HG interface */
    HG_Finalize();

    /* Finalize the NA interface */
    NA_Finalize(na_class);

    return EXIT_SUCCESS;
}

Serializing and deserializing arguments

To be able to send the open, one must tell the mercury interface what its arguments are, so that they can be correctly encoded and decoded on both the client and the server. To do that, mercury defines a macro that generates all the boilerplate code for serializing and deserializing function arguments (by building on top of the BOOST preprocessor interface). Arguments fall into two categories, input and output arguments, which means that the macro must be called twice, once for the input and once for the output:

MERCURY_GEN_PROC( open_in_t, ((hg_const_string_t)(path)) ((hg_int32_t)(flags)) ((hg_uint32_t)(mode)) )
MERCURY_GEN_PROC( open_out_t, ((hg_int32_t)(ret)) )

Calling this macro defines open_in_t and open_out_t, which can be used to pass and retrieve the function arguments, as well as two routines for each struct that serializes and deserializes its members into/from a binary buffer.

Once this macro is called, the interface needs to associate the input and output arguments to the function by using a unique string identifier (which may be the name of the function). The same identifier will then be used on the server as well to execute the function that corresponds to the incoming request. For example, on the client side:

hg_id_t open_id;

/* Register RPC call to the HG interface */
open_id = MERCURY_REGISTER(”open”, open_in_t, open_out_t);

/* Send RPC request using open_id */
[...]

And on the server side, the same procedure must be repeated, but this time, a function callback must also be passed, which tells the server what it needs to execute when the RPC request comes in.

/* Register RPC call */
MERCURY_HANDLER_REGISTER(”open”, open_rpc, open_in_t, open_out_t);

Sending and executing an RPC request

At this point, a client knows how to initialize the interface, look up a server address, register and serialize function arguments. All it needs to do then is to fill in the input structure and call HG_Forward:

na_addr_t server_na_addr;
hg_id_t open_id;
open_in_t open_in_args;
open_out_t open_out_args;
hg_request_t rpc_request;

/* Initialize the interface / register / lookup server_na_addr */
[...]

/* Fill input structure */
open_in_args.path = pathname;
open_in_args.flags = flags;
open_in_args.mode = mode;

/* Forward call to server addr and get a new RPC request */
HG_Forward(server_na_addr, open_id, &open_in_args, &open_out_args, &rpc_request);

/* Wait for call to be executed and return value to be sent back */
HG_Wait(rpc_request, HG_MAX_IDLE_TIME, HG_STATUS_IGNORE);

/* Get output arguments */
open_ret = open_out_args.ret;

/* Free request */
HG_Request_free(rpc_request);

/* Finalize the interface */
[...]

HG_Forward is non-blocking and returns a request, which only completes after a call to HG_Wait. When calling HG_Forward, input function arguments are serialized. When calling HG_Wait, if the request completes, output function arguments are deserialized. After the output arguments are copied, a call to HG_Request_free frees the resources that may have been allocated when deserializing the arguments.

Once this is done, the remaining part is the execution of the request callback on the server. Every RPC request callback follows this prototype, passed to the MERCURY_HANDLER_REGISTER routine:

typedef hg_return_t (*hg_handler_cb_t)(hg_handle_t handle);

In the case of the open, one would define:

hg_return_t
open_rpc(hg_handle_t handle)
{
    hg_return_t hg_ret = HG_SUCCESS;
    open_in_t open_in_args;
    open_out_t open_out_args;
    const char *path;
    int flags;
    mode_t mode;
    int ret;

    /* Get input struct */
    HG_Handler_get_input(handle, &open_in_args);

    path = open_in_args.path;
    flags = open_in_args.flags;
    mode = open_in_args.mode;

    /* Call open */
    ret = open(path, flags, mode);

    /* Fill output structure */
    open_out_args.ret = ret;

    /* Free handle and send response back */
    HG_Handler_start_output(handle, &open_out_args);

    return hg_ret;
}

The callback is composed of three steps: getting the input arguments (which have been deserialized) using HG_Handler_get_input, calling the actual open and passing the output arguments back to the client using HG_Handler_start_output. Note that HG_Handler_start_output is non-blocking (and completion is taken care of by the mercury layer).

Once this final part is done, the RPC for the open function can be executed.