20

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:

  • Not prone to clock skew (minus, of course, the system clock's own skew)
  • Resilient to the system clock being set or a leap second occurring
  • Able to support multiple timers in the same process
  • Available on at least Linux, Mac OS X, and FreeBSD

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.

Community
  • 1
  • 1
Left For Archive
  • 2,626
  • 1
  • 18
  • 19
  • Minor correction to the above regarding FreeBSD: clock_gettime(2) with CLOCK_MONOTONIC has been available in FreeBSD for quite a while (added in Feb 2003). clock_nanosleep(2) as described by POSIX.1 is not yet available (https://wiki.freebsd.org/FreeBSD_and_Standards) although could be partially implemented now (possibly without all the TIMER_ABSTIME features). – Juan Dec 31 '16 at 16:44

5 Answers5

6

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.

Sources:

Example Code

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;
}

Output

$ 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
Community
  • 1
  • 1
andrewrk
  • 30,272
  • 27
  • 92
  • 113
5

POSIX timers (timer_create) do not require signals; you can also arrange for the timer expiration to be delivered in a thread via the SIGEV_THREAD notification type. Unfortunately glibc's implementation actually creates a new thread for each expiration (which both has a lot of overhead and destroys any hope of realtime-quality robustness) despite the fact that the standard allows reuse of the same thread for each expiration.

Short of that, I would just recommend making your own thread that uses clock_nanosleep with TIMER_ABSTIME and CLOCK_MONOTONIC for an interval timer. Since you mentioned that some broken systems might lack these interfaces, you could simply have a drop-in implementation (based e.g. on pthread_cond_timedwait) on such systems, and figure it might be lower-quality due to lack of monotonic clock, but that this is just a fundamental limitation of using a low-quality implementation like MacOSX.

As for your concern about leap seconds, if ntpd or similar is making your realtime clock jump backwards when a leap second occurs, that's a serious bug in ntpd. POSIX time (seconds since the epoch) are in units of calendar seconds (exactly 1/86400 of a day) per the standard, not SI seconds, and thus the only place leap second logic belongs on a POSIX system (if anywhere) is in mktime/gmtime/localtime when they convert between time_t and broken-down time. I haven't been following the bugs that hit this time, but they seem to have resulted from system software doing a lot of stupid and wrong stuff, not from any fundamental issue.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • timer_create is also, unfortunately, not available on the Mac :( Neither is clock_nanosleep or CLOCK_MONOTONIC. Otherwise, this is exactly what I'd do. – Left For Archive Jul 05 '12 at 14:39
  • Also, the way time is mapped during a leap second is implementation defined. ntpd makes a call advising the kernel that there is a leap second coming, which it then 'inserts' at the appropriate time, I believe by repeating the previous second. See: http://derickrethans.nl/leap-seconds-and-what-to-do-with-them.html. Even without that problem, anything that calls `settimeofday` can make the clock jump arbitrarily. – Left For Archive Jul 05 '12 at 14:48
  • See also the explanation of the Linux leap second bug: https://lkml.org/lkml/2012/7/1/203. Note the cause was failure to call `clock_was_set()` after jumping the clock back one second. – Left For Archive Jul 05 '12 at 15:48
  • Jumping the clock back whatsoever is **fundamentally wrong**. The system realtime clock is in calendar seconds, not SI seconds, per POSIX. Accounting for leap seconds should just happen (1) as part of the natural gradual adjustment ntpd performs with `adjtimex`, and (2) when converting to/from `struct tm` format, which is allowed (but not required) to use SI seconds/leap seconds. – R.. GitHub STOP HELPING ICE Jul 05 '12 at 23:38
  • The system realtime clock is in calendar seconds, _minus leap seconds_. From the spec: http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_15. Besides, fundamentally wrong or not, stepping the clock back one second is what happens, at least on Linux. – Left For Archive Jul 06 '12 at 00:20
  • Your citation says it *should increase even during leap seconds*. Jumping backwards is just wrong. The Linux ntp behavior is buggy and should be fixed. It can be fixed just by removing the leap second code from ntpd and having it treat the upsteam leap second as a condition that requires gradual adjustment client-side, the same as if the client-side clock were drifting badly that day. – R.. GitHub STOP HELPING ICE Jul 06 '12 at 00:44
  • 3
    (1) The language seems to stop short of requiring it, though. (2) Even without leap seconds, what happens when the admin sets the clock back 5 minutes manually? Interestingly enough, the standard seems to put the responsibility on the admin not to do that if it'll break an application. It seems unreasonable, though, to write code that depends on an admin never doing something so seemingly innocent as correcting the time on the machine. Can you see why I'd want to handle this sanely in my application? – Left For Archive Jul 06 '12 at 00:56
  • Yes, I see why applications want a monotonic clock. At the same time, however, I believe the whole point of using ntp is to make it so the admin never has to set the clock in a way that breaks the monotonicity and continuity of the realtime clock, and the ridiculous way it's treating leap seconds defeats that purpose. – R.. GitHub STOP HELPING ICE Jul 06 '12 at 01:37
  • 1
    @R.: It's not that simple. Note that POSIX gives [a formula for conversion between UTC and seconds-since-the-epoch](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15), and if you want your seconds-since-the-epoch value to reflect actual UTC as per that definition at all times then you need to jump the clock backwards at the leap second. – caf Jul 07 '12 at 14:25
  • @caf: See the "should" I cited above. Perhaps this is inconsistent, but I think the intent is that POSIX time should not involve leap seconds whatsoever. Of course I'm biased and hate leap seconds with a passion... – R.. GitHub STOP HELPING ICE Jul 07 '12 at 15:59
  • @R.: I tend to agree that POSIX time *should* have simply been specified as the number of SI seconds elapsed since the epoch, and leap seconds left up to the usual conversion to timezone, removing the privileged status of UTC. But it also seems that that ship has long since sailed. – caf Jul 08 '12 at 00:32
  • Not sure who you're agreeing with. I think SI seconds should have been left out of everything to do with ordinary human timekeeping and everything (including `struct tm`) should be in calendar seconds. – R.. GitHub STOP HELPING ICE Jul 08 '12 at 00:54
1

You can look at the question here for clock_gettime emulation, which I've also supplied an answer for, but helped me as well. I've recently added a simple timer to a little repository I keep for Mac OS X timing that partially emulates POSIX calls. A simple test runs the timer at 2000Hz. The repo is called PosixMachTiming. Try it out.

PosixMachTiming is based on Mach. It seems some of the timing-related Mach API has disappeared from Apple's pages and has deprecated, but there are still bits of source code floating around. It looks like AbsoluteTime units and kernel abstractions found here are the new way of doing things. Anyways the PosixMachTiming repo still works for me.

Overview of PosixMachTiming

clock_gettime is emulated for CLOCK_REALTIME by a mach function calls that tap into the system realtime clock, dubbed CALENDAR_CLOCK.

The clock_gettime is emulated for CLOCK_MONOTONIC by using a global variable (extern mach_port_t clock_port). This clock is initialized when the computer turns on or maybe wakes up. I'm not sure. In any case, it's the global variable that the function mach_absolute_time() calls.

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, ...) is emulated by using nanosleep on the difference between current time and the absolute monotonic time.

itimer_start() and itimer_step() are based on calling clock_nanosleep for a target absolute monotonic time. It increments the target time by the time-step at each iteration (not the current time) so that clock skew is not an issue.

Note that this does not satisfy your requirement to be able to support multiple timers in the same process.

Community
  • 1
  • 1
ChisholmKyle
  • 449
  • 3
  • 10
1

There's also the new CLOCK_TAI which doesn't take the leap second corrections the real time clocks do.

The UNIX Man
  • 111
  • 3
  • Note also that leap seconds are going away soon (in large part because they're well-nigh impossible to implement properly). – Steve Summit Feb 03 '23 at 19:41
-2

We can make use of timer_create () or timerfd_create () . Their examples are present in man page .

vikash singh
  • 1,479
  • 1
  • 19
  • 29
  • 1
    Thanks for the suggestion! However, timer_create, while included in POSIX, is unimplemented on macOS at least as recently as 10.14.6. timerfd_create is Linux-specific. – Left For Archive Jan 22 '20 at 01:57