On 12/30/2022 9:08 AM, pozz wrote:
>>> At startup, if NTP server is not available and I don't have any notion of
>>> "now", I start from a date in the past, i.e. 01/01/2020.
>>
>> Then you have to be able to accept a BIG skew in the time when the first
>> update arrives. What if that takes an hour, a day or more (because the
>> server is down, badly configured or incorrect routing)? What if it
>> *never* arrives?
>
> Certainly there's an exception at startup. When the *first* NTP response
> received, the code should accept a BIG shock of the current notion of now (that
> could be undefined or 2020 or another epoch until now).
> I read that ntpd accepts -g command line option that enable one (and only one)
> big difference between current system notion of now and "NTP now".
Yes. But, your system design still has to "make sense" if it NEVER gets
told the current time.
> I admit that this could lead to odd behaviours as you explained. IMHO however
> there aren't many solutions at startup, mainly if the embedded device should be
> autonomous and can't accept suggestions from the user.
Note that "wall/calendar time" is strictly a user convenience. A device
need only deal with it if it has to interact with a world in which the
user relates to temporal events using some external timepiece -- which
may actually be inaccurate!
But, your device can always have its own notion of time that monotonically
increases -- even if the rate of time that it increases isn't entirely
accurate wrt "real units" (i.e., if YOUR second is 1.001 REAL seconds,
that's likely not too important)
So, if you can postpone binding YOUR "system time" (counting jiffies)
to "wall time", then the problem is postponed.
E.g., I deal with events as referenced to *my* timebase (bogounits):
00000006 system initialized
00001003 network on-line
00001100 accepting requests
00020348 request from 10.0.1.88
00020499 reply to 10.0.1.88 issued
...
Eventually, there will be an entry:
XXXXXXXX NTPclient receives update (12:42:00.444)
At this point, you can retroactively update the times in the "log" with
"real" times, relative to the time delivered to the NTP client.
(or, leave the log in bogounits and not worry about it)
The real problem comes when <someone> wants <something> to happen at
some *specific* wall time -- and, you don't yet know what the current
wall time happens to be!
If that will be guaranteed to be sufficiently far in the future that
you (think!) the actual wall time will be known to you, then you
can just cross your fingers and wait to sort out "when" that will be.
> One is to suspend, at startup, all the device activites until a "fresh now" is
> received from NTP server. After that, the normal tasks are started. As you
> noted, this could introduce a delay (even a BIG delay, depending on Internet
> connection and NTP servers) between the power on and the start of tasks. I
> think this isn't compatible with many applications.
>
> Another solution is to fix the code in such a way it correctly faces the
> situation of a big afterward or backward step in the "now" counter.
You're better off picking a time you KNOW to be in the past so that
any adjustments continue to run time *forwards*. We inherently think
of A happening after B implying that time(A) > time(B). It's so
fundamental that you likely don't even notice these dependencies in
your code/algorithms.
> The code I'm thinking of is not the one that manages normal timers that can
> depend on a local reference (XTAL, ceramic resonator, ...) completely
> independent from calendar counter. Most of the time, the precision of timers
> isn't strict and intervals are short: we need to activate a relay for 3 seconds
> (but nothing happens if it is activated for 3.01 seconds) or we need to
> generate a pulse on an output of 100ms (but no problem if it is 98ms).
> This means having a main counter clocked at 10ms (or whatever) from a local
> clock of 100Hz (or whatever). This counter isn't corrected with NTP.
> The only code that must be fixed is the one that manages events that must
> occurs at specific calendar times (at 12 o'clock of 1st January, at 8:30 of
> everyday, and so on). So you should have *another* counter clocked at 1Hz (or
> 10Hz or 100Hz) that is adjusted by NTP. And abrupt changes should be taken into
> account (event if I don't know how).
You can use NTP to discipline the local oscillator so that times
measured from it are "more accurate". This, regardless of whether
or not the local time tracks the wall time.
>> If you apply the new time in a step function, then all of the potential
>> time related events between ~1/1/2020 and "now" will appear to occur
>> at the same instant -- *now* -- or, not at all. And, any time-related
>> calculations will be grossly incorrect.
>>
>> start_time := now()
>> dispenser(on)
>> wait_until(start_time + interval)
>>
>> Imagine what will happen if the time is changed during this fragment.
>> If the change adds >= interval to the local notion of now, then the
>> dispenser will be "on" only momentarily. If it adds (0,interval),
>> then it will be on for some period LESS than the "interval" intended.
>>
>> [I'm ignoring the possibility of it going BACKWARDS, for now]
>>
>> Note that wait_until() could have been expressed as delay(interval)
>> and, depending on how this is internally implemented, it might be
>> silently translated to a wait_until() and thus dependant on the
>> actual value of now().
>
> Good point. As I wrote before, events that aren't strictly related to wall
> clock shouldn't be coded with functions() that use now(). If the code that
> makes a 100ms pulse at an output uses now(), it is wrong and must be corrected.
Time should always be treated "fuzzily".
So, if (now() == CONSTANT) may NEVER be satisfied! E.g., if the code
runs at time CONSTANT+1, then you can know that it's never going to
meet that condition (imagine it in a wait_till loop)
If, instead, you assume that something may delay that statement from
being executed *prior* to CONSTANT, you may, instead, want to
code it as "if (now() >= CONSTANT)" to ensure it gets executed.
(and, if you only want it to be executed ONCE, then take steps to
note when you *have* executed it so you don't execute it again)
For example, my system is real-time so every action has an
associated deadline. But, it is entirely possible that some
actions will be blocked until long after their deadlines
have expired. Checking for "now() == deadline" would lead
to erroneous behavior; the time between deadline and now()
effectively doesn't exist, from the perspective of the
action in question. So, the deadline handler should be
invoked for ANY now() >= deadline.
What if a "second" is skipped (because some higher priority activity
was using the processor)?
> void cevents_do(time_t now) {
> static time_t old_now;
> if (now != old_now + 1) {
> /* There's a discontinuity in now. What can we do?
> * - Remove expired events without calling callback
> * - Remove expired events and call callback for each of them
> * I think the choice is application dependent */
> }
> /* Process the first elements of FIFO queue (that is sorted) */
> cevent_s *ev;
> while((ev = cevents_queue_peek())->time == now) {
> ev->fn(ev->arg);
> cevents_queue_pop();
> }
> old_now = now;
> }
You have to figure out how to generalize this FOR YOUR APPLICATION.
Some things may not be important enough to waste effort on;
others may be considerably more sensitive.
>>>> *or* you have to look at your current notion of "now"
>>>> and ensure that the "real" value of now, when obtained from the
>>>> time server, is always in the future relative to your notion.
>>>
>>> Actually I don't do that and I replace the timer counter with the value
>>> retrieved from NTP.
>>
>> Then you run the risk that the local counter may have already surpassed
>> the NTP "count" by, for example, N seconds. And, time now jerks backwards
>> as the previous N seconds appear to be relived.
>>
>> Will you AGAIN do the task that was scheduled for "a few seconds ago"?
>> (even though it has already been completed) Will you remember to ALSO
>> do the task that expected to be done an hour before that -- if the "jerk
>> back" wasn't a full hour?
>
> Good questions. You could try to implement a complex calendar time system in
> your device, one that mimics full featured OS. I mean the counter that tracks
> "now" (seconds or milliseconds from an epoch) isn't changed abruptly, but its
> reference is slowed down or accelerated.
If you ensure time always moves forward, most of these issues are
easy to resolve. You *know* you haven't done things scheduled for
t > now().
> You should have an hw that supports this. Many processors have timers that can
> be used as counters, but their clock reference is limited to a prescaled main
> clock and the prescaler value is usually an integer, maybe only one from a
> limited set of values (1, 2, 4, 8, 32, 64, 256).
You can dither the timebase so the average rate tracks your intent.
> Anyway, even if you are so smart to implement this in a correct way, you have
> to solve the "startup issue". What happens if the first NTP response arrived
> after 5 minutes from startup and your notion of now at startup is completely
> useless (i.e., no battery is present)?
> Maybe during initialization code you already added some calendar events.
So, those may *never* get executed -- if the NTP server never replies OR
if you've coded for "time == now()". Or, may get executed (much) later
than intended (e.g., if the NTP server tells you it is 6:00, now, and
you had something scheduled for 5:00...)
>>> What happens if the time doesn't flow in one direction only?
>>
>> Then everything that (implicitly) relies on time to be monotonic is
>> hosed.
>>
>> Repeat the examples at the start of my post with the case of time
>> jumping backwards and see what happens.
>>
>> What if time goes backwards enough to muck with some calculation
>> or event sequence -- but, not far enough to cause the code that
>> *schedules* those events to reflect the difference.
>>
>> What would you do if you saw entries in a log file:
>>
>> 12:01:07 start something
>> 12:01:08 did whatever
>> 12:01:15 did something else
>> 12:01:04 finished up
>
> In a real world, could this happen?
In a multithreaded application, of course it can!
task0() {
spawn(task1);
log("finished up");
}
task1() {
log("start something");
...
log("did whatever");
log("did something else")
}
Assume log() prepends a timestamp to the message emitted.
Assume task1 is lower priority than task0. It is spawned by
task0 but doesn't get a chance to execute until after
task0 has already printed its final message and quit.
If multiple processors/nodes are involved, then the uncertainty
between their individual clocks further complicates this.
And, of course, what do you do if <something> deliberately
introduces a delta to the current time?
Imagine Bob wants to set an alarm for a meeting at 5:00PM.
He then changes the current time to one hour later -- presumably
because he noticed that the clock was incorrect. Does that
mean the meeting will be one hour *sooner* than it would
appear to have been, previously?
What if he notices the date is off and it's really "tomorrow"
and advances the date by one day. Should the alarm be
canceled as the appointed time has already passed? Or,
should the date component of the alarm time be similarly
advanced?
And, what will *Bob* think the correct answers to these
questions should be? Will he be pissed because the alarm
didn't go off when he *expected* it? Or, pissed because the
alarm went off even though the meeting was YESTERDAY?