C API

While Warabi is primarily a C++ library, it also provides a C API for use with Margo-based applications. This tutorial covers the C API, which is useful when you need to integrate Warabi with C code or prefer a C-style interface.

Headers and types

Include the following headers:

#include <warabi/client.h> // if using client library
#include <warabi/server.h> // if using server library

Core types:

  • warabi_client_t: Client instance

  • warabi_provider_t: Provider instance

  • warabi_target_handle_t: Handle to a remote target

  • warabi_region_t: Region identifier (16-byte opaque structure)

  • warabi_async_request_t: Asynchronous request handle

  • warabi_err_t: Error code (check with != WARABI_SUCCESS)

Server-side (Provider)

Creating a provider requires initializing Margo and registering a Warabi provider with a configuration:

    /* Initialize Margo */
    margo_instance_id mid = margo_init(argv[1], MARGO_SERVER_MODE, 0, 0);
    if(mid == MARGO_INSTANCE_NULL) {
        fprintf(stderr, "Failed to initialize Margo\n");
        return -1;
    }

    /* Warabi configuration (JSON) */
    const char* config = "{"
        "\"target\": "
            "{\"type\": \"memory\", \"config\": {}}"
    "}";

    /* Register Warabi provider */
    warabi_provider_t provider;
    warabi_err_t ret = warabi_provider_register(
        &provider,              /* Output provider */
        mid,                    /* Margo instance */
        42,                     /* Provider ID */
        config,                 /* Configuration */
        NULL                    /* Default init args */
    );

Key points:

  • Configuration is provided as a JSON string

  • Use "target": {} (singular, not "targets": [])

  • Provider ID should match what clients will use

  • Call margo_wait_for_finalize() to keep server running

Client initialization

Creating a client:

Initialize Margo in client mode and create a Warabi client:

    /* Initialize Margo */
    mid = margo_init("na+sm", MARGO_CLIENT_MODE, 0, 0);
    if(mid == MARGO_INSTANCE_NULL) {
        fprintf(stderr, "Failed to initialize Margo\n");
        return -1;
    }
    printf("Client initialized\n");

    /* Create Warabi client */
    ret = warabi_client_create(mid, &client);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create Warabi client\n");
        goto cleanup;
    }

Creating target handles:

To access a target, create a handle with the server address and provider ID:

    /* Create target handle */
    ret = warabi_client_make_target_handle(
        client,
        server_addr,
        provider_id,
        &target
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create target handle\n");
        goto cleanup;
    }
    printf("Connected to target\n");

Cleanup:

Always free resources in reverse order of creation:

    /* Free resources */
    if(target != WARABI_TARGET_HANDLE_NULL)
        warabi_target_handle_free(target);
    if(client != WARABI_CLIENT_NULL)
        warabi_client_free(client);
    if(addr != HG_ADDR_NULL)
        margo_addr_free(mid, addr);
    if(mid != MARGO_INSTANCE_NULL)
        margo_finalize(mid);

Region operations

Creating a region:

Regions must be created with a fixed size:

    /* Create a region */
    warabi_region_t region;
    size_t region_size = 1024;

    ret = warabi_create(
        target,
        region_size,
        &region,
        WARABI_ASYNC_REQUEST_IGNORE
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create region\n");
        goto cleanup;
    }
    printf("Created region\n");

Writing data:

Write data to a region at a specific offset:

    /* Write data to the region */
    const char* message = "Hello, Warabi!";
    size_t message_size = strlen(message);

    ret = warabi_write(
        target,
        region,
        0,           /* offset */
        message,
        message_size,
        false,       /* persist */
        WARABI_ASYNC_REQUEST_IGNORE
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to write data\n");
        goto cleanup_region;
    }
    printf("Wrote %zu bytes\n", message_size);

The persist parameter controls whether data is immediately flushed to durable storage (for pmem and abtio backends).

Reading data:

Read data back from a region:

    /* Read data back */
    char buffer[1024];
    size_t buffer_size = message_size;

    ret = warabi_read(
        target,
        region,
        0,           /* offset */
        buffer,
        buffer_size,
        WARABI_ASYNC_REQUEST_IGNORE
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to read data\n");
        goto cleanup_region;
    }
    printf("Read: %.*s\n", (int)buffer_size, buffer);

Erasing a region:

Clean up regions when done:

cleanup_region:
    /* Erase the region */
    ret = warabi_erase(target, region, WARABI_ASYNC_REQUEST_IGNORE);
    if(ret == WARABI_SUCCESS) {
        printf("Region erased\n");
    }

Asynchronous operations

For better performance, use asynchronous operations that don’t block:

Async write:

Issue a write operation that returns immediately:

    /* Async write example */
    const char* data = "Async write test";
    warabi_async_request_t write_req;

    printf("Issuing async write...\n");
    ret = warabi_write(
        target,
        region,
        0,              /* offset */
        data,
        strlen(data),
        false,          /* persist */
        &write_req      /* Async request */
    );

    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to issue async write\n");
        goto cleanup;
    }

The operation continues in the background. You can do other work while it completes.

Waiting for completion:

Use warabi_wait() to wait for an async operation to complete:

    /* Do other work while write is in progress */
    printf("Write in progress, doing other work...\n");

    /* Wait for write to complete */
    ret = warabi_wait(write_req);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Async write failed\n");
        goto cleanup;
    }
    printf("Async write completed\n");

Testing for completion:

Use warabi_test() to check if an operation has completed without blocking:

    /* Test for completion */
    bool completed = false;
    int iterations = 0;

    while(!completed) {
        ret = warabi_test(read_req, &completed);
        if(completed) {
            printf("Async read completed after %d checks\n", iterations);
        } else {
            iterations++;
            /* Do other work... */
        }
    }

Complete examples

Client example:

Full working client that creates a region, writes data, reads it back, and verifies:

/*
 * (C) 2024 The University of Chicago
 *
 * See COPYRIGHT in top-level directory.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <margo.h>
#include <warabi/client.h>

int main(int argc, char** argv)
{
    if(argc != 3) {
        fprintf(stderr, "Usage: %s <server_addr> <provider_id>\n", argv[0]);
        return -1;
    }

    const char* server_addr = argv[1];
    uint16_t provider_id = (uint16_t)atoi(argv[2]);

    warabi_err_t ret;
    margo_instance_id mid = MARGO_INSTANCE_NULL;
    hg_addr_t addr = HG_ADDR_NULL;
    warabi_client_t client = WARABI_CLIENT_NULL;
    warabi_target_handle_t target = WARABI_TARGET_HANDLE_NULL;

    /* Initialize Margo */
    mid = margo_init("na+sm", MARGO_CLIENT_MODE, 0, 0);
    if(mid == MARGO_INSTANCE_NULL) {
        fprintf(stderr, "Failed to initialize Margo\n");
        return -1;
    }
    printf("Client initialized\n");

    /* Create Warabi client */
    ret = warabi_client_create(mid, &client);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create Warabi client\n");
        goto cleanup;
    }

    /* Look up server address */
    hg_return_t hret = margo_addr_lookup(mid, server_addr, &addr);
    if(hret != HG_SUCCESS) {
        fprintf(stderr, "Failed to lookup server address\n");
        goto cleanup;
    }

    /* Create target handle */
    ret = warabi_client_make_target_handle(
        client,
        server_addr,
        provider_id,
        &target
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create target handle\n");
        goto cleanup;
    }
    printf("Connected to target\n");

    /* Create a region */
    warabi_region_t region;
    size_t region_size = 1024;

    ret = warabi_create(
        target,
        region_size,
        &region,
        WARABI_ASYNC_REQUEST_IGNORE
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create region\n");
        goto cleanup;
    }
    printf("Created region\n");

    /* Write data to the region */
    const char* message = "Hello, Warabi!";
    size_t message_size = strlen(message);

    ret = warabi_write(
        target,
        region,
        0,           /* offset */
        message,
        message_size,
        false,       /* persist */
        WARABI_ASYNC_REQUEST_IGNORE
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to write data\n");
        goto cleanup_region;
    }
    printf("Wrote %zu bytes\n", message_size);

    /* Read data back */
    char buffer[1024];
    size_t buffer_size = message_size;

    ret = warabi_read(
        target,
        region,
        0,           /* offset */
        buffer,
        buffer_size,
        WARABI_ASYNC_REQUEST_IGNORE
    );
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to read data\n");
        goto cleanup_region;
    }
    printf("Read: %.*s\n", (int)buffer_size, buffer);

    /* Verify data */
    if(memcmp(buffer, message, message_size) == 0) {
        printf("SUCCESS: Data verified\n");
    } else {
        printf("ERROR: Data mismatch\n");
    }

cleanup_region:
    /* Erase the region */
    ret = warabi_erase(target, region, WARABI_ASYNC_REQUEST_IGNORE);
    if(ret == WARABI_SUCCESS) {
        printf("Region erased\n");
    }

cleanup:
    /* Free resources */
    if(target != WARABI_TARGET_HANDLE_NULL)
        warabi_target_handle_free(target);
    if(client != WARABI_CLIENT_NULL)
        warabi_client_free(client);
    if(addr != HG_ADDR_NULL)
        margo_addr_free(mid, addr);
    if(mid != MARGO_INSTANCE_NULL)
        margo_finalize(mid);

    return (ret == WARABI_SUCCESS) ? 0 : -1;
}

Server example:

Warabi server that exposes a memory-backed target:

/*
 * (C) 2024 The University of Chicago
 *
 * See COPYRIGHT in top-level directory.
 */
#include <stdio.h>
#include <stdlib.h>
#include <margo.h>
#include <warabi/server.h>

int main(int argc, char** argv)
{
    if(argc != 2) {
        fprintf(stderr, "Usage: %s <protocol>\n", argv[0]);
        return -1;
    }

    /* Initialize Margo */
    margo_instance_id mid = margo_init(argv[1], MARGO_SERVER_MODE, 0, 0);
    if(mid == MARGO_INSTANCE_NULL) {
        fprintf(stderr, "Failed to initialize Margo\n");
        return -1;
    }

    /* Warabi configuration (JSON) */
    const char* config = "{"
        "\"target\": "
            "{\"type\": \"memory\", \"config\": {}}"
    "}";

    /* Register Warabi provider */
    warabi_provider_t provider;
    warabi_err_t ret = warabi_provider_register(
        &provider,              /* Output provider */
        mid,                    /* Margo instance */
        42,                     /* Provider ID */
        config,                 /* Configuration */
        NULL                    /* Default init args */
    );

    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create provider\n");
        margo_finalize(mid);
        return -1;
    }

    /* Print server address */
    hg_addr_t self_addr;
    margo_addr_self(mid, &self_addr);
    char self_addr_str[128];
    hg_size_t self_addr_size = 128;
    margo_addr_to_string(mid, self_addr_str, &self_addr_size, self_addr);
    margo_addr_free(mid, self_addr);

    printf("Warabi server running at: %s\n", self_addr_str);
    printf("Provider ID: 42\n");

    /* Wait for finalize */
    margo_wait_for_finalize(mid);

    /* Cleanup */
    warabi_provider_deregister(provider);
    margo_finalize(mid);

    return 0;
}

Async example:

Demonstrates asynchronous write and read operations with completion testing:

/*
 * (C) 2024 The University of Chicago
 *
 * See COPYRIGHT in top-level directory.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <margo.h>
#include <warabi/client.h>

int main(int argc, char** argv)
{
    if(argc != 3) {
        fprintf(stderr, "Usage: %s <server_addr> <provider_id>\n", argv[0]);
        return -1;
    }

    const char* server_addr = argv[1];
    uint16_t provider_id = (uint16_t)atoi(argv[2]);
    warabi_err_t ret;

    /* Initialize Margo */
    margo_instance_id mid = margo_init("na+sm", MARGO_CLIENT_MODE, 0, 0);
    if(mid == MARGO_INSTANCE_NULL) {
        fprintf(stderr, "Failed to initialize Margo\n");
        return -1;
    }

    /* Create Warabi client */
    warabi_client_t client;
    ret = warabi_client_create(mid, &client);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create client\n");
        margo_finalize(mid);
        return -1;
    }

    /* Create target handle */
    warabi_target_handle_t target;
    ret = warabi_client_make_target_handle(client, server_addr, provider_id, &target);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create target handle\n");
        warabi_client_free(client);
        margo_finalize(mid);
        return -1;
    }

    /* Create a region */
    warabi_region_t region;
    ret = warabi_create(target, 1024, &region, WARABI_ASYNC_REQUEST_IGNORE);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to create region\n");
        goto cleanup;
    }

    /* Async write example */
    const char* data = "Async write test";
    warabi_async_request_t write_req;

    printf("Issuing async write...\n");
    ret = warabi_write(
        target,
        region,
        0,              /* offset */
        data,
        strlen(data),
        false,          /* persist */
        &write_req      /* Async request */
    );

    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to issue async write\n");
        goto cleanup;
    }

    /* Do other work while write is in progress */
    printf("Write in progress, doing other work...\n");

    /* Wait for write to complete */
    ret = warabi_wait(write_req);
    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Async write failed\n");
        goto cleanup;
    }
    printf("Async write completed\n");

    /* Async read example */
    char buffer[1024];
    warabi_async_request_t read_req;

    printf("Issuing async read...\n");
    ret = warabi_read(
        target,
        region,
        0,              /* offset */
        buffer,
        strlen(data),
        &read_req       /* Async request */
    );

    if(ret != WARABI_SUCCESS) {
        fprintf(stderr, "Failed to issue async read\n");
        goto cleanup;
    }

    /* Test for completion */
    bool completed = false;
    int iterations = 0;

    while(!completed) {
        ret = warabi_test(read_req, &completed);
        if(completed) {
            printf("Async read completed after %d checks\n", iterations);
        } else {
            iterations++;
            /* Do other work... */
        }
    }

    buffer[strlen(data)] = '\0';
    printf("Read: %s\n", buffer);

    /* Cleanup */
    warabi_erase(target, region, WARABI_ASYNC_REQUEST_IGNORE);

cleanup:
    warabi_target_handle_free(target);
    warabi_client_free(client);
    margo_finalize(mid);

    return (ret == WARABI_SUCCESS) ? 0 : -1;
}