monotonicClockOffset is a timeval member with no initializer, and Timekeeper's constructor
only initialises offset. The singleton is heap allocated, so the member holds indeterminate
bytes until Timekeeper::setOffset latches it, which happens on the first Clock::setClock.
Until that point getClockMonotonic returns garbage plus uptime, and at the latch it jumps by
whatever the difference happens to be. Any Countdown or Stopwatch armed before the latch
expires wrongly, in both directions:
backward jump: the getCurrentTime() < startTime guard in Countdown::hasTimedOut
On our OBC this is not a race but the normal case. The CoreController sets the clock from its
own task, so everything constructed during object creation and early boot is on the wrong side
of the latch.
Why the offset can go
The monotonicity came from the uptime alone. The offset only made the value look epoch based,
and nothing depends on that:
Countdown uses getCurrentTime() - startTime
Stopwatch uses endTime - startTime
PeriodicHelper::performPeriodicHkGeneration uses now - setSpec.lastGenerated
Those are the only three callers in the framework. All take differences.
The Linux and host implementations already return CLOCK_MONOTONIC_RAW with no offset, so
FreeRTOS was the only OSAL where getClockMonotonic meant something different. The interface
doc in Clock.h also already describes the intended contract: "less suited when the absolute
time is required", with CLOCK_MONOTONIC_RAW named as the reference implementation.
osal/freertos/Timekeeper.{h,cpp}: drop monotonicClockOffset, monotonicClockInitialized, getMonotonicClockOffset and the never defined setMonotonicClockOffset. setOffset is now
a one liner
timemanager/Clock.h: drop the monotonicClockInitialized and monotonicClockOffset statics,
which were declared but never defined or used
Clock::getClock is unchanged and still returns offset plus uptime, so the wall clock is
unaffected.
Compatibility
getClockMonotonic on FreeRTOS now counts from scheduler start instead of the epoch. Code that
compares a monotonic timestamp against a wall clock value would break, but that would already be
broken on Linux, and no such code exists.
`Clock::getClockMonotonic` on FreeRTOS returned an epoch offset added on top of the uptime:
```cpp
*time = Timekeeper::instance()->getMonotonicClockOffset() + getUptime();
```
`monotonicClockOffset` is a `timeval` member with no initializer, and `Timekeeper`'s constructor
only initialises `offset`. The singleton is heap allocated, so the member holds indeterminate
bytes until `Timekeeper::setOffset` latches it, which happens on the first `Clock::setClock`.
Until that point `getClockMonotonic` returns garbage plus uptime, and at the latch it jumps by
whatever the difference happens to be. Any `Countdown` or `Stopwatch` armed before the latch
expires wrongly, in both directions:
- forward jump: `getCurrentTime() - startTime >= timeout`
- backward jump: the `getCurrentTime() < startTime` guard in `Countdown::hasTimedOut`
On our OBC this is not a race but the normal case. The CoreController sets the clock from its
own task, so everything constructed during object creation and early boot is on the wrong side
of the latch.
## Why the offset can go
The monotonicity came from the uptime alone. The offset only made the value look epoch based,
and nothing depends on that:
- `Countdown` uses `getCurrentTime() - startTime`
- `Stopwatch` uses `endTime - startTime`
- `PeriodicHelper::performPeriodicHkGeneration` uses `now - setSpec.lastGenerated`
Those are the only three callers in the framework. All take differences.
The Linux and host implementations already return `CLOCK_MONOTONIC_RAW` with no offset, so
FreeRTOS was the only OSAL where `getClockMonotonic` meant something different. The interface
doc in `Clock.h` also already describes the intended contract: "less suited when the absolute
time is required", with `CLOCK_MONOTONIC_RAW` named as the reference implementation.
## Changes
- `osal/freertos/Clock.cpp`: `getClockMonotonic` returns `getUptime()`
- `osal/freertos/Timekeeper.{h,cpp}`: drop `monotonicClockOffset`, `monotonicClockInitialized`,
`getMonotonicClockOffset` and the never defined `setMonotonicClockOffset`. `setOffset` is now
a one liner
- `timemanager/Clock.h`: drop the `monotonicClockInitialized` and `monotonicClockOffset` statics,
which were declared but never defined or used
`Clock::getClock` is unchanged and still returns offset plus uptime, so the wall clock is
unaffected.
## Compatibility
`getClockMonotonic` on FreeRTOS now counts from scheduler start instead of the epoch. Code that
compares a monotonic timestamp against a wall clock value would break, but that would already be
broken on Linux, and no such code exists.
getClockMonotonic added monotonicClockOffset on top of the uptime. That
offset is indeterminate until the first setClock call and jumps when it is
latched, so every Countdown and Stopwatch armed before that point either
expires immediately or measures against garbage.
The monotonicity came from the uptime alone, the offset only made the value
look epoch based. The Linux and host implementations return
CLOCK_MONOTONIC_RAW with no offset, so this also makes the OSALs agree.
All users take differences only: Countdown, Stopwatch and PeriodicHelper.
Also drops the Clock statics monotonicClockInitialized and
monotonicClockOffset, which were declared but never defined or used.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tbaumgartl
requested review from muellerr 2026-09-24 23:02:26 +02:00
that comment is unnecessary. there is no need to justify or mention old/deleted/buggy code. maybe just mention that this retrieves the FreeRTOS uptime (which is hopefully fully monotonic)
that comment is unnecessary. there is no need to justify or mention old/deleted/buggy code. maybe just mention that this retrieves the FreeRTOS uptime (which is hopefully fully monotonic)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Clock::getClockMonotonicon FreeRTOS returned an epoch offset added on top of the uptime:monotonicClockOffsetis atimevalmember with no initializer, andTimekeeper's constructoronly initialises
offset. The singleton is heap allocated, so the member holds indeterminatebytes until
Timekeeper::setOffsetlatches it, which happens on the firstClock::setClock.Until that point
getClockMonotonicreturns garbage plus uptime, and at the latch it jumps bywhatever the difference happens to be. Any
CountdownorStopwatcharmed before the latchexpires wrongly, in both directions:
getCurrentTime() - startTime >= timeoutgetCurrentTime() < startTimeguard inCountdown::hasTimedOutOn our OBC this is not a race but the normal case. The CoreController sets the clock from its
own task, so everything constructed during object creation and early boot is on the wrong side
of the latch.
Why the offset can go
The monotonicity came from the uptime alone. The offset only made the value look epoch based,
and nothing depends on that:
CountdownusesgetCurrentTime() - startTimeStopwatchusesendTime - startTimePeriodicHelper::performPeriodicHkGenerationusesnow - setSpec.lastGeneratedThose are the only three callers in the framework. All take differences.
The Linux and host implementations already return
CLOCK_MONOTONIC_RAWwith no offset, soFreeRTOS was the only OSAL where
getClockMonotonicmeant something different. The interfacedoc in
Clock.halso already describes the intended contract: "less suited when the absolutetime is required", with
CLOCK_MONOTONIC_RAWnamed as the reference implementation.Changes
osal/freertos/Clock.cpp:getClockMonotonicreturnsgetUptime()osal/freertos/Timekeeper.{h,cpp}: dropmonotonicClockOffset,monotonicClockInitialized,getMonotonicClockOffsetand the never definedsetMonotonicClockOffset.setOffsetis nowa one liner
timemanager/Clock.h: drop themonotonicClockInitializedandmonotonicClockOffsetstatics,which were declared but never defined or used
Clock::getClockis unchanged and still returns offset plus uptime, so the wall clock isunaffected.
Compatibility
getClockMonotonicon FreeRTOS now counts from scheduler start instead of the epoch. Code thatcompares a monotonic timestamp against a wall clock value would break, but that would already be
broken on Linux, and no such code exists.
@@ -49,3 +49,3 @@ReturnValue_t Clock::getClockMonotonic(timeval* time) {*time = Timekeeper::instance()->getMonotonicClockOffset() + getUptime();// Ticks since scheduler start, with no epoch offset on top. Same contract as the Linux andthat comment is unnecessary. there is no need to justify or mention old/deleted/buggy code. maybe just mention that this retrieves the FreeRTOS uptime (which is hopefully fully monotonic)