| Elektra 0.11.0

Files
I/O Bindings

Asynchronous I/O feature. More...

## Files
file | kdbio.h
| Elektra-I/O structures for I/O bindings, plugins and applications.
file | kdbioplugin.h
| Elektra-I/O functions and declarations for the I/O binding test suite.
file | kdbiotest.h
| Elektra-I/O functions and declarations for the I/O binding test suite.

## Detailed Description

Asynchronous I/O feature.

# Asynchronous I/O with Elektra

## Overview

I/O bindings allow Elektra and its plugins to integrate into different main loop APIs using a thin abstraction layer. For example, this is used for notification transport plugins which receive notifications using ZeroMQ, D-Bus, etc.

I/O bindings are created using an initialization function for a specific main loop API. Please see bindings for available I/O bindings and their according READMEs for more details. After creating, an I/O binding is associated to a KDB instance using elektraIoSetBinding(). Having different I/O bindings (e.g. same or different main loop APIs) for different KDB instances is supported.

The remainder of this page contains useful details for creating I/O bindings and using the operations provided by these bindings. Application developers are normally not required to do those tasks. For more information about using I/O bindings from an application developer perspective please read the Notification Tutorial.

## Introduction

An I/O binding needs to handle different types of operations. These operations are used by plugins that require asynchronous I/O. In this document we will call developers of these plugins "users". The three types of operations are:

- file descriptor watch operations
- timer operations
- idle operations

Each operation has a user callback that is called under the following conditions:

- For file descriptor watch operations a user callback shall be called whenever the descriptor status matches the "flags" bitmask (readable, writable or both).
- For timer operations a user callback shall be called whenever a given interval has elapsed.
- Idle operations shall call a user callback whenever possible without interfering other operations. These operations are used for bulk data transfers or expensive calculations that would stall other operations when done at once.

Each operation has different properties. The following properties are shared by all operations:

- callback (elektraIoFdGetCallback();set by elektraIoNewFdOperation()) user callback
- enabled (elektraIoFdIsEnabled();elektraIoFdSetEnabled()) indicates whether the operation is enabled. No callback shall be fired when an operation is disabled.
- privateData (elektraIoFdGetData();set by elektraIoNewFdOperation()) pointer to data owned by user
- bindingData (elektraIoFdGetBindingData();elektraIoFdSetBindingData()) pointer to data owned by I/O binding

For brevity only file descriptor operation variants are listed here. Variants for timer and idle operations are called `elektraIoTimer*` and `elektraIoIdle*`. All `elektraIo*` utility functions are provided by the `elektra-io` library.

File descriptor watch operations have the following additional properties:

- fd (elektraIoFdGetFd();set by elektraIoNewFdOperation()) file descriptor number to be watched
- flags (elektraIoFdGetFlags();elektraIoFdSetFlags()) flags bitmask

Timer operations have the following additional properties:

- interval (elektraIoTimerGetInterval();elektraIoTimerSetInterval()) minimum interval in milliseconds after the user callback shall be fired

Idle operations have no additional properties.

Creating a new I/O Binding

Every I/O binding needs to provide ten functions:

- file descriptor watch operations
  - ElektraIoBindingAddFd (e.g. ioDocBindingAddFd())
  - ElektraIoBindingUpdateFd (e.g. ioDocBindingUpdateFd())
  - ElektraIoBindingRemoveFd (e.g. ioDocBindingRemoveFd())
- timer operations
  - ElektraIoBindingAddTimer (e.g. ioDocBindingAddTimer())
  - ElektraIoBindingUpdateTimer (e.g. ioDocBindingUpdateTimer())
  - ElektraIoBindingRemoveTimer (e.g. ioDocBindingRemoveTimer())
- idle operations
  - ElektraIoBindingAddIdle (e.g. ioDocBindingAddIdle())
  - ElektraIoBindingUpdateIdle (e.g ioDocBindingUpdateIdle())
  - ElektraIoBindingRemoveIdle (e.g. ioDocBindingRemoveIdle())
- ElektraIoBindingCleanup (e.g. ioDocBindingCleanup())

In order to create a new I/O binding you have to create an entry point for your binding (e.g. elektraIoDocNew()). This entry point then calls elektraIoNewBinding() with pointers to the ten required functions.

// Initialize I/O interface
ElektraIoInterface * binding = elektraIoNewBinding (
// file descriptors
ioDocBindingAddFd, ioDocBindingUpdateFd, ioDocBindingRemoveFd,
// timers
ioDocBindingAddTimer, ioDocBindingUpdateTimer, ioDocBindingRemoveTimer,
// idle
ioDocBindingAddIdle, ioDocBindingUpdateIdle, ioDocBindingRemoveIdle,
// cleanup
ioDocBindingCleanup);
if (binding == NULL)
{
ELEKTRA_LOG_WARNING ("elektraIoNewBinding failed");
return NULL;
}
elektraIoNewBinding
ElektraIoInterface * elektraIoNewBinding(ElektraIoBindingAddFd *addFd, ElektraIoBindingUpdateFd *updateFd, ElektraIoBindingRemoveFd *removeFd, ElektraIoBindingAddTimer *addTimer, ElektraIoBindingUpdateTimer *updateTimer, ElektraIoBindingRemoveTimer *removeTimer, ElektraIoBindingAddIdle *addIdle, ElektraIoBindingUpdateIdle *updateIdle, ElektraIoBindingRemoveIdle *removeIdle, ElektraIoBindingCleanup *cleanup)
Create a new I/O binding.
Definition io.c:43
ioDocBindingUpdateFd
int ioDocBindingUpdateFd(ElektraIoFdOperation *fdOp)
Update information about a file descriptor watched by I/O binding.
Definition io_doc.c:368
ioDocBindingUpdateTimer
int ioDocBindingUpdateTimer(ElektraIoTimerOperation *timerOp)
Update timer in I/O binding.
Definition io_doc.c:432
ioDocBindingAddTimer
int ioDocBindingAddTimer(ElektraIoInterface *binding, ElektraIoTimerOperation *timerOp)
Add timer for I/O binding.
Definition io_doc.c:448
ioDocBindingCleanup
int ioDocBindingCleanup(ElektraIoInterface *binding)
Cleanup.
Definition io_doc.c:551
ioDocBindingUpdateIdle
int ioDocBindingUpdateIdle(ElektraIoIdleOperation *idleOp)
Update idle operation in I/O binding.
Definition io_doc.c:492
ioDocBindingRemoveFd
int ioDocBindingRemoveFd(ElektraIoFdOperation *fdOp)
Remove file descriptor from I/O binding.
Definition io_doc.c:414
ioDocBindingRemoveIdle
int ioDocBindingRemoveIdle(ElektraIoIdleOperation *idleOp)
Remove idle operation from I/O binding.
Definition io_doc.c:534
ioDocBindingAddIdle
int ioDocBindingAddIdle(ElektraIoInterface *binding, ElektraIoIdleOperation *idleOp)
Add idle operation to I/O binding.
Definition io_doc.c:507
ioDocBindingAddFd
int ioDocBindingAddFd(ElektraIoInterface *binding, ElektraIoFdOperation *fdOp)
Add file descriptor to I/O binding.
Definition io_doc.c:384
ioDocBindingRemoveTimer
int ioDocBindingRemoveTimer(ElektraIoTimerOperation *timerOp)
Remove timer from I/O binding.
Definition io_doc.c:476
ElektraIoInterface
struct _ElektraIoInterface ElektraIoInterface
I/O binding handle.
Definition kdbio.h:22

If your I/O management library requires you to store additional data you can do so using elektraIoBindingSetData(). Let's assume you have the following data structure:

typedef struct DocBindingData
{
char * foo;
// Add additional members as required
} DocBindingData;
DocBindingData
[kdbio operation data]
Definition io_doc.c:269
DocBindingData::foo
char * foo
Example member.
Definition io_doc.c:271

Then you can store your data with the I/O binding.

// Store binding relevant data in the interface
DocBindingData * bindingData = elektraMalloc (sizeof (*bindingData));
if (bindingData == NULL)
{
ELEKTRA_LOG_WARNING ("elektraMalloc failed");
return NULL;
}
elektraIoBindingSetData (binding, bindingData);
bindingData->foo = foo;
elektraMalloc
void * elektraMalloc(size_t size)
Allocate memory for Elektra.
Definition internal.c:274
elektraIoBindingSetData
int elektraIoBindingSetData(ElektraIoInterface *binding, void *data)
Set private data from I/O Binding.
Definition io.c:296

Of course if you need to store only a single pointer (e.g. a handle) you can omit the struct and directly use elektraIoBindingSetData() with your pointer.

Implementing Operations

The next step is to implement operation functions. We'll walk through the implementation of the functions for managing file descriptor watch operations. Timer and idle variants are the same except for the operation properties.

For reconstructing the user callback it is advisable to store a context for each operation in your I/O management library. Most I/O management libraries let you pass this context when adding an operation to the library. This context is then passed by the library back to your callbacks. You can use the operation data itself as context and store additional data like handles from your I/O management library by using elektraIoFdSetBindingData().

Let's assume the data structure looks like this:

typedef struct DocOperationData
{
char * bar;
// Add additional members as required
} DocOperationData;
DocOperationData
[kdbio operation data]
Definition io_doc.c:257
DocOperationData::bar
char * bar
Example member.
Definition io_doc.c:259

Using this struct's members you can store additional data like handles in operations. The member `bar` is just an example.

The following snippet from ioDocBindingAddFd() shows example code for ElektraIoBindingAddFd. Code for ElektraIoBindingAddTimer and ElektraIoBindingAddIdle is similar.

DocOperationData * operationData = newOperationData ();
if (operationData == NULL)
{
return 0;
}
// You can use private data stored in the I/O binding
// e.g. MyData data = (MyData *)elektraIoBindingGetData (binding);
elektraIoFdSetBindingData (fdOp, operationData);
// You can store additional data for each operation in your operationData structure
operationData->bar = "foo";
// Here you need to add the operation to the I/O management library
// ioDocBindingFdCallback() holds an example callback to pass to your I/O management library
// assume SomeIoLibHandle * someIoLibAddFd (int fd, int flags, int enabled, void * privateData, callback)
// operationData->handle = someIoLibAddFd (elektraIoFdGetFd (fdOp), elektraIoFdGetFlags (fdOp), elektraIoFdIsEnabled (fdOp), &fdOp,
// ioDocBindingFdCallback)
return 1;
elektraIoFdSetBindingData
int elektraIoFdSetBindingData(ElektraIoFdOperation *fdOp, void *data)
Set private binding data for operation.
Definition io.c:487
newOperationData
DocOperationData * newOperationData(void)
[kdbio binding data]
Definition io_doc.c:280

In ElektraIoBindingUpdateFd or ElektraIoBindingRemoveFd you can access your binding operation data by using elektraIoFdGetBindingData().

DocOperationData * operationData = (DocOperationData *) elektraIoFdGetBindingData (fdOp);
elektraIoFdGetBindingData
void * elektraIoFdGetBindingData(ElektraIoFdOperation *fdOp)
Get private binding data from operation.
Definition io.c:499

When your I/O management library detects a change of the file descriptor status it will call a callback supplied by your I/O binding. We will assume for file descriptor watch operations this is ioDocBindingFdCallback(). Your I/O binding's task is to call the operation callback supplied by the user with the correct arguments.

/*static*/ void ioDocBindingFdCallback (SomeIoLibHandle * handle, int bitmask)
{
// For this example let's assume handle is passed as argument
ELEKTRA_NOT_NULL (handle->data);
ElektraIoFdOperation * fdOp = (ElektraIoFdOperation *) handle->data;
// Convert bitmask to Elekta's flags
elektraIoFdGetCallback (fdOp) (fdOp, someBitMaskToElektraIoFlags (bitmask));
}
elektraIoFdGetCallback
ElektraIoFdCallback elektraIoFdGetCallback(ElektraIoFdOperation *fdOp)
Get callback of file descriptor watch operation.
Definition io.c:543
ioDocBindingFdCallback
void ioDocBindingFdCallback(SomeIoLibHandle *handle, int bitmask)
Calls the associated operation callback.
Definition io_doc.c:320
someBitMaskToElektraIoFlags
int someBitMaskToElektraIoFlags(int bitmask)
Convert your I/O library bit mask to Elektra's I/O flags.
Definition io_doc.c:298
ElektraIoFdOperation
struct _ElektraIoFdOperation ElektraIoFdOperation
file descriptor watch operation handle
Definition kdbio.h:25
SomeIoLibHandle
Example I/O management library data structure.
Definition io_doc.c:231
SomeIoLibHandle::data
void * data
Let's you access the context you supplied to the I/O management library.
Definition io_doc.c:233

We assumed `SomeIoLibHandle->data` let's you access your context. Since we have used the original operation data as context we directly obtain the operation data to retrieve the user callback using elektraIoFdGetCallback(). Additionally it is necessary to convert the I/O management library's bitmask to Elekta's I/O bitmask (ElektraIoFdFlags) and then call the user callback.

When implementing ElektraIoBindingRemoveFd (or the timer and idle equivalents) make sure to free data allocated in the add functions.

Cleanup

ElektraIoBindingCleanup is the place to free data allocated for your I/O binding.

At least you need to free the pointer returned from elektraIoNewBinding() in your I/O binding's entry point.

Linking

Make sure to link against the `elektra-io` library for the `elektraIo*` utility functions that create bindings or operations and allow access to their fields. This library is available via `pkg-config`.

Testing

Elektra provides a test suite for I/O bindings in order to make sure that transport plugins will work with all bindings. To run the test suite you need to execute elektraIoTestSuite() and provide the necessary callbacks for creating a new binding, starting and stopping asynchronous processing (ElektraIoTestSuiteCreateBinding, ElektraIoTestSuiteStart and ElektraIoTestSuiteStop).

int main (int argc, char ** argv)
{
init (argc, argv);
elektraIoTestSuite (createBinding, startLoop, stopLoop);
print_result ("iowrapper_doc");
return nbError;
}
elektraIoTestSuite
void elektraIoTestSuite(ElektraIoTestSuiteCreateBinding createBinding, ElektraIoTestSuiteStart start, ElektraIoTestSuiteStop stop)
Test-Suite for I/O Bindings.

The functions supplied to elektraIoTestSuite() are called for setup, starting and stopping of the tests.

For example ElektraIoTestSuiteCreateBinding of the "doc" binding:

static ElektraIoInterface * createBinding (void)
{
return elektraIoDocNew ("foo");
}
elektraIoDocNew
ElektraIoInterface * elektraIoDocNew(char *foo)
Create and initialize a new doc I/O binding.
Definition io_doc.c:564

Of course starting and stopping is specific to your I/O management library.