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.