Using External Event Loops
This section describes how you can use the library in a non-blocking fashion through integrating with an existing event loop created by your application.
The C SDK makes use of its own I/O abstraction layer known as IOPS (I/O
OperationS) to communicate with the underlying platform and I/O frameworks. This abstraction
layer provides an API which is used to perform common event loop operations, such as:
- Creating a new socket
- Performing reads and writes from and to the socket
- Scheduling a callback to be invoked when a socket is available for writing or reading
- Scheduling a callback to be invoked after a certain period of time has elapsed
Using a Built-In I/O implementation
The library contains predefined implementations of this API for common event libraries,
such as libevent, libev, and libuv. If your application makes
use of any of these libraries for its own purposes, integrating with the library is
as simple as providing an instance of such an implementation to the
lcb_create_st structure. Here is an example using
libevent:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <libcouchbase/couchbase.h>
static void bootstrap_callback(lcb_t instance, lcb_error_t err)
{
lcb_store_cmd_t cmd = { 0 };
const lcb_store_cmd_t *cmdlist = &cmd;
if (err != LCB_SUCCESS) {
fprintf(stderr, "ERROR: %s\n", lcb_strerror(instance, err));
exit(EXIT_FAILURE);
}
// The client is now connected. We may start performing operations
cmd.v.v0.key = "foo";
cmd.v.v0.nkey = 3;
cmd.v.v0.bytes = "bar";
cmd.v.v0.nbytes = 3;
cmd.v.v0.operation = LCB_SET;
lcb_store(instance, NULL, 1, &cmdlist);
}
static void get_callback(lcb_t instance, const void *cookie, lcb_error_t error,
const lcb_get_resp_t *resp)
{
if (error != LCB_SUCCESS) {
fprintf(stderr, "Failed to get key: %s\n", lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
fprintf(stdout, "I stored and retrieved the key 'foo'. Terminate program\n");
event_base_loopbreak((void *)lcb_get_cookie(instance));
}
static void store_callback(lcb_t instance, const void *cookie, lcb_storage_t operation,
lcb_error_t error, const lcb_store_resp_t *resp)
{
lcb_get_cmd_t cmd = { 0 };
const lcb_get_cmd_t *cmdlist = &cmd;
if (error != LCB_SUCCESS) {
fprintf(stderr, "Failed to store key: %s\n", lcb_strerror(instance, error));
exit(EXIT_FAILURE);
}
cmd.v.v0.key = "foo";
cmd.v.v0.nkey = 3;
lcb_get(instance, NULL, 1, cmds);
}
static lcb_io_opt_t create_libevent_io_ops(struct event_base *evbase)
{
struct lcb_create_io_ops_st ciops;
lcb_io_opt_t ioops;
lcb_error_t error;
memset(&ciops, 0, sizeof(ciops));
ciops.v.v0.type = LCB_IO_OPS_LIBEVENT;
ciops.v.v0.cookie = evbase;
error = lcb_create_io_ops(&ioops, &ciops);
if (error != LCB_SUCCESS) {
fprintf(stderr, "Failed to create IOPS: %s\n", lcb_strerror(NULL, error));
exit(EXIT_FAILURE);
}
return ioops;
}
static lcb_t create_libcouchbase_handle(lcb_io_opt_t ioops)
{
lcb_t instance;
lcb_error_t error;
struct lcb_create_st copts = { 0 };
copts.version = 3;
copts.v.v3.connstr ;
copts.v.v3.io = ioops;
lcb_create(&instance, &copts);
lcb_connect(instance);
lcb_set_bootstrap_callback(instance, bootstrap_callback);
lcb_set_get_callback(instance, get_callback);
lcb_set_store_callback(instance, store_callback);
return instance;
}
int main(void)
{
struct event_base *evbase = event_base_new();
lcb_io_opt_t ioops = create_libevent_io_ops(evbase);
lcb_t instance = create_libcouchbase_handle(ioops);
// Store the evbase structure as the 'cookie' for this instance
lcb_set_cookie(instance, evbase);
// Run the event loop. The rest of the program will be executed from this
// function.
event_base_loop(evbase, 0);
// Cleanup
event_base_free(evbase);
lcb_destroy(instance);
exit(EXIT_SUCCESS);
}
Note: The previous example has some error checking left out for brevity. A fuller example
can be found inside the example/ibeventdirect directory
within the source tree.
In the above example, the
lcb_create_io_opts() function is used to instantiate the
built-in libevent implementation of the IOPS API. Note that the
lcb_create_io_ops_st structure contains a cookie
field which should contain a pointer to an implementation-defined object; in the case
of the libevent implmentation this should be a pointer to an existing
struct event_base (which is an instance of the libevent loop
itself). Each of the built-in implementations contains its own header file that
provides more detail about what the cookie field is supposed to contain. The header
files are named in the form of ${TYPE}_io_opts.h, where
${TYPE} is the name of the implementation. They are located
inside the libcouchbase include directory upon installation, and
are located in the plugins directory within the source tree.After the IOPS instance has been created, it is set in the io field of the lcb_create_st structure and the lcb_t object is created.
Creating a custom I/O implementation
Note: The I/O interface is a work in progress and subject to change
A custom I/O
implementation can be created by implementing the interface, featured in
<libcouchbase/iops.h>. The interface is a work in progress
and is considered to be volatile.Usage Differences in Non-Blocking mode
For the most part, programming with libcouchbase is the same regardless of whether you're
using it in a blocking or non-blocking application. There are some key differences to
note, however:
- lcb_wait() should not be called in non-blocking mode. By definition, the lcb_wait() routine will block the application until all pending I/O completes. In non-blocking mode the pending I/O is completed when control is returned back to the event loop.
- You must not schedule operations until the bootstrap callback,
lcb_set_bootstrap_callback(), has been invoked. This is
because operations must be forwarded to a destination node in the cluster
depending on the key specified within the operation. Until the client has been
bootstrapped it does not know how to forward keys to any nodes.Unlike blocking mode where you may simply do:
You need to use the callback variant that notifies your application when the library is ready.lcb_connect(instance); lcb_wait(instance); if (lcb_get_bootstrap_status(instance) == LCB_SUCCESS)) { // Start operations }
- You are responsible for ensuring that the iops structure passed to the library remains valid until lcb_destroy is invoked. Likewise, you are responsible for freeing the iops structure via lcb_destroy_io_opts() when it is no longer required.
- Currently the library does blocking DNS lookups via the standard getaddrinfo() call. Typically this should not take a long time but may potentially block your application if an invalid host name is detected or the DNS server in use is slow to respond.