In the previous example I showed you how to hook libcouchbase into your own event loop, but not everyone wants to use it asynchronously. The best part of an asynchronous library is that it's pretty easy to make a synchronous library on top of it. All you need to do is to call the function and wait for it to complete, but in libcouchbase I went the extra mile. You may use it fully asynchronous, you may use it fully synchronous or you may use it somewhat in the middle.
The easiest way to use libcouchbase in a synchronous way is to toggle it's internals to the syncmode setting by calling:
lcb_behavior_set_syncmode(instance, LCB_SYNCHRONOUS);
One "drawback" with the syncmode setting is that you can't execute a chunk of commands concurrently. Lets look at the following example:
lcb_store(instance, NULL, 15, store_commands);
lcb_get(instance, NULL, 2, get_commands);
With syncmode enabled
all of the store commands must have been executed and the response received before any of the get commands is sent (even if they don't hit the same servers). It would improve the latency of your application if you could shuffle as much data as possible over the network before blocking to wait for the result. That's when you want to set the syncmode to
LCB_ASYNCHRONOUS (the default) and use
lcb_wait() instead:
lcb_store(instance, NULL, 15, store_commands);
lcb_get(instance, NULL, 2, get_commands);
lcb_wait(instance);
when
lcb_wait() returns we know that all of the above commands are executed (and the appropriate callbacks called).
So let's whip up a small program that shows you how to use the syncmode setting. To keep the example as small as possible I'm going to skip error recovery etc. Let's jump straight to the
main() method:
int main(void)
{
lcb_error_t error;
lcb_t instance = create_instance();
if ((error = lcb_connect(instance)) != LCB_SUCCESS) {
fprintf(stderr, "Failed to connect to cluster: %s\n",
lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
set_key(instance);
get_key(instance);
lcb_destroy(instance);
exit(EXIT_SUCCESS);
}
As you see, the program is pretty simple. First we create a libcouchbase instance and connect to the cluster; set and get a key before we clean up and exits.
Let's look inside create_instance:
static lcb_t create_instance(void)
{
lcb_t instance;
struct lcb_create_st copt;
lcb_error_t error;
memset(&copt, 0, sizeof(copt));
copt.v.v0.host = "myserver:8091";
if ((error = lcb_create(&instance, &copt)) != LCB_SUCCESS) {
fprintf(stderr, "Failed to create libcuchbase instance: %s\n",
lcb_strerror(NULL, error));
exit(EXIT_FAILURE);
}
lcb_behavior_set_syncmode(instance, LCB_SYNCHRONOUS);
lcb_set_error_callback(instance, error_handler);
lcb_set_store_callback(instance, store_handler);
lcb_set_get_callback(instance, get_handler);
return instance;
}
This looks pretty much like how we normally do stuff except for the line I've marked as bold. From that line all of the lcb_ functions will block until we've received a reply from the server for the requested operation. You might be curious about how my handler functions looks like, and they are pretty boring:
static void error_handler(lcb_t instance, lcb_error_t err, const char *info)
{
fprintf(stderr, "FATAL! an error occured: %s (%s)\n",
lcb_strerror(instance, err), info ? info : "none");
exit(EXIT_FAILURE);
}
static void store_handler(lcb_t instance, const void *cookie,
lcb_storage_t operation, lcb_error_t error,
const lcb_store_resp_t *resp)
{
(void)cookie; (void)operation;
if (error != LCB_SUCCESS) {
fprintf(stderr, "Failed to store the key on the server: %s\n",
lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
if (resp->version == 0) {
fprintf(stdout, "Successfully stored \"");
fwrite(resp->v.v0.key, 1, resp->v.v0.nkey, stdout);
fprintf(stdout, "\"\n");
}
}
static void get_handler(lcb_t instance, const void *cookie,
lcb_error_t error, const lcb_get_resp_t *resp)
{
(void)cookie;
if (error != LCB_SUCCESS) {
fprintf(stderr, "Failed to read the key from the server: %s\n",
lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
/* Validate that I read the correct key and value back */
if (resp->version != 0) {
fprintf(stderr,
"WARNING: I don't support this version of libcouchbase\n");
exit(EXIT_FAILURE);
}
fprintf(stdout, "I received \"");
fwrite(resp->v.v0.key, 1, resp->v.v0.nkey, stdout);
fprintf(stdout, "\" with the value: [");
fwrite(resp->v.v0.bytes, 1, resp->v.v0.nbytes, stdout);
fprintf(stdout, "]\n");
}
If we jump back to our main function the next instructions to execute would be:
if ((error = lcb_connect(instance)) != LCB_SUCCESS) {
fprintf(stderr, "Failed to connect to cluster: %s\n",
lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
Now that we've enabled the
LCB_SYNCHRONOUS mode in libcouchbase this will be a blocking call (I fixed that bug today Sept 17th), so that when the method returns we know the topology of the Couchbase cluster and we're about to store our object there in the set_key method that looks like:
static const char const *key = "mykey";
static const char const *value = "myvalue";
static void set_key(lcb_t instance)
{
lcb_store_cmd_t cmd;
lcb_error_t error;
const lcb_store_cmd_t * const commands[] = { &cmd };
memset(&cmd, 0, sizeof(cmd));
cmd.v.v0.key = key;
cmd.v.v0.nkey = strlen(key);
cmd.v.v0.bytes = value;
cmd.v.v0.nbytes = strlen(value);
cmd.v.v0.operation = LCB_SET;
if ((error = lcb_store(instance, NULL, 1, commands)) != LCB_SUCCESS) {
fprintf(stderr, "Failed to store key: %s\n",
lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
}
This looks exactly like how we would have done it in our asynchronous application, and here is a very important detail. The return code of the function is
not the return code of the actual operation! It means the exact same thing as it used to: if we encountered any problems
initiating the operation. It is the
callback function that will tell you the result of the operation from the server!
To make the example complete let's look at the get_key function as well:
static void get_key(lcb_t instance)
{
lcb_get_cmd_t cmd;
lcb_error_t error;
const lcb_get_cmd_t * const commands[] = { &cmd };
memset(&cmd, 0, sizeof(cmd));
cmd.v.v0.key = key;
cmd.v.v0.nkey = strlen(key);
if ((error = lcb_get(instance, NULL, 1, commands)) != LCB_SUCCESS) {
fprintf(stderr, "Failed to get key: %s\n",
lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
}
Pretty simple and straight forward.
Happy hacking!