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 instancewarabi_provider_t: Provider instancewarabi_target_handle_t: Handle to a remote targetwarabi_region_t: Region identifier (16-byte opaque structure)warabi_async_request_t: Asynchronous request handlewarabi_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,
®ion,
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,
®ion,
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, ®ion, 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;
}