libstddjb
libskarnet
skalibs
Software
skarnet.org
The following functions are declared in the skalibs/iopause.h header, and implemented in the libskarnet.a or libskarnet.so library.
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() n or 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.
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 tain_ts. 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.
int iopause (iopause_fd *x, unsigned int len, tain_t const *deadline, tain_t 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_t const *deadline, tain_t *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().
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.