========================================
Adding RPC Functions With Margo Library
========================================
In this section, we describe how to add an RPC function using
the Margo library API.
.. note::
The following documentation uses ``unifyfs_mount_rpc()`` as an example
client-server RPC function to demonstrate the required code modifications.
---------------------------
Common
---------------------------
1. Define structs for the input and output parameters of your RPC handler.
The struct definition macro ``MERCURY_GEN_PROC()`` is used to define
both input and output parameters. For client-server RPCs, the
definitions should be placed in ``common/src/unifyfs_client_rpcs.h``,
while server-server RPC structs are defined in
``common/src/unifyfs_server_rpcs.h``.
The input parameters struct should contain all values the client needs
to pass to the server handler function.
The output parameters struct should contain all values the server needs
to pass back to the client upon completion of the handler function.
The following shows the input and output structs used by
``unifyfs_mount_rpc()``.
.. code-block:: c
MERCURY_GEN_PROC(unifyfs_mount_in_t,
((int32_t)(dbg_rank))
((hg_const_string_t)(mount_prefix))
((hg_const_string_t)(client_addr_str)))
MERCURY_GEN_PROC(unifyfs_mount_out_t,
((int32_t)(app_id))
((int32_t)(client_id))
((int32_t)(ret)))
.. note::
Passing some types can be an issue. Refer to the Mercury documentation for
supported types: ``_ (look
under `Predefined Types`). If your type is not predefined, you will need to
either convert it to a supported type or write code to serialize/deserialize
the input/output parameters. Phil Carns said he has starter code for this,
since much of the code is similar.
---------------------------
Server
---------------------------
1. Implement the RPC handler function for the server.
This is the function that will be invoked on the client and executed on
the server. Client-server RPC handler functions are implemented in
``server/src/unifyfs_client_rpc.c``, while server-server RPC handlers go
in ``server/src/unifyfs_p2p_rpc.c`` or ``server/src/unifyfs_group_rpc.c``.
All the RPC handler functions follow the same protoype, which is passed
a Mercury handle as the only argument. The handler function should use
``margo_get_input()`` to retrieve the input parameters struct provided by
the client. After the RPC handler finishes its intended action, it replies
using ``margo_respond()``, which takes the handle and output parameters
struct as arguments. Finally, the handler function should release the
input struct using ``margo_free_input()``, and the handle using
``margo_destroy()``. See the existing RPC handler functions for more info.
After implementing the handler function, place the Margo RPC handler
definition macro immediately following the function.
.. code-block:: c
static void unifyfs_mount_rpc(hg_handle_t handle)
{
...
}
DEFINE_MARGO_RPC_HANDLER(unifyfs_mount_rpc)
2. Register the server RPC handler with margo.
In ``server/src/margo_server.c``, update the client-server RPC registration
function ``register_client_server_rpcs()`` to include a registration macro
for the new RPC handler. As shown below, the last argument to
``MARGO_REGISTER()`` is the handler function address. The prior two arguments
are the input and output parameters structs.
.. code-block:: c
MARGO_REGISTER(unifyfsd_rpc_context->mid,
"unifyfs_mount_rpc",
unifyfs_mount_in_t,
unifyfs_mount_out_t,
unifyfs_mount_rpc);
---------------------------
Client
---------------------------
1. Add a Mercury id for the RPC handler to the client RPC context.
In ``client/src/margo_client.h``, update the ``ClientRpcIds`` structure
to add a new ``hg_id_t _id`` variable to hold the RPC handler id.
.. code-block:: c
typedef struct ClientRpcIds {
...
hg_id_t mount_id;
}
2. Register the RPC handler with Margo.
In ``client/src/margo_client.c``, update ``register_client_rpcs()`` to
register the new RPC handler by its name using
``CLIENT_REGISTER_RPC()``, which will store its Mercury id in the
``_id`` structure variable defined in the first step. For example:
.. code-block:: c
CLIENT_REGISTER_RPC(mount);
3. Define and implement an invocation function that will execute the RPC.
The declaration should be placed in ``client/src/margo_client.h``, and the
definition should go in ``client/src/margo_client.c``.
.. code-block:: c
int invoke_client_mount_rpc(unifyfs_client* client, ...);
A handle for the RPC is obtained using ``create_handle()``, which takes the
the id of the RPC as its only parameter. The RPC is actually
initiated using ``forward_to_server()``, where the RPC handle, input
struct address, and RPC timeout are given as parameters. Use
``margo_get_output()`` to obtain the returned output
parameters struct, and release it with ``margo_free_output()``. Finally,
``margo_destroy()`` is used to release the RPC handle. See the existing
invocation functions for more info.
.. note::
The general workflow for creating new RPC functions is the same if you want to
invoke an RPC on the server, and execute it on the client. One difference is
that you will have to pass `NULL` to the last parameter of `MARGO_REGISTER()` on
the server, and on the client the last parameter to `MARGO_REGISTER()` will be
the name of the RPC handler function. To execute RPCs on the client it needs to
be started in Margo as a `SERVER`, and the server needs to know the address of
the client where the RPC will be executed. The client has already been
configured to do those two things, so the only change going forward is how
`MARGO_REGISTER()` is called depending on where the RPC is being executed
(client or server).