The source code looks good.
Merge to main
and ship it.
Consider writing test code which highlights observed
behavior difference between this and competing time sources.
I worry that using "raw" without frequency correction
will produce output which isn't directly comparable with
the S.I. second.
The code relies on some assumptions the OP explained:
Since steady_clock is affected by certain clock adjustments,
in some parts of our codebase ...
I believe "affected" is a smaller concern than you feel it is.
Some review context is missing.
I could be more specific if the OP had described the app-level
concerns about error magnitudes, and what portion of the error budget
is consumed by non-clock sources of error.
(1.) apply offset
Yes, upon bootup we may notice the system time is e.g. more than
an hour off from NTP time, so we immediately apply a large +
or -
offset.
In principle this can happen after the OS has been running for a while.
Applications rightly fear this,
and they properly choose a monotonic clock which will never go backwards.
In practice, for an OS that boots up and runs continuously for some number of days,
we wouldn't really expect to see more than one offset applied during those days.
Even the laptop "suspend" scenario should work smoothly with time-sensitive apps.
(2.) slew rate
Dave Mills originally designed adjtime(2) to run the clock 10% faster or 10% slower
until it drove the NTP error to approximately zero.
Modern usage is significantly more conservative.
The (darwin) mac manpage explains:
adjtime – correct the time to allow synchronization of the system clock
adjtime() makes small adjustments to the system time, as returned by
gettimeofday(2), advancing or retarding it by the time specified by the
timeval delta. If delta is negative, the clock is slowed down by
incrementing it more slowly than normal until the correction is complete.
If delta is positive, a larger increment than normal is used.
The skew
used to perform the correction is generally a fraction of one percent.
Thus, the time is always a monotonically increasing function.
BSD source code
explains that typically if the slew rate is non-zero it will
adjust by
just half a millisecond every second:
/*
Apply any correction from adjtime(2). If more than one second
off we slew at a rate of 5ms/s (5000 PPM) else 500us/s (500PPM)
until the last second is slewed the final < 500 usecs.
*/
The linux source
is less explicit:
/*
If the clock is behind the NTP time, increase the multiplier by 1
to catch up with it. If it's ahead and there was a remainder in the
tick division, the clock will slow down. Otherwise it will stay
ahead until the tick length changes to a non-divisible value.
*/
Sorry, I wasn't able to divine any numeric bounds from that.
But maybe someone can, and will post a comment?
BTW, time daemons on linux prefer this interface:
adjtimex(2) is Linux-specific and should not be used in programs intended to be portable. See adjtime(3) for a more portable, but
less flexible, method of adjusting the system clock.
iburst
Time daemons would traditionally boot up then
measure time and frequency offsets for at least 64 seconds,
often for a few minutes,
before issuing the first correction through a kernel interface.
In part this conservative behavior was to
avoid {DDoS, network load, traffic amplification}
effects seen by central time servers.
And then came iburst
, the "initial burst" of time queries.
Trust me, you really want at least one reachable iburst server
in your NTP config, it's pretty essential.
The idea is that biggest NTP error offset will typically
be observed right around boot time,
and we should deal with it fast,
without awaiting the results of careful long-term observation.
So instead of having "candidate" time servers for the
first few minutes after boot, a time client will quickly
settle on one and issue a local correction.
As the minutes go by it might decide that actually another server
appears to be optimal, and switch to that.
But we're probably within 10 msec of true time at that point,
so apps are unlikely to notice the switch.
leap second
In principle a POSIX app can observe "23:59:59" tick over
to "23:59:60" during a leap second.
In practice only astronomers care about such details,
and everyone else would rather pretend the earth isn't slowing down.
Typically a time daemon will "smear" that one second of error
across a few hours prior to UTC midnight.
You should either document such concerns for callers of your API
and author relevant unit tests,
or explicitly write down "it just doesn't matter!"
tl;dr: The implementation looks good.
Depending on app details we don't know about,
there possibly is no need to {implement, document, test, support} this API.