$darkmode
Elektra 0.11.0
|
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. | |
Asynchronous I/O feature.
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.
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:
Each operation has a user callback that is called under the following conditions:
Each operation has different properties. The following properties are shared by all operations:
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:
Timer operations have the following additional properties:
Idle operations have no additional properties.
Every I/O binding needs to provide ten functions:
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.
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:
Then you can store your data with the I/O binding.
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.
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:
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.
In ElektraIoBindingUpdateFd or ElektraIoBindingRemoveFd you can access your binding operation data by using elektraIoFdGetBindingData().
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.
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.
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.
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
.
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).
The functions supplied to elektraIoTestSuite() are called for setup, starting and stopping of the tests.
For example ElektraIoTestSuiteCreateBinding of the "doc" binding:
Of course starting and stopping is specific to your I/O management library.