libuev
|
“Event driven software improves concurrency” – Dave Zarzycki, Apple
The C API to libuEv, listed in uev/uev.h
, handles three different types of events: I/O (pipes, sockets, message queues, etc.), timers, and signals. The Summary details a slight caveat on signals.
Notice the lack of support for regular files and directories. This is a limitation of the underlying Linux epoll
interface which will return EPERM
for regular files. Except for the special case when stdin
is redirected from the command line. See examples/redirect.c
for more on this particular case.
Timers can be either relative, timeout in milliseconds, or absolute with a time given in time_t
, see mktime()
et al. Absolute timers are called cron timers and their callbacks get an UEV_HUP
error event if the wall clock changes, either via NTP or user input.
NOTE: On some systems, embedded in particular, time_t
is a 32-bit integer that wraps around in the year 2038. A GLIBC workaround (-D_TIME_BITS=64
) protects those systems, but users of other C libraries have no known workarounds. It is strongly recommended to use relative timers as often as possible.
To monitor events the developer first creates an event context, this is achieved by calling uev_init()
with a pointer to a (thread) local uev_ctx_t
variable.
For each event to monitor, be it a signal, cron/timer or a file/network descriptor, a watcher must be registered with the event context. The watcher, an uev_t
, is registered by calling the event type's _init()
function with the uev_ctx_t
context, the callback, and an optional argument.
Here is a signal example:
Notice that the callback must be prepared to handle UEV_ERROR
. I/O watchers in particular, but also timer watchers, must be restarted if required by the application. libuEv automatically tries to restart a signal watcher, but should that fail the callback will return error as well.
I/O watchers should also check for UEV_HUP
, preferably when handling any short read()
or write()
system calls. A short read on a socket may be due to the remote end having performed a shutdown()
. This is signaled to the callback using UEV_HUP
in the events
mask.
When all watchers are registered, call the event loop with uev_run()
and the argument to the event context. The flags
parameter can be used to integrate libuEv into another event loop.
In this example we set flags
to none:
With flags
set to UEV_ONCE
the event loop returns as soon as it has served the first event. If flags
is set to UEV_ONCE | UEV_NONBLOCK
the event loop returns immediately if no event is available.
If the call to uev_run()
fails you should notify the user somehow. libuEv fails if there is an invalid pointer, if uev_init()
was not called, or recurring epoll()
errors thare are impossible to recover from should occur. This is true for individual watchers as well, in particular signal and timer watchers which can fail in miserable ways.
Note: libuEv handles many types of errors, stream close, or peer shutdowns internally, but also lets the callback run. This is useful for stateful connections to be able to detect EOF.
uev_init()
uev_io_init()
, uev_signal_init()
or uev_timer_init()
Make sure callbacks checks their events
mask and handles:
UEV_ERROR
, e.g. I/O watchers must be restartedUEV_HUP
, reading any remaining data on the descriptorIn both of these cases the watcher is stopped by libuEv. On HUP the descriptor/connection must be reopened and the watcher reinitialized with uev_io_set()
, if required by the application.
uev_run()
uev_exit()
, possibly from a callbackNote 1: Make sure to use non-blocking stream I/O! Most hard to find bugs in event driven applications are due to sockets and files being opened in blocking mode. Be careful out there!
Note 2: When closing a descriptor or socket, make sure to first stop your watcher, if possible. This will help prevent any nasty side effects on your program.
Note 3: a certain amount of care is needed when dealing with APIs that employ signalfd. If your application use system()
you replace that with fork()
, and then in the child, unblock all signals blocked by your parent process, before you run exec()
. This because Linux does not unblock signals for your children, and neither does most (all?) C-libraries. See the finit project's implementation of run()
for an example of this. For more details on this issue, see this article at lwn.net.
libuEv is by default installed as a library with a few header files, you should only ever need to include one:
The output from the pkg-config
tool holds no surprises:
The prefix path /usr/local/
shown here is only the default. Use the configure
script to select a different prefix when installing libuEv.
For GNU autotools based projects, use the following in configure.ac
:
and in your Makefile.am
:
Here follows a very brief example to illustrate how one can use libuEv to act upon joystick input.
To build the example, follow installation instructions below, then save the code as joystick.c
and call GCC
Alternatively, call the Makefile
with make joystick
from the unpacked libuEv distribution.
More complete and relevant example uses of libuEv is the FTP/TFTP server uftpd, and the Linux /sbin/init
replacement finit. Both successfully employ libuEv.
Also see the bench.c
program (make bench
from within the library) for reference benchmarks against libevent and libev.