Writing a Bedrock module in C
If you have programmed your own Mochi component, writing a module to make it usable with Bedrock is really not difficult. Such a module consists of a single dynamic library (.so) that can be implemented as show in the example bellow.
#include <bedrock/module.h>
#include <abt-io.h>
#include <string.h>
static struct bedrock_dependency ModuleA_provider_dependencies[] = {
{ "io", "abt_io", BEDROCK_REQUIRED },
{ "sdskv_ph", "sdskv", BEDROCK_ARRAY | BEDROCK_REQUIRED },
BEDROCK_NO_MORE_DEPENDENCIES
};
static struct bedrock_dependency ModuleA_client_dependencies[] = {
BEDROCK_NO_MORE_DEPENDENCIES
};
static int ModuleA_register_provider(
bedrock_args_t args,
bedrock_module_provider_t* provider)
{
margo_instance_id mid = bedrock_args_get_margo_instance(args);
uint16_t provider_id = bedrock_args_get_provider_id(args);
ABT_pool pool = bedrock_args_get_pool(args);
const char* config = bedrock_args_get_config(args);
const char* name = bedrock_args_get_name(args);
abt_io_instance_id* io = bedrock_args_get_dependency(args, "io", 0);
(void)io; // not using it
size_t num_databases = bedrock_args_get_num_dependencies(args, "sdskv_ph");
unsigned i;
for(i=0; i < num_databases; i++) {
void* sdskv_ph = bedrock_args_get_dependency(args, "sdskv_ph", i);
(void)sdskv_ph; // not using it
/* ... */
}
*provider = strdup("ModuleA:provider"); // just to put something in *provider
printf("Registered a provider from module A\n");
printf(" -> mid = %p\n", (void*)mid);
printf(" -> provider id = %d\n", provider_id);
printf(" -> pool = %p\n", (void*)pool);
printf(" -> config = %s\n", config);
printf(" -> name = %s\n", name);
return BEDROCK_SUCCESS;
}
static int ModuleA_deregister_provider(
bedrock_module_provider_t provider)
{
free(provider);
printf("Deregistered a provider from module A\n");
return BEDROCK_SUCCESS;
}
static char* ModuleA_get_provider_config(
bedrock_module_provider_t provider) {
(void)provider;
return strdup("{}");
}
static int ModuleA_init_client(
bedrock_args_t args,
bedrock_module_client_t* client)
{
(void)args;
*client = strdup("ModuleA:client");
printf("Registered a client from module A\n");
return BEDROCK_SUCCESS;
}
static int ModuleA_finalize_client(
bedrock_module_client_t client)
{
free(client);
printf("Finalized a client from module A\n");
return BEDROCK_SUCCESS;
}
static char* ModuleA_get_client_config(
bedrock_module_client_t client) {
(void)client;
return strdup("{}");
}
static int ModuleA_create_provider_handle(
bedrock_module_client_t client,
hg_addr_t address,
uint16_t provider_id,
bedrock_module_provider_handle_t* ph)
{
(void)client;
(void)address;
(void)provider_id;
*ph = strdup("ModuleA:provider_handle");
printf("Created provider handle from module A\n");
return BEDROCK_SUCCESS;
}
static int ModuleA_destroy_provider_handle(
bedrock_module_provider_handle_t ph)
{
free(ph);
printf("Destroyed provider handle from module A\n");
return BEDROCK_SUCCESS;
}
static struct bedrock_module ModuleA = {
.register_provider = ModuleA_register_provider,
.deregister_provider = ModuleA_deregister_provider,
.get_provider_config = ModuleA_get_provider_config,
.init_client = ModuleA_init_client,
.finalize_client = ModuleA_finalize_client,
.get_client_config = ModuleA_get_client_config,
.create_provider_handle = ModuleA_create_provider_handle,
.destroy_provider_handle = ModuleA_destroy_provider_handle,
.provider_dependencies = ModuleA_provider_dependencies,
.client_dependencies = ModuleA_client_dependencies
};
BEDROCK_REGISTER_MODULE(module_a, ModuleA)
Module dependencies
The first thing to declare in this module is the dependencies of providers
and client.
This is the list of dependencies that your component’s providers and clients
are expecting. It is expressed as two arrays of bedrock_dependency
structures terminated by a BEDROCK_NO_MORE_DEPENDENCIES
entry.
Each of the elements in this array has a dependency name,
a type, and a flag. The name is what will identify the dependency
in the JSON configuration file. For instance, our provider here has
two dependencies, io and sdskv_ph, hence the dependencies
field of a module_a provider in a JSON configuration should have
a io
entry and an sdskv_ph
entry. Our client has no dependency.
The type of dependency tells Bedrock what to look for. The abt_io
type tells Bedrock to find an ABT-IO instance. The sdskv
type
tells Bedrock to find an object (provider, client, or provider handle)
from the SDSKV module.
Finally the flag may be BEDROCK_OPTIONAL
for optional dependencies,
BEDROCK_REQUIRED
for a required dependencies,
BEDROCK_ARRAY
for an array of dependencies
(including an empty array), or BEDROCK_REQUIRED | BEDROCK_ARRAY
for
an array of at least one element.
Given the above dependency declarations for our module, a valid provider instantiation in the JSON document might look like the following.
{
"libraries" : {
"module_a" : "path/to/libbedrock-module-a.so"
},
"providers" : [
{
"name" : "ProviderA",
"type" : "module_a",
"provider_id" : 42,
"pool" : "my_pool",
"config" : {},
"dependencies" : {
"io" : "my_abt_io",
"sdskv_ph" : [ "my_sdskv@ssg://my_group/0", "other_sdskv@local" ]
}
}
]
}
The libraries
section must associate the module_a type with the
library we just built. Assuming an ABT-IO instance named “my_abt_io” was
declared in the abt_io
section of the document, the “io” dependency
will be resolved to that instance. The “sdskv_ph” dependency will resolve
to an array of two provider handles pointing to SDSKV providers.
Callback functions
The rest of the module consists of callback functions to register and deregister a provider, get a provider’s configuration, initialize and finalize a client, and create and destroy a provider handle for your module.
The provider registration and client initialization callbacks are being passed
a bedrock_args_t
handle from which we can extrat various configuration
parameters, including the Margo instance, the provider id (in provider registration),
the Argobots pool (same), the configuration (config
field
from the JSON configuration of the provider or the client), the name of the provider/client,
as well as its dependencies using bedrock_args_get_num_dependencies
and
bedrock_args_get_dependency
(dependencies that are not an array are treated
like an array of size 1. bedrock_args_get_num_dependencies
can also be
used to check for the presence of an optional dependency).
Note that Bedrock checks ahead of time that all required dependencies are present, hence the provider registration function does not have to check this.
Note
The function that returns a provider’s configuration must return a heap-allocated
string, that Bedrock will later free
by itself.
Important
At the moment, Bedrock initializes clients before providers, hence clients cannot depend on providers. Clients can only depend on provider handles to SSG groups, ABT-IO instances, other clients defined earlier, and provider handles.
Registering the module
The last things to add to our library is to fill out a bedrock_module
structure
with our callbacks and dependency array, and to call BEDROCK_REGISTER_MODULE(module_a, ModuleA)
.
The first argument of this macro is the name under which we want our module to be known.
The second is the name of the bedrock_module
structure we have built.
Note
All the above functions as well as the dependency array and the module structure instance
can safely be declared static
. The only symbole required by the Bedrock to
work with the library will be generated by the BEDROCK_REGISTER_MODULE
macro.