On Tue, 23 Oct 2018, Chris Jones wrote:

I've stumbled into the same issue twice in recent days, with two different ports, which is the use of

clock_gettime(CLOCK_REALTIME, &wait);

which is only available in macOS 10.12 or newer. See for instance the issue I found yesterday in xrootd.

https://github.com/xrootd/xrootd/issues/846

I am still waiting to see what upstream say, but I am hopeful they will consider it a bug. ( It would seem quite extreme to reduce the supported OSX releases from 10.7+ to 10.12+ in a minor patch revision...)

But I was wondering, is this something anyone else has stumbled over, and do we have a way of fixing this particular issue in the older OSes ?

Yes, in both GPSD and ntpsec. Rather than trying to figure out which of the many messages in this thread to answer directly, I'll just throw out what I know about this issue.

There are three "global" timescales potentially provided by clock_gettime(): CLOCK_REALTIME, CLOCK_MONOTONIC, and CLOCK_MONOTONIC_RAW. Only the first is eligible for clock_settime().

CLOCK_REALTIME is the same "Unix" timescale as the original time() and later gettimeofday(), but with (ostensibly) nanosecond resolution. It's subject to both slewing and step adjustments, as needed to synchronize its value to some time source.

CLOCK_MONOTONIC was created to avoid problems (including crashes) sometimes caused by the backward step adjustments that may be applied to CLOCK_REALTIME. Although the official documentation is woefully underspecified, it's typically implemented as a variation on CLOCK_REALTIME that excludes all step adjustments (including the initial one that "sets the clock"), but includes all slewing. This makes it continuous as well as monotonic, but its rate accuracy is corrupted by the slewing adjustments. In practice, it's almost never what you really want.

CLOCK_MONOTONIC_RAW is also woefully underspecified, but is usually just the raw hardware time source scaled to standard units based on the assumed clock rate, but not steered at all. Since even the cheapest crystals are typically rated at +/- 100ppm or better, and since the slewing adjustments applied to CLOCK_MONOTONIC can easily be much larger than that, CLOCK_MONOTONIC_RAW is usually a more accurate timescale for rates, durations, and delays than CLOCK_MONOTONIC.


There are basically two fallback options for CLOCK_REALTIME: gettimeofday() and the Mach-specific clock_get_time() based on CALENDAR_CLOCK. The latter ostensibly has nanosecond resolution, but in reality it's only the *representation* that has nanosecond resolution, while the values are all multiples of 1000 nanoseconds. In addition, it's more than an order of magnitude slower than gettimeofday() even in the best case, and the commonly circulated example of its use is even slower, as well as having a "port leak" bug. Thus, it's best to simply use gettimeofday() with a microseconds->nanoseconds as a fallback. This approach also works for substituting settimeofday() for clock_settime(CLOCK_REALTIME, ...). IMO, the microsecond->nanosecond conversion should be done without "rounding", but the nanosecond->microsecond conversions should round by adding 500ns prior to the floored division by 1000. The unrounded conversion is consistent with both clock_get_time() and the "official" clock_gettime() in 10.12+, which still only has microsecond actual resolution.

For CLOCK_MONOTONIC, I believe the only functionally correct fallback is to use clock_get_time() with SYSTEM_CLOCK. As noted above, programs shouldn't really be using CLOCK_MONOTONIC anyway, but it's necessary to include it for compatibility with programs too dumb to know that. The problem with clock_get_time() is that it requires messing with Mach ports. The most efficient way to do this is to obtain a SYSTEM_CLOCK port once initially, and the reuse it on each call. Even with this, it takes over 700ns on a 3.46GHz Mac Pro, as compared to ~40ns for gettimeofday(), but that's the price of correctness. Since something intended to be a drop-in replacement for clock_gettime() can't rely on initialization or cleanup functions, the best it can do is to allocate the Mach port on first call, and then rely on exit cleanup to eventually deallocate it.

For CLOCK_MONOTONIC_RAW, the straightforward approach is to use mach_absolute_time() with the proper scaling. As long as the scale factors are constant, they can be obtained once initially and then cached for later use. Unlike the clock_get_time() case, cleanup isn't even an issue. However, I've seen some mention of the possibility that the rate of mach_absolute_time() may not be constant. I'm not aware of any cases where this actually happens, and perhaps it's only theoretical, but if it did actually happen, it would complicate things significantly. In order to convert a variable-rate clock to standard units, it's necessary to know not only the current scale factor, but also the last time that the factor changed and what the correspondence was at that time. Since that information isn't provided, either the scale is actually constant or the API is deficient. Hopefully it's the former.


My clock_gettime() replacement for ntpsec is defined directly in a header file as an inline function. Although it currently only supports CLOCK_REALTIME, it does include a switch() on the clock_id for extensibility. A significant advantage of the inline approach is that whenever the clock_id is a compile-time constant (as is almost always the case in real use cases), the optimizer can completely remove the switch() and degenerate into just the inline code needed (quite simple for CLOCK_REALTIME) in the relevant case. And of course it also avoids adding new link-time dependencies.

Fred Wright

Reply via email to