libstddjb
libskarnet
skalibs
Software
skarnet.org

The iopause library interface

The following functions are declared in the skalibs/iopause.h header, and implemented in the libskarnet.a or libskarnet.so library.

General information

iopause is the skalibs API for event loop selection. It's a wrapper around the system's ppoll() or poll() (if available) or select() (if neither ppoll() nor poll() is available) function. It works around some system-dependent quirks; also it works with absolute dates instead of timeouts. This is a good thing: see below.

iopause is a derived work from Dan J. Bernstein's iopause library, but the skalibs implementation is subtly different.

Data structures

An iopause_fd structure is similar to a struct pollfd structure, and must be filled the same way. Usually, the user declares an array of iopause_fd and fills it, one element per descriptor to select on. If x is an iopause_fd:

Unlike poll() or select(), which use a timeout argument, the iopause() function uses a deadline argument, i.e. an absolute time at which it must return 0 if no event has happened so far, as well as a stamp argument, i.e. an absolute time meaning now. Those arguments are stored in tains. Here is why:

The event loop pattern is mostly used to multiplex several asynchronous events that can happen independently, at the same time or not. Let's say you have 3 events, x, y and z. Each of those has a separate timeout: if x happens before x-timeout milliseconds, you call the x-event-handler function, but if x-timeout milliseconds elapse without x happening, you call x-timeout-handler function. And similarly with y and z.

But the selection function returning does not mean x has happened or that x has timed out. It might also mean that y has happened, that y has timed out, that z has happened, that z has timed out, or something else entirely. In the post-selection part of the loop, the proper handler is called for the event or timeout that has happened; then the loop is executed again, and in the pre-selection part of the loop, the array describing the events is filled, and the selection timeout is computed.

How are you going to compute that global selection timeout? Easy: it's the shortest of the three. But we just spent some amount of time waiting, so the individual timeouts must be recomputed! This means:

That is really cumbersome. A much simpler way of doing things is:

Maintaining a global timestamp and using absolute times instead of relative times really is the right way to work with event loops, and the iopause interface reflects that. Of course, you need a reliable, bug-free time library and a monotonic, constant system clock to handle absolute times correctly; that is why iopause relies on the tai library.

Functions

int iopause (iopause_fd *x, unsigned int len, tain const *deadline, tain const *stamp)
Blocks until one of the events described in the x array, of length len, happens, or until the absolute date *deadline is reached. deadline may be null, in which case the function blocks indefinitely until an event happens. If deadline is not null, then stamp must not be null, and must contain an accurate estimation of the current time. The function returns the number of events that have happened, 0 for a timeout, or -1 (and sets errno) for an error.

int iopause_stamp (iopause_fd *x, unsigned int len, tain const *deadline, tain *stamp)
Like iopause(), but if stamp is not null, it is updated right before the function returns. This helps the user always keep a reasonably accurate estimation of the current time in stamp; it is recommended to use this function instead of the lower-level iopause().

Underlying implementations

iopause is an alias to one of iopause_ppoll, iopause_poll or iopause_select. It is always aliased to iopause_ppoll if the ppoll() function is available on the system; else, it's aliased to iopause_poll by default, and users can alias it to iopause_select instead if they configure skalibs with the --enable-iopause-select option.

poll() has a more comfortable API than select(), but its maximum precision is 1 millisecond, which might not be enough for some applications; using select() instead incurs some CPU overhead for the API conversion, but has a 1 microsecond precision. ppoll() gets the best of both worlds with the same interface model as poll() and a 1 nanosecond precision, which is why skalibs always uses it when available.