SimGrid
Lesson 5: Using globals in processes

Table of Contents


Introduction

Callbacks are great to express your processes as state machines, but they pose another problem: callbacks don't have acces to the variable declared within the scope of the process' main function (of course). You should however resist to the temptation to declare globals outside of the scope of the functions, or you won't be able to use more than one process of each type in the simulation. Remember, all gras processes run as thread within the same naming space in SG so your globals will be shared between the several instances of your process, leading to bad problems.

Instead, you you have to put all globals in a structure, and let GRAS handle it with the gras_userdata_* functions (there is only 3 of them ;).

Putting globals into action

We will now modify the example to add a "kill" message, and let the server loop on incoming messages until it gets such a message. We only need a boolean, so the structure is quite simple:

typedef struct {
  int killed;
} server_data_t;

Then, we need to create this structure in the process main function. We could use either gras_userdata_new() or gras_userdata_set(). The former is an helper macro mallocing the space needed by the structure and passing it to gras using the latter function. If you go for gras_userdata_set(), you should pass it a pointer to your data you want to retrieve afterward.

  globals = gras_userdata_new(server_data_t);

BEWARE, the gras_userdata_new expects the pointed type, not the pointer type. As you can see, in our example, you should pass server_data_t to the macro, even if the global variable is later defined as being of type server_data_t*.

Once you declared a global that way, retriving this (for example in a callback) is really easy:

  server_data_t *globals = (server_data_t *) gras_userdata_get();

We can now write the callback, which simply retrive the globals and change the value of the kileld field.

int server_kill_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  gras_socket_t client = gras_msg_cb_ctx_from(ctx);
  server_data_t *globals = (server_data_t *) gras_userdata_get();

  fprintf(stderr, "Argh, killed by %s:%d! Bye folks...\n",
          gras_socket_peer_name(client), gras_socket_peer_port(client));

  globals->killed = 1;

  return 0;
}                               /* end_of_kill_callback */

And we replace the single gras_msg_handle() of the server main function by a loop:

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

Please note that in our example, only one process creates a global structure. But this is naturally completely ok to have several processes creating their globals this way. Each of these globals will be separated, so process A cannot access globals defined by process B. Maybe this implies that the name "globals" is a bit misleading. It should be "process state" or something similar.

Common pitfall of globals

There is an error that I do myself every other day using globals in GRAS. This is to write something like:

int server(int argc, char *argv[]) {
  server_data_t globals=gras_user_new(server_data_t);
  /* other variable definition */
  
  gras_init(&argc, argv);
  
  /* rest of the code */
}

The problem is that you call gras_userdata_new() before gras_init(). Doing so, embarass GRAS since it does not have its internal buffer initialized yet, and cannot store your data anywhere. That is why doing so triggers an error at run time.

Also, as noted above, the gras_userdata_new expects the pointed type, not the pointer type. As you can see, in our example, you should pass server_data_t to the macro, even if the global variable is later defined as being of type server_data_t*.

Recaping everything together

The whole 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 <stdio.h>
#include <gras.h>

typedef struct {
  int killed;
} server_data_t;


int server_kill_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  gras_socket_t client = gras_msg_cb_ctx_from(ctx);
  server_data_t *globals = (server_data_t *) gras_userdata_get();

  fprintf(stderr, "Argh, killed by %s:%d! Bye folks...\n",
          gras_socket_peer_name(client), gras_socket_peer_port(client));

  globals->killed = 1;

  return 0;
}                               /* end_of_kill_callback */

int server_hello_cb(gras_msg_cb_ctx_t ctx, void *payload)
{
  gras_socket_t client = gras_msg_cb_ctx_from(ctx);

  fprintf(stderr, "Cool, we received the message from %s:%d.\n",
          gras_socket_peer_name(client), gras_socket_peer_port(client));

  return 0;
}                               /* end_of_hello_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->killed = 0;

  gras_msgtype_declare("hello", NULL);
  gras_msgtype_declare("kill", NULL);
  mysock = gras_socket_server(atoi(argv[1]));

  gras_cb_register("hello", &server_hello_cb);
  gras_cb_register("kill", &server_kill_cb);

  while (!globals->killed) {
    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);

  gras_msgtype_declare("hello", NULL);
  gras_msgtype_declare("kill", NULL);
  mysock = gras_socket_server_range(1024, 10000, 0, 0);

  fprintf(stderr, "Client ready; listening on %d\n",
          gras_socket_my_port(mysock));

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

  gras_msg_send(toserver, "hello", NULL);
  fprintf(stderr,
          "we sent the data to the server on %s. Let's do it again for fun\n",
          gras_socket_peer_name(toserver));
  gras_msg_send(toserver, "hello", NULL);

  fprintf(stderr, "Ok. Enough. Have a rest, and then kill the server\n");
  gras_os_sleep(5);             /* sleep 1 second and half */
  gras_msg_send(toserver, "kill", NULL);

  gras_exit();
  return 0;
}

And here is the output (unchanged wrt previous version):

$ ./test_server 12345 & ./test_client 127.0.0.1 12345
Client ready; listening on 1024
we sent the data to the server on 127.0.0.1. Let's do it again for fun
Ok. Enough. Have a rest, and then kill the server
Cool, we received the message from 127.0.0.1:1024.
Cool, we received the message from 127.0.0.1:1024.
[arthur:client:(28822) 0.000027] [gras/INFO] Exiting GRAS
Argh, killed by 127.0.0.1:1024! Bye folks...
[arthur:server:(28820) 0.000013] [gras/INFO] Exiting GRAS
$
$ ./test_simulator platform.xml test.xml
Client ready; listening on 1024
we sent the data to the server on Jacquelin. Let's do it again for fun
Cool, we received the message from Boivin:1024.
Ok. Enough. Have a rest, and then kill the server
Cool, we received the message from Boivin:1024.
[Boivin:client:(2) 0.000000] [gras/INFO] Exiting GRAS
Argh, killed by Boivin:1024! Bye folks...
[Jacquelin:server:(1) 0.000000] [gras/INFO] Exiting GRAS
$

That's it, we're done. We have a server able to handle any number of messages, which the client can stop remotely properly. That's already something, hu?

Go to Lesson 6: Logging informations properly


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