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:
    lcb_connect(instance);
    lcb_wait(instance);
    if (lcb_get_bootstrap_status(instance) == LCB_SUCCESS)) {
      // Start operations
    }
    You need to use the callback variant that notifies your application when the library is ready.
  • 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.