Waiting for a specific wall-clock time in Unix
At least on Unix systems, time is a subtle but big pain for
programmers. The problem is that because the clock can jump forward,
stand still (during leap seconds), or even go backwards, your
expectations about what subtracting and adding times does can wind
up being wrong under uncommon or rare circumstances. For instance,
you can write code that assumes that the difference between a time
in the past and now()
can be at most zero. This assumption recently
led to a Cloudflare DNS outage during a leap second, as covered
in Cloudflare's great writeup of this incident.
The solution to this is a new sort of time. Instead of being based on wall-clock time, it is monotonic; it always ticks forward and ticks at a constant rate. Changes in wall-clock time don't affect the monotonic clock, whether those are leap seconds, large scale corrections to the clock, or simply your NTP daemon running the clock a little bit slow or fast in order to get it to the right time. Monotonic clocks are increasingly supported by Unix systems and more and more programming environments are either supporting them explicitly or quietly supporting them behind the scenes. All of this is good and fine and all that, and it's generally just what you want.
I have an unusual case, though, where I'd actually like the reverse functionality. I have a utility that wants to wait until a specific wall-clock time. If the system's wall-clock time is adjusted, I'd like my waiting to immediately be updated to reflect that and my program woken up if appropriate. Until I started writing this entry, I was going to say that this is impossible, but now I believe that it's possible in POSIX. Well, in theory it's possible in POSIX; in practice it's not portable to at least one major Unix OS, because FreeBSD doesn't currently support the necessary features.
On a system that supports this POSIX feature, you have two options:
just sleeping, or using timers. Sleeping is easier; you use
clock_nanosleep
using the CLOCK_REALTIME
clock with the TIMER_ABSTIME
flag.
The POSIX standard (and Linux)
specify that if the wall-clock time is changed, you still get woken
up when appropriate. With timers, you use a similar but more
intricate process. You create a CLOCK_REALTIME
timer with
timer_create
and then use timer_settime
to set a TIMER_ABSTIME
wait time. When the timer expires, you
get signalled in whatever way you asked for.
In practice, though, this doesn't help me. Not only is this clearly
not supported on every Unix, but as far as I can see Go doesn't
expose any API for clock_nanosleep
or equivalent functionality.
This isn't terribly surprising, since sleeping in Go is already
deeply intertwined with its multi-threaded runtime. Right now my
program just approximates what I want by waking up periodically in
order to check the clock; this is probably the best I can do in
general for a portable program, even outside of Go.
(If I was happy with a non-portable program that only worked on
Linux, probably the easiest path would be to use Python with the
ctypes
module
to directly call clock_nanosleep
with appropriate arguments.
I'm picking Python here because I expect it's the easiest language
for easy and reasonably general time parsing code. Anyways, I already
know Python and I've never used the ctypes
module, so it'd be fun.)
Sidebar: The torture case here is DST transitions
I started out thinking that DST transitions would be a real problem, since either an hour disappears or happens twice. For example, if I say 'wait until 2:30 am' on the night of a transition into DST, I probably want my code to wake up again when the wall-clock time ticks from 2 am straight to 3 am. Similarly, on a transition out of DST, should I say 'wake up at 2:10 am', I probably don't want my code waking up at the second 1:10 am.
However, the kernel actually deals in UTC time, not local time. In practice all of the complexity is in the translation from your (local time) time string into UTC time, and in theory a fully timezone and DST aware library could get this (mostly) right. For '2:30 am during the transition into DST', it would probably return an error (since that time doesn't actually exist), and for '2:10 am during the transition out of DST' it should return a UTC time that is an hour later than you'd innocently expect.
(This does suggest that parsing such times is sort of current-time dependent. Since there are two '1:30 am' times on the transition out of DST, which one you want depends in part on what time it is now. If the transition hasn't happened yet, you probably want the first one; if the transition has happened but it's not yet the new 1:30 am yet, you probably want the second.)
|
|