Inspired by the last leap second, I've been exploring timing (specifically, interval timers) using POSIX calls.
POSIX provides several ways to set up timers, but they're all problematic:
sleep
and nanosleep
—these are annoying to restart after they're interrupted by a signal, and they introduce clock skew. You can avoid some, but not all, of this skew with some extra work, but these functions use the realtime clock, so this isn't without pitfalls.setitimer
or the more modern timer_settime
—these are designed to be interval timers, but they're per-process, which is a problem if you need multiple active timers. They also can't be used synchronously, but that's less of a big deal.clock_gettime
and clock_nanosleep
seem like the right answer when used with CLOCK_MONOTONIC
. clock_nanosleep
supports absolute timeouts, so you can just sleep, increment the timeout, and repeat. It's easy to restart after an interruption that way, too. Unfortunately, these functions might as well be Linux-specific: there's no support for them on Mac OS X or FreeBSD.pthread_cond_timedwait
is available on the Mac and can work with gettimeofday
as a kludgy workaround, but on the Mac it can only work with the realtime clock, so it's subject to misbehavior when the system clock is set or a leap second happens.Is there an API I'm missing? Is there a reasonably portable way to create well-behaved interval timers on UNIX-like systems, or does this sum up the state of things today?
By well-behaved and reasonably portable, I mean:
A note on leap seconds (in response to R..'s answer):
POSIX days are exactly 86,400 seconds long, but real-world days can rarely be longer or shorter. How the system resolves this discrepancy is implementation-defined, but it's common for the leap second to share the same UNIX timestamp as the previous second. See also: Leap Seconds and What To Do With Them.
The Linux kernel leap second bug was a result of failing to do housekeeping after setting the clock back a second: https://lkml.org/lkml/2012/7/1/203. Even without that bug, the clock would have jumped backwards one second.
The POSIX-TIMERS API is a compatible implementation of the POSIX 1003.1 real-time clock/timer API. This feature is simply a library that might or might not be linked with an application. It is not a feature that can be turned on or off when configuring a system.
The interval timer APIs allow a process to set up and query a timer that can be programmed to auto-recur at a fixed time interval. The relevant system calls are these: #include <sys/time.
kqueue
and kevent
can be utilized for this purpose. OSX 10.6 and FreeBSD 8.1 add support for EVFILT_USER
, which we can use to wake up the event loop from another thread.
Note that if you use this to implement your own condition and timedwait, you do not need locks in order to avoid race conditions, contrary to this excellent answer, because you cannot "miss" an event on the queue.
Compile with clang -o test -std=c99 test.c
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
// arbitrary number used for the identifier property
const int NOTIFY_IDENT = 1337;
static int kq;
static void diep(const char *s) {
perror(s);
exit(EXIT_FAILURE);
}
static void *run_thread(void *arg) {
struct kevent kev;
struct kevent out_kev;
memset(&kev, 0, sizeof(kev));
kev.ident = NOTIFY_IDENT;
kev.filter = EVFILT_USER;
kev.flags = EV_ADD | EV_CLEAR;
struct timespec timeout;
timeout.tv_sec = 3;
timeout.tv_nsec = 0;
fprintf(stderr, "thread sleep\n");
if (kevent(kq, &kev, 1, &out_kev, 1, &timeout) == -1)
diep("kevent: waiting");
fprintf(stderr, "thread wakeup\n");
return NULL;
}
int main(int argc, char **argv) {
// create a new kernel event queue
kq = kqueue();
if (kq == -1)
diep("kqueue()");
fprintf(stderr, "spawn thread\n");
pthread_t thread;
if (pthread_create(&thread, NULL, run_thread, NULL))
diep("pthread_create");
if (argc > 1) {
fprintf(stderr, "sleep for 1 second\n");
sleep(1);
fprintf(stderr, "wake up thread\n");
struct kevent kev;
struct timespec timeout = { 0, 0 };
memset(&kev, 0, sizeof(kev));
kev.ident = NOTIFY_IDENT;
kev.filter = EVFILT_USER;
kev.fflags = NOTE_TRIGGER;
if (kevent(kq, &kev, 1, NULL, 0, &timeout) == -1)
diep("kevent: triggering");
} else {
fprintf(stderr, "not waking up thread, pass --wakeup to wake up thread\n");
}
pthread_join(thread, NULL);
close(kq);
return EXIT_SUCCESS;
}
$ time ./test
spawn thread
not waking up thread, pass --wakeup to wake up thread
thread sleep
thread wakeup
real 0m3.010s
user 0m0.001s
sys 0m0.002s
$ time ./test --wakeup
spawn thread
sleep for 1 second
thread sleep
wake up thread
thread wakeup
real 0m1.010s
user 0m0.002s
sys 0m0.002s
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With