SimGrid 3.6.2
Scalable simulation of distributed systems
Lesson 10: Remote Procedure Calling (RPC)

Table of Contents


Introduction

So far, we saw how to send messages from one host to another, but quite often, we need a two-way message exchange: a "client" sends a request to a "server", and the server returns a result after doing some sort of computation. This design is often refered to as "Remote Procedure Call" or RPC for short.

It is naturally possible to build RPC exchanges using only one-way messages, as the ones we used in GRAS so far, but it's a bit awkward (specially when the server wants to return a value to the client in a remote function call). That is why GRAS provide a support for RPC, as we will now detail.

Putting rpc into action

We will build a simple RPC where clients use a remote server to convert strings into numbers and vice-versa (ie, changing between "1234" and 1234). To achieve its duty, the server will simply use the strtol function in one direction. In the other direction, we will use bprintf(). This is a sprintf() version allocating the needed room before doing the conversion. Its portability is discutable, but SimGrid declares this function when it cannot be found on the host architecture, so you can use it peacefully.

Declaring the RPC

To declare a RPC message, we should simply use gras_msgtype_declare_rpc(). Compared to gras_msgtype_declare() that we use to declare one-way messages, this function accepts one extra argument: the datatype of the answer message. In our example, we accept one string in input, and a long in output for the a2i conversion (a=char 2=to i=integer), and the contrary in the other direction.

Declaring a simple RPC callback: the integer to string conversion

RPC callbacks are very close to "regular" ones. The only difference is that they must call gras_msg_rpcreturn() at some point to return their result to the caller. This function accepts 3 arguments: First the timeout to use when sending back the result (we must use callbacks when doing network communication to avoid deadlocks and such issues). The second argument is the callback context that the callback got as first argument. It denotes how to reach the caller and such. The last argument is a pointer to a variable containing the result to pass to the caller.

Having the callee explicitly returning data to the caller allows to free data that were allocated to do the job asked by the client, as in this example.

int server_convert_i2a_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  long data = *(long *) payload;
  char *result;
  char *p;

  XBT_INFO("Convert %ld to string", data);
  result = bprintf("%ld", data);
  XBT_INFO("%ld converted to string: %s", data, result);

  gras_msg_rpcreturn(60, ctx, &result);
  free(result);
  return 0;
}                               /* end_of_convert_callback */

RPC and exceptions: the string to integer conversion

When converting strings into integer, we must deal with the possibility that the provided string is not a number. This is done very easily by raising an exception in the RPC callback. This exception will get captured by the middleware running the callback on the server side, sent accross the network to the client side, and revived here. In short, exceptions raised on callee side get passed automagically to the caller.

int server_convert_a2i_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  char *string = *(char **) payload;
  long result;
  char *p;

  XBT_INFO("Convert %s to long", string);
  result = strtol(string, &p, 10);

  if (*p != '\0')
    THROWF(arg_error, 0,
           "Error while converting %s: this does not seem to be a valid number (problem at '%s')",
           string, p);

  gras_msg_rpcreturn(60, ctx, &result);
  return 0;
}                               /* end_of_convert_callback */

The rest of the story

The rest of the story is not really exciting. The server and the client are very classical compared to what we saw so far. We simply have a specific message "done" to stop the server when the client is done using it.

This may also be the first time you see the xbt_ex_display() function, which allows to display an exception as if it were not catched without killing the process.

Recapping everything together

The program now reads:

/* Copyright (c) 2006, 2007, 2009, 2010. The SimGrid Team.
 * All rights reserved.                                                     */

/* This program is free software; you can redistribute it and/or modify it
  * under the terms of the license (GNU LGPL) which comes with this package. */

#include <stdlib.h>
#include <gras.h>

XBT_LOG_NEW_DEFAULT_CATEGORY(test, "My little example");

typedef struct {
  int done;
} server_data_t;
int server_done_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  server_data_t *globals = (server_data_t *) gras_userdata_get();
  globals->done = 1;
  XBT_INFO("Server done");

  return 0;
}                               /* end_of_done_callback */

void message_declaration(void)
{
  gras_msgtype_declare_rpc("convert a2i", gras_datadesc_by_name("string"),
                           gras_datadesc_by_name("long"));
  gras_msgtype_declare_rpc("convert i2a", gras_datadesc_by_name("long"),
                           gras_datadesc_by_name("string"));

  /* the other message allowing the client to stop the server after use */
  gras_msgtype_declare("done", NULL);
}

int server_convert_i2a_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  long data = *(long *) payload;
  char *result;
  char *p;

  XBT_INFO("Convert %ld to string", data);
  result = bprintf("%ld", data);
  XBT_INFO("%ld converted to string: %s", data, result);

  gras_msg_rpcreturn(60, ctx, &result);
  free(result);
  return 0;
}                               /* end_of_convert_callback */

int server_convert_a2i_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  char *string = *(char **) payload;
  long result;
  char *p;

  XBT_INFO("Convert %s to long", string);
  result = strtol(string, &p, 10);

  if (*p != '\0')
    THROWF(arg_error, 0,
           "Error while converting %s: this does not seem to be a valid number (problem at '%s')",
           string, p);

  gras_msg_rpcreturn(60, ctx, &result);
  return 0;
}                               /* end_of_convert_callback */


int server(int argc, char *argv[])
{
  gras_socket_t mysock;         /* socket on which I listen */
  server_data_t *globals;

  gras_init(&argc, argv);

  globals = gras_userdata_new(server_data_t *);
  globals->done = 0;

  message_declaration();
  mysock = gras_socket_server(atoi(argv[1]));

  gras_cb_register("convert a2i", &server_convert_a2i_cb);
  gras_cb_register("convert i2a", &server_convert_i2a_cb);
  gras_cb_register("done", &server_done_cb);

  while (!globals->done) {
    gras_msg_handle(-1);        /* blocking */
  }

  gras_exit();
  return 0;
}

int client(int argc, char *argv[])
{
  gras_socket_t mysock;         /* socket on which I listen */
  gras_socket_t toserver;       /* socket used to write to the server */

  gras_init(&argc, argv);

  message_declaration();
  mysock = gras_socket_server_range(1024, 10000, 0, 0);

  XBT_VERB("Client ready; listening on %d", gras_socket_my_port(mysock));

  gras_os_sleep(1.5);           /* sleep 1 second and half */
  toserver = gras_socket_client(argv[1], atoi(argv[2]));

  long long_to_convert = 4321;
  char *string_result;
  XBT_INFO("Ask to convert %ld", long_to_convert);
  gras_msg_rpccall(toserver, 60, "convert i2a", &long_to_convert,
                   &string_result);
  XBT_INFO("The server says that %ld is equal to \"%s\".", long_to_convert,
        string_result);
  free(string_result);

  char *string_to_convert = "1234";
  long long_result;
  XBT_INFO("Ask to convert %s", string_to_convert);
  gras_msg_rpccall(toserver, 60, "convert a2i", &string_to_convert,
                   &long_result);
  XBT_INFO("The server says that \"%s\" is equal to %d.", string_to_convert,
        long_result);

  xbt_ex_t e;
  string_to_convert = "azerty";
  TRY {
    gras_msg_rpccall(toserver, 60, "convert a2i", &string_to_convert,
                     &long_result);
  }
  CATCH(e) {
    XBT_INFO
        ("The server refuses to convert %s. Here is the received exception:",
         string_to_convert);
    xbt_ex_display(&e);
    xbt_ex_free(e);
    XBT_INFO("Again, previous exception was excepted");
  }

  gras_msg_send(toserver, "done", NULL);
  XBT_INFO("Stopped the server");

  gras_exit();
  return 0;
}

Which produces the expected output:

$ ./test_server & ./test_client 127.0.0.1 
[arthur:server:(12832) 0.000013] [test/INFO] Convert 4321 to string
[arthur:server:(12832) 0.000128] [test/INFO] 4321 converted to string: 4321
[arthur:server:(12832) 0.003889] [test/INFO] Convert 1234 to long
[arthur:server:(12832) 0.008356] [test/INFO] Convert azerty to long
[arthur:server:(12832) 0.053995] [gras_msg/INFO] Propagate local exception ('Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')') from 'convert a2i' RPC cb back to 127.0.0.1:1024
** SimGrid: UNCAUGHT EXCEPTION received on arthur(12832): category: invalid_arg; value: 0
** Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
** Thrown by server() in this process
[arthur:server:(12832) 0.054088] xbt/ex.c:113: [xbt_ex/CRITICAL] Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')

**   In server_convert_a2i_cb() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:48
**   In gras_msg_handle() at /home/mquinson/Code/simgrid-git/src/gras/Msg/gras_msg_exchange.c:400
**   In server() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:71
[arthur:server:(12832) 0.055022] [test/INFO] Server done
[arthur:server:(12832) 0.055063] [gras/INFO] Exiting GRAS
[arthur:client:(12837) 0.000025] [test/INFO] Ask to convert 4321
[arthur:client:(12837) 0.004416] [test/INFO] The server says that 4321 is equal to "4321".
[arthur:client:(12837) 0.004468] [test/INFO] Ask to convert 1234
[arthur:client:(12837) 0.008930] [test/INFO] The server says that "1234" is equal to 1234.
[arthur:client:(12837) 0.055412] [test/INFO] The server refuses to convert azerty. Here is the received exception:
** SimGrid: UNCAUGHT EXCEPTION received on arthur(12837): category: invalid_arg; value: 0
** Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
** Thrown by server() on host arthur(12832)
[arthur:client:(12837) 0.055505] xbt/ex.c:113: [xbt_ex/CRITICAL] Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')

**   In server_convert_a2i_cb() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:48
**   In gras_msg_handle() at /home/mquinson/Code/simgrid-git/src/gras/Msg/gras_msg_exchange.c:400
**   In server() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:71
[arthur:client:(12837) 0.055564] [test/INFO] Again, previous exception was excepted
[arthur:client:(12837) 0.056367] [test/INFO] Stopped the server
[arthur:client:(12837) 0.056404] [gras/INFO] Exiting GRAS
$
$ killall test_server
$
$ ./test_simulator platform.xml test.xml
[Boivin:client:(2) 0.000000] [test/INFO] Ask to convert 4321
[Jacquelin:server:(1) 0.000538] [test/INFO] Convert 4321 to string
[Jacquelin:server:(1) 0.000538] [test/INFO] 4321 converted to string: 4321
[Boivin:client:(2) 0.001077] [test/INFO] The server says that 4321 is equal to "4321".
[Boivin:client:(2) 0.001077] [test/INFO] Ask to convert 1234
[Jacquelin:server:(1) 0.001615] [test/INFO] Convert 1234 to long
[Boivin:client:(2) 0.002153] [test/INFO] The server says that "1234" is equal to 1234.
[Jacquelin:server:(1) 0.002692] [test/INFO] Convert azerty to long
[Jacquelin:server:(1) 0.002692] [gras_msg/INFO] Propagate local exception ('Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')') from 'convert a2i' RPC cb back to Boivin:1024
** SimGrid: UNCAUGHT EXCEPTION received on Jacquelin(1): category: invalid_arg; value: 0
** Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
** Thrown by server() in this process
[Jacquelin:server:(1) 0.002692] xbt/ex.c:113: [xbt_ex/CRITICAL] Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')

**   In server_convert_a2i_cb() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:48
**   In gras_msg_handle() at /home/mquinson/Code/simgrid-git/src/gras/Msg/gras_msg_exchange.c:400
**   In server() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:71
**   In xbt_ctx_sysv_stop() at /home/mquinson/Code/simgrid-git/src/xbt/xbt_context_sysv.c:257
**   In makecontext() at ??:0
**   In update_actions_state() at /home/mquinson/Code/simgrid-git/src/surf/surf_timer.c:108
**   In surf_solve() at /home/mquinson/Code/simgrid-git/src/surf/surf.c:543
**   In SIMIX_solve() at /home/mquinson/Code/simgrid-git/src/simix/smx_global.c:347
**   In gras_main() at /home/mquinson/Code/simgrid-git/src/gras/Virtu/sg_process.c:222
[Boivin:client:(2) 0.003490] [test/INFO] The server refuses to convert azerty. Here is the received exception:
** SimGrid: UNCAUGHT EXCEPTION received on Boivin(2): category: invalid_arg; value: 0
** Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
** Thrown by server() on host Jacquelin(1)
[Boivin:client:(2) 0.003490] xbt/ex.c:113: [xbt_ex/CRITICAL] Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')

**   In server_convert_a2i_cb() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:48
**   In gras_msg_handle() at /home/mquinson/Code/simgrid-git/src/gras/Msg/gras_msg_exchange.c:400
**   In server() at /home/mquinson/Code/simgrid-git/doc/gtut-files/test.c:71
**   In xbt_ctx_sysv_stop() at /home/mquinson/Code/simgrid-git/src/xbt/xbt_context_sysv.c:257
**   In makecontext() at ??:0
**   In update_actions_state() at /home/mquinson/Code/simgrid-git/src/surf/surf_timer.c:108
**   In surf_solve() at /home/mquinson/Code/simgrid-git/src/surf/surf.c:543
**   In SIMIX_solve() at /home/mquinson/Code/simgrid-git/src/simix/smx_global.c:347
**   In gras_main() at /home/mquinson/Code/simgrid-git/src/gras/Virtu/sg_process.c:222
[Boivin:client:(2) 0.003490] [test/INFO] Again, previous exception was excepted
[Boivin:client:(2) 0.004027] [test/INFO] Stopped the server
[Boivin:client:(2) 0.004027] [gras/INFO] Exiting GRAS
[Jacquelin:server:(1) 0.004027] [test/INFO] Server done
[Jacquelin:server:(1) 0.004027] [gras/INFO] Exiting GRAS
$

Now, you know how to send messages, attach callbacks and do RPCs. The next lesson will learn you the last missing part of the messaging library: Lesson 11: Explicitely waiting for messages


Back to the main Simgrid Documentation page The version of Simgrid documented here is v3.6.2.
Documentation of other versions can be found in their respective archive files (directory doc/html).
Generated for SimGridAPI by doxygen