Skip to content

Instantly share code, notes, and snippets.

@steveyen
Created March 18, 2015 00:35
Show Gist options
  • Save steveyen/5ec1a1807edc0ed02a1a to your computer and use it in GitHub Desktop.
Save steveyen/5ec1a1807edc0ed02a1a to your computer and use it in GitHub Desktop.
get couchbaselabs/lcbook/iops.md
get couchbaselabs/lcbook/iops.md
VALUE couchbaselabs/lcbook/iops.md 1 22501
{"type":"github/text","repo":"couchbaselabs/lcbook","project":"lcbook","key":"couchbaselabs/lcbook/iops.md","url":"https://github.com/couchbaselabs/lcbook/blob/master/iops.md","ext":".md","title":"iops.md","contents":"<!-- vim: set noexpandtab: --!>\n## I/O Integration\n\nlibcouchbase is an asynchronous I/O client and can integrate with numerous\nexisting event frameworks. The library gives several levels of control over\nits I/O integration. You can\n\n* Use a predefined backend with normal synchronous operation\n* Use a predefined backend with asynchronous operation\n* Implement an adaptor between your custom I/O system and libcouchbase\n\n### Using a compiled I/O backend\n\n> ###### Availability of Backends\n> Note that the availability of a backend will depend on whether the supporting\n> library is available and installed on the current platform. Not all backends\n> are available for all platforms.\n\nYou may use a predefined event loop backend either by forcing it via an\nenvironment variable or by specifically placing it inside the constructor\noptions.\n\nTo do this via an environment variable you can use the \n`LIBCOUCHBASE_EVENT_PLUGIN_NAME`, thus e.g.\n\n```\n$ LIBCOUCHBASE_EVENT_PLUGIN_NAME=select ./foo\n```\n\n**Note that the environment variable takes precedence over any setting\ndefined in code**\n\nYou can set a backend via the `lcb_create_st` structure as well:\n\n```C\nlcb_io_opt_t io;\nlcb_error_t err;\nstruct lcb_create_io_ops_st io_cropts;\nlcb_t instance;\nstruct lcb_create_st cropts;\n\nmemset(&io_cropts, 0, sizeof(io_cropts));\nio_cropts.version = 0;\nio_cropts.type = LCB_IO_OPS_LIBEV;\nerr = lcb_create_io_ops(&io, &io_cropts);\nif (err != LCB_SUCCESS) {\n\tdie(\"Couldn't create IO structure\", err);\n}\nmemset(&cropts, 0, sizeof(cropts));\ncropts.v.v0.io = io;\n/** Initialize more fields here */\nerr = lcb_create(&instance, &cropts);\n\nif (err != LCB_SUCCESS) {\n\tdie(\"Failed to initialize instance!\\n\");\n}\n```\n\nBe sure to check the error return value from `lcb_create_io_ops`. Specifically\nif a dependency or plugin is not found, an `LCB_DLOPEN_FAILED` or\n`LCB_DLSYM_FAILED` will be returned.\n\nYou can also set `LIBCOUCHBASE_DLOPEN_DEBUG` in the environment which will\nprint details on what the library is doing when searching for the plugin.\n\n#### Backend Selection/Search order\n\nSelecting the backend is done with this algorithm\n\n1. If a backend is specified in the `LIBCOUCHBASE_EVENT_PLUGIN_NAME` it is used\n and anything else ignored. If the plugin in the environment cannot be found\n the `LCB_BAD_ENVIRONMENT` error will be returned\n2. If a backend is specified explicitly in the `lcb_create_io_ops_st` structure\n then it is used\n3. Otherwise, the backend selected will be the platform default. The platform\n default is determined by the availability of dependent libraries at the time\n _libcouchbase was compiled_. For *nix-based systems this goes in order of\n preference as:\n * libevent\n * libev\n * libuv\n * select\n\n### Getting Backend Information\n\nYou may query libcouchbase to determine information about I/O backend\nselection. Specifically you may get information about:\n\n* The platform default backend\n* Any backend specified by the environment variable\n* The current backend object being used by the library\n\nThe following snippet of code displays information on the backend which\nthe library shall select:\n\n```\nstruct lcb_create_io_ops_st io_cropts = { 0 };\nstruct lcb_cntl_iops_info_st io_info = { 0 };\nio_info.v.v0.options = &io_cropts;\n\nlcb_error_t err;\n\nerr = lcb_cntl(NULL, LCB_CNTL_GET, LCB_CNTL_IOPS_DEFAULT_TYPES, &io_info);\nif (err != LCB_SUCCESS) {\n\tdie(\"Couldn't get I/O defaults\", err);\n}\nprintf(\"Default IO For platform is %d\\n\", io_info.v.v0.os_default);\nprintf(\"Effective IO (after options and environment) is %d\\n\",\n\t\tio_info.v.v0.effective);\n\n```\n\nThe `os_default` shows the platform default which is determined when\nlibcouchbase was compiled. The `effective` shows the effective backend\nafter examining any overrides in the environment variable and the creation\nstructure.\n\nNote that the creation structure is used only for informational purposes\nand is _not_ modified (and is declared in the info structure as `const`).\n\nTo get the actual IO pointer table, use the `LCB_CNTL_IOPS` on the instance\nitself.\n\n### Using libcouchbase In Async Environments\n\n#### Configuring Backends for Asynchronous Operation\n\nWhile most of the bundled backends in libcouchbase are _capable_ of\nasynchronous operation, by default they are configured to be used\nsynchronously. Some backends may require specific steps to make them suitable\nfor async usage.\n\nMost backends require you pass them a specific \"loop\" structure. You may do\nthis via the `lcb_create_io_ops_st` structure's `cookie` field.\n\nEach built-in backend contains its own header file which describes\nthe type of argument it accepts for its `cookie` parameter.\n\nAdditionally, do not forget to free the IO structure (i.e. the\n`lcb_io_opt_t`) - as it will **not** be freed when the library itself is\ndestroyed - this is because unless the IO structure was created by the library\nit may be possible for several `lcb_t` structures to share the same \n`lcb_io_opt_t` object.\n\n##### Configuring the _libevent_ backend for Async Operation\n\nThe _libevent_ backend works with both >= 1.4 and 2.x versions. To use\nthe backend in async mode, you will need to pass it an `event_base` structure.\n\n```C\n#include <event2/event.h> /** For libevent 2.x. 1.x has a different header */\nstruct lcb_create_io_opt_t io_cropts = { 0 };\nstruct event_base *evbase = event_base_new();\nio_cropts.v.v0.type = LCB_IO_OPS_LIBEVENT;\nio_cropts.v.v0.cookie = evbase;\n\n```\n\nThe plugin will _not_ free the `event_base` structure, so be sure to free it\nyourself once it's not needed (and after the plugin itself is destroyed).\n\n##### Configuring the _libev_ backend for Async Operation\n\nThe _libev_ backend works with both 3.x and 4.x versions of `libev`. The usage\npattern does not differ much from _libevent_.\n\nThe default backend behavior is _not_ to use the default `ev_loop` object\nbut to allocate a new one (in case there is one already in use). To use\nyour own `ev_loop` structure (or use the default one):\n\n```C\n#include <ev.h>\nstruct lcb_create_io_opt_t io_cropts = { 0 };\nev_loop *loop;\n\nloop = ev_default_loop(0);\nio_cropts.v.v0.type = LCB_IO_OPS_LIBEV;\nio_cropts.v.v0.cookie = loop;\n\n```\n\n##### Configuring the _libuv_ backend for Async Operation\n\n[libuv](https://github.com/joyent/libuv) is a cross platform event loop\nwhich uses a completion model interface. At the time of writing binary\ndistributions do not exist for most platforms and typically a users of\nlibuv will need to compile the library themselves.\n\nAs such, the binary distribution of libcouchbase does not contain a\n_libuv_ binary plugin. However the entire plugin code itself may be\n\"included\" into your own code, as the plugin is installed along with\nthe libcouchbase headers and you may use it like so:\n\n```C\n#include <libcouchbase/plugins/io/libuv/plugin-libuv.c>\n```\n\nCompile this file as `plugin-libuv.o` and ensure the `LCBUV_EMBEDDED_SOURCE`\nflag is set on your compiler, for example:\n\n```\n$ cc -c -o uvapp.o -DLCBUV_EMBEDDED_SOURCE\n$ cc -c -o plugin-libuv.o -DLCBUV_EMBEDDED_SOURCE\n$ cc -o uvapp uvapp.o plugin-libuv.o -lcouchbase -luv\n```\n\nThe `LCBUV_EMBEDDED_SOURCE` primarily determines whether symbols are to be\nimported (for Windows this means using the `__declspec(dllimport)`) from an\nexternal shared object, or whether the symbols are present in the current\nbinary. Setting the `LCBUV_EMBEDDED_SOURCE` macro will remove those\nimport and export decorators.\n\nTo actually use the plugin you should initialize a\n```C\nlcbuv_options_t uv_options;\nuv_options.version = 0;\nuv_options.v.v0.loop = uv_default_loop();\nuv_options.v.v0.startsop_noop = 1;\n```\n\nThe `startstop_noop` flag is used to determine whether the loop is self\ncontained within the library or not. Because of how _libuv_ handles\nresources, streams which are logically closed by libcouchbase may not\nhave all their resources destroyed until a callback is delivered. If\nthis flag is not set then `lcb_destroy()` will run the UV event loop\nuntil all pending items have received their close callback.\n\n#### Connecting and Executing Operations in Async Mode\n\nIn all the examples above we demonstrated operations via the _schedule-wait_\nsequence, where the completion of an operation could be determined once\n`lcb_wait()` returns. However `lcb_wait()` is a blocking operation and cannot\nbe used in asynchronous environments.\n\nAsync operation follows the same pattern as sync operations, but requires a\ndifferent approach. Most of these modifications do not affect how libcouchbase\nbehaves but rather how to effectively program with libcouchbase in async.\n\nThe first notable difference is the handling of the `cookie` parameter.\nWhile in sychronous mode it was safe to pass a stack-allocated cookie\nit is not safe to do so in `async` mode. Specifically with async mode the\ndata callback will never complete when the scheduler's stack is in scope\nand thus once the callback is invoked, trying to dereference the pointer\nto stack data will likely result in stack corruption.\n\nThus it is recommended you use only dynamically allocated memory (i.e.\n`malloc`) for your cookie data. As a consequence you must also ensure that\nyour cookie data correctly determines the number of callbacks it shall receive\nbefore it can be freed. In other words, if you are using your cookie for more\nthan a single operation it would be wise to keep it reference counted.\n\nThe following comprehensive example details several key aspects in handling\noperations within async mode. It may be more robust in error handling, but\ndoes demonstrate the proper sequence of events and resource management\nwhen running in async.\n\nThe example requires `libevent2`. It contains a function called\n`write_keys_to_file` which opens a file, fetches several keys from the cluster\nand then writes their values to the file. The function is asynchronous\nwhich means that the function will only _schedule_ the operations and not\nactually wait for execution. A callback is passed to this function to\nnotify the caller when all the keys have been retrieved and written to the\nfile.\n\nThe file also contains specific details on handling the library instance\nitself which we'll go into later.\n\n```C\n/** vim: set noexpandtab: */\n/**\n * Compile as:\n * cc -o cbfiles cbfiles.c -levent -lcouchbase\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/time.h>\n\n#include <libcouchbase/couchbase.h>\n#include <event2/event.h>\n\nstruct app_context_st;\nstruct app_context_st {\n\tlcb_t instance;\n\tlcb_io_opt_t io;\n\tint connected;\n\tstruct event_base *evbase;\n\tvoid (*on_connected)(struct app_context_st *, int);\n\tvoid (*on_cleaned)(struct app_context_st *);\n\tstruct event *async_timer;\n};\n\nstruct my_cookie_st {\n\tunsigned remaining;\n\tFILE *fp;\n\tchar *prefix;\n\tvoid (*callback)(struct app_context_st *, void *);\n\tvoid *arg;\n};\n\nstatic void\nconfiguration_callback(lcb_t instance, lcb_configuration_t config)\n{\n\tstruct app_context_st *ctx;\n\tctx = (void *)lcb_get_cookie(instance);\n\tif (ctx->connected) {\n\t\treturn;\n\t}\n\tctx->connected = 1;\n\tctx->on_connected(ctx, 1);\n}\n\nstatic void\nerror_callback(lcb_t instance, lcb_error_t err, const char *msg)\n{\n\tstruct app_context_st *ctx = (void *)lcb_get_cookie(instance);\n\tif (ctx->connected) {\n\t\treturn;\n\t}\n\tctx->on_connected(ctx, 0);\n}\n\nstatic void\nget_callback(\n\tlcb_t instance,\n\tconst void *cookie,\n\tlcb_error_t err,\n\tconst lcb_get_resp_t *resp)\n{\n\tstruct my_cookie_st *info = (void *)cookie;\n\tfprintf(info->fp, \"%s:%.*s \",\n\t\t\tinfo->prefix, (int)resp->v.v0.nkey, resp->v.v0.key);\n\n\tif (err != LCB_SUCCESS) {\n\t\tfprintf(info->fp, \"<NOT_FOUND>\");\n\t} else {\n\t\tfprintf(info->fp, \"%.*s\", (int)resp->v.v0.nbytes, resp->v.v0.bytes);\n\t}\n\n\tfprintf(info->fp, \"\\n\");\n\n\tif (! --info->remaining) {\n\t\tstruct app_context_st *ctx = (void *)lcb_get_cookie(instance);\n\t\tinfo->callback(ctx, info->arg);\n\t\tfclose(info->fp);\n\t\tfree(info->prefix);\n\t\tfree(info);\n\t}\n}\n\nvoid\napp_context_init(struct app_context_st *ctx,\n\t\t\t\tvoid (*callback)(struct app_context_st *, int))\n{\t\n\tstruct lcb_create_io_ops_st io_cropts = { 0 };\n\tstruct lcb_create_st cropts = { 0 };\n\tlcb_error_t err;\n\n\tctx->evbase = event_base_new();\n\tctx->on_connected = callback;\n\tctx->instance = NULL;\n\tctx->connected = 0;\n\n\tio_cropts.v.v0.type = LCB_IO_OPS_LIBEVENT;\n\tio_cropts.v.v0.cookie = ctx->evbase;\n\terr = lcb_create_io_ops(&ctx->io, &io_cropts);\n\tif (err != LCB_SUCCESS) {\n\t\tfprintf(stderr, \"couldn't create io structure\\n\");\n\t\texit(EXIT_FAILURE);\n\t}\n\n\tcropts.v.v0.io = ctx->io;\n\terr = lcb_create(&ctx->instance, &cropts);\n\tif (err != LCB_SUCCESS) {\n\t\tfprintf(stderr, \"couldn't create instance!\\n\");\n\t\texit(EXIT_FAILURE);\n\t}\n\n\tlcb_set_cookie(ctx->instance, ctx);\n\tlcb_set_get_callback(ctx->instance, get_callback);\n\tlcb_set_error_callback(ctx->instance, error_callback);\n\tlcb_set_configuration_callback(ctx->instance, configuration_callback);\n\n\terr = lcb_connect(ctx->instance);\n\tif (err != LCB_SUCCESS) {\n\t\tfprintf(stderr, \"Couldn't schedule connection!\\n\");\n\t\texit(EXIT_FAILURE);\n\t}\n}\n\n\nstatic void\ninner_clean(int sockdummy, short events, void *arg)\n{\n\tstruct app_context_st *ctx = arg;\n\tevtimer_del(ctx->async_timer);\n\tevent_free(ctx->async_timer);\n\tlcb_destroy(ctx->instance);\n\tlcb_destroy_io_ops(ctx->io);\n\tctx->on_cleaned(ctx);\n}\n\nvoid\napp_context_clean(\n\tstruct app_context_st *ctx,\n\tvoid (*callback)(struct app_context_st *))\n{\n\tstruct timeval tv = { 0, 0 };\n\tctx->async_timer = evtimer_new(ctx->evbase, inner_clean, ctx);\n\tctx->on_cleaned = callback;\n\tevtimer_add(ctx->async_timer, &tv);\n}\n\nvoid\nwrite_keys_to_file(\n\tstruct app_context_st *ctx,\n\tconst char *path,\n\tconst char *prefix,\n\tconst char * const * keys,\n\tvoid (*callback)(struct app_context_st *, void*),\n\tvoid *arg)\n{\n\tstruct my_cookie_st *info;\n\tconst char * const *curkey;\n\tlcb_t instance = ctx->instance;\n\n\tinfo = calloc(1, sizeof(*info));\n\tinfo->prefix = malloc(strlen(prefix) + 1);\n\tstrcpy(info->prefix, prefix);\n\tinfo->fp = fopen(path, \"a\");\n\tinfo->callback = callback;\n\tinfo->arg = arg;\n\n\tfor (curkey = keys; *curkey; curkey++) {\n\t\tlcb_error_t err;\n\t\tlcb_get_cmd_t cmd = { 0 }, *cmdp = &cmd;\n\n\t\tcmd.v.v0.key = *curkey;\n\t\tcmd.v.v0.nkey = strlen(*curkey);\n\t\terr = lcb_get(instance, info, 1, (const lcb_get_cmd_t * const *)&cmdp);\n\t\tif (err != LCB_SUCCESS) {\n\t\t\tfprintf(info->fp, \"%s:%s <ERROR>\\n\", prefix, *curkey);\n\t\t} else{\n\t\t\t++info->remaining;\n\t\t}\n\t}\n}\n\nstatic void\ndo_exit(struct app_context_st *ctx)\n{\n\tevent_base_loopexit(ctx->evbase, NULL);\n}\n\nstatic int keysets_remaining;\n\nstatic void\ndone_callback(struct app_context_st *ctx, void *arg)\n{\n\tchar *keyset = arg;\n\tprintf(\"All keys for keyset %s done!\\n\", keyset);\n\tif (!--keysets_remaining) {\n\t\tapp_context_clean(ctx, do_exit);\n\t}\n}\n\nstatic void\ninstance_connected(struct app_context_st *ctx, int ok)\n{\n\tconst char *keyset1[] = { \"foo1\", \"bar1\", \"baz1\" , NULL };\n\tconst char *keyset2[] = { \"foo2\", \"bar2\", \"baz2\", NULL };\n\tif (!ok) {\n\t\tfprintf(stderr, \"Connection Failed!\\n\");\n\t\texit(EXIT_FAILURE);\n\t}\n\n\twrite_keys_to_file(ctx, \"/tmp/keys_1\", \"first\", keyset1, done_callback, \"k1\");\n\twrite_keys_to_file(ctx, \"/tmp/keys_2\", \"second\", keyset2, done_callback, \"k2\");\n\tkeysets_remaining = 2;\n}\n\n\n\nint\nmain(void)\n{\n\tstruct app_context_st *ctx = calloc(1, sizeof(*ctx));\n\tapp_context_init(ctx, instance_connected);\n\tevent_base_loop(ctx->evbase, 0);\n\tevent_base_free(ctx->evbase);\n\tfree(ctx);\n\texit(EXIT_SUCCESS);\n}\n```\n\n#### Async Connections\n\nConnecting an instance in async mode follows the same normal creation\nparameters described above. However instead of calling `lcb_wait` you should\ninstead return control to the event loop (this is usually implicit in exiting\nyour function as you function is likely a callback (or a callee thereof) \nitself).\n\nBefore you begin scheduling operations you must ensure the instance has already\nbeen connected and configured. Otherwise you will receive \n`LCB_NO_MATCHIN_SERVER` and/or `LCB_CLIENT_ETMPFAIL` error codes as the library\ndoes not know where to send commands to.\n\nInitial configuration notifications are done via the the _configuration\ncallback_ which is set via `lcb_set_configuration_callback`. The configuration\ncallback is invoked whenever the client receives a new configuration and is\npassed the instance and a status depending on whether the configuration was\nthe first one we received (`LCB_CONFIGURATION_NEW`), or it was a modified\nversion (`LCB_CONFIGURATION_CHANGED`) or was identical to the one the library\nwas already using (`LCB_CONFIGURATION_UNCHANGED`). In any event receiving such\na callback signals to the user that the instance contains a configuration\nand may start being used for data operations.\n\nIf an error occurs during configuration it will be relayed via the\n`error_callback`.\n\n_Note that the `error\\_callback` and `configuration\\_callback`\nhandlers are not specific to the initial connection phase and may be invoked\nany time the library receives a new configuration or experiences errors\nin the configuration; so be sure to set an internal flag checking whether this\nis the first time either of the callbacks were invoked if you are relying on \nthis for initial connection notifications_.\n\n### Writing your own I/O Backend\n\nYou may also author your own IO backend or I/O plugin. You can choose between\nthe simpler event-based interface or the more complex buffer-based interface.\n\nWhich interface you employ depends on the environments you are working with.\nTypically most libraries will employ the event-based interface which follows\nthe normal `select()` or `poll()` model where a socket is polled for read\nor write ability and is then dispatched to a handler which performs the actual\nI/O operations.\n\nSome event systems (notably _UV_, Windows' IOCP and others) use a buffer-based\ncompletion model wherein sockets are submitted operations to perform on a\nbuffer (particularly read in the buffer and write from it).\n\nThis section assumes you have some understanding of asynchronous I/O.\n\nThe IOPS interface is defined in `<libcouchbase/types.h>`\n\n#### Event Model\n\nIn the event model you are presented with opaque 'event' structures which\ncontain information about:\n\n1. What events to watch for\n2. What to do when said events take place\n\nA sample event structure may look like this:\n\n```C\nstruct my_event {\n\tint sockfd; /** Socket to poll on */\n\tshort events; /** Mask of events to watch */\n\t/** callback to invoke when ready */\n\tvoid (*callback)(int sockfd, short events, void *arg);\n\t/** Argument to pass to callback */\n\tvoid *cbarg;\r}\n```\n\nIn concept this is similar to the `pollfd` structure (see `poll(2)`).\n\nThe lifecycle for events takes place as follows:\n\n1. An event structure's memory is first allocated. This is done by a call\n to `create_event()`.\n\n2. The structure is paired with a socket, event, callback and data. When\n the event is ready, the callback is invoked with those parameters. This\n is done via the `update_event()` call. Note the `void *` pointer in the\n callback signature. This is how the library is able to associate a given\n socket/event with an internal structure of its own.\n\n3. The polling for events is stopped for the specified socket via the\n `delete_event()` call. This undoes anything performed by the `update_event`\n call\n\n4. When the event structure is no longer needed, it is destroyed (and its\n memory resources freed) via `destroy_event`.\n\n\nNote that the most common calls will be to `update_event` and `delete_event`\nand thus plugins should optimize for these.\n\nThe events may be a mask of `LCB_READ_EVENT` and `LCB_WRITE_EVENT`; these\nhave the same semantics as `POLLIN` and `POLLOUT` respectively. If an error\ntakes place on a socket, the `LCB_ERROR_EVENT` flag may be _output_ as well.\n\nWhen an event is ready, the callback is to be invoked specifying the mask\nof the events which _actually took place_, for example if `update_event()`\nwas called with `LCB_READ_EVENT|LCB_WRITE_EVENT` but the socket was only\navailable for writing, then the callback is only invoked with\n`LCB_WRITE_EVENT`.\n\nNote that the library will call `update_event()` whenever it wishes to\nreceive an event. The registration should be considered cleared (i.e\nas if `delete_event` is called) right before the callback is to be\ninvoked, thus e.g.\n\n```C\nvoid send_events(my_event *event, short events)\n{\n\tdelete_event(my_event);\n\tmy_event->allback(my_event->sockfd, events, my_event->arg);\n}\n```\n\n##### I/O Operations\n\nThe IOPS structure for the 'event' model contains function pointers for\nbasic I/O operations like `send`, `recv`, etc.\n\nThe semantics of these operations are identical to those functions of the\ncorresponding name within the Berkeley Socket API. Some of the naming might\nbe a bit different, for example `sendv` does not exist in the BSD socket APIs\nand is typically implemented as `sendmsg` (Portable) or `writev` (POSIX only).\n\nThese operations are also passed the Plugin Instance itself as their first\nargument. If your plugin just proxies to OS calls then you may ignore it in\nthose functions.\n\n##### Timer Operations\n\nTimer operations determine that a callback will be invoked after a specific\namount of time has elapsed. The interval itself is measured in microseconds.\n\nThe signature of the timer callback is the same as the event callback, except\nthat the socket and event arguments are ignored. Timers are considered\nnon-repeating and thus once a timer callback has been delivered it should not\nbe delivered again until `update_timer` has been called.\n"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment