Repository navigation
Commit 4cc93b7
feat(time): Add a monotonic clock abstraction (JAVA-571) (#6028)
* feat(time): Add uptime and elapsed-real-time clock abstractions (JAVA-571)
`ICurrentDateProvider.getCurrentTimeMillis()` has two implementations that
mean different things: `CurrentDateProvider` returns wall time, while
`AndroidCurrentDateProvider` returns `SystemClock.uptimeMillis()`, which is
monotonic and pauses in deep sleep. Every consumer has to hand-pick the one
matching whatever it compares against, a wrong pairing compiles silently, and
the tests inject fakes so nothing catches it.
Name the guarantee instead. UptimeClock excludes time the device spent
suspended and is what ANR detection needs, since counting suspended time
reports a responsive main thread as blocked. ElapsedRealtimeClock includes it
and is what a rate-limit window or a cache TTL needs. A call site declaring
which one it wants can no longer be handed the other.
Both extend Ticker, which promises only "a nanosecond counter with an
arbitrary origin" so that Deadline and Stopwatch can be written once. That
minimalism is deliberate: a name promising a guarantee it does not keep is
the bug being fixed here.
Deadline and Stopwatch exist so callers never do arithmetic on raw ticks. A
tick carries no unit and no epoch, so `now - then < ttl` spelled out at each
call site is where unit mix-ups, sentinels that happen to mean "boot", and
wrap-unsafe comparisons come from. Deadline.passed() gives "not populated
yet" a representation outside the numeric range, hasPassed() subtracts rather
than compares so it holds for any origin, and remaining() rounds up so a
caller scheduling work for it never wakes to find the deadline still standing.
No call site is converted and no behaviour changes. Only the elapsed-real-time
clock will need an Android implementation: `SystemClock.uptimeNanos()` is API
34 against a minSdk of 21, and `System.nanoTime()` is already CLOCK_MONOTONIC
on Android, so it serves as the uptime clock on both platforms.
* changelog
* ref(time): Drop the uptime clock options seam (JAVA-571)
System.nanoTime() is CLOCK_MONOTONIC on Android too, and
SystemClock.uptimeNanos() is API 34 against minSdk 21, so there is no
platform-specific uptime implementation to install. The setter had no
production caller and its only test was a test of itself, while still
occupying binary-compatibility surface in sentry.api.
ElapsedRealtimeClock keeps its seam: RateLimiter lives in the core
module but needs SystemClock.elapsedRealtimeNanos() on Android, which
only sentry-android-core can supply.
UptimeClock and JavaUptimeClock remain; call sites that want the
guarantee named in their type resolve the singleton directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ref(time): Trim comments that restate the type name (JAVA-571)
The elapsed-real-time field carried a comment repeating its own type,
and both the setter and JavaElapsedRealtimeClock restated what the
ElapsedRealtimeClock javadoc already says at length.
The setter javadoc now answers the question a reader actually has when
they find a setter on an internal option: which platform installs one,
and why the core module cannot construct it itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(android): Back the elapsed-real-time clock with SystemClock (JAVA-571)
Installs AndroidElapsedRealtimeClock in AndroidOptionsInitializer, beside
the existing SentryAndroidDateProvider. Nothing reads the clock yet, so
this changes no behaviour.
Without it the options seam added in this PR is inert on Android: the
default resolves to System.nanoTime(), which is CLOCK_MONOTONIC and stops
in deep sleep, so a reviewer sees a setter with no caller and Android
silently gets the guarantee the type says it does not provide. That was
the flaw in the previous attempt at this abstraction, where the Android
clock was built into a local and never installed.
io.sentry.android.core.internal is in apiValidation.ignoredPackages, so
there is no .api diff.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(time): Drop the accessor and singleton clock tests (JAVA-571)
SentryOptionsClockTest asserted that a LazyEvaluator-backed getter
returns what its setter was given; AndroidOptionsInitializerTest already
covers the setter for real, on the one caller that uses it.
JavaClocksTest asserted singleton identity and that a nanosecond counter
does not run backwards.
Neither can fail without the language failing first. DeadlineTest and
StopwatchTest, which cover the arithmetic this package exists to
centralise, are untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ref(android): Override the elapsed-real-time clock instead of setting it
SentryAndroidOptions now returns AndroidElapsedRealtimeClock from an
override, so SentryOptions no longer needs a setter and the core default
collapses to the singleton it always returned.
Three things get better. The setter was a mutation point on an option
nobody should swap, and it is gone from sentry.api. Android is correct
from construction rather than from the moment AndroidOptionsInitializer
runs, closing the window where a reader saw System.nanoTime(). And
consumers that take a clock in their constructor, as RateLimiter will,
keep their own injection point for tests, so nothing lost a seam.
The cost is that this is the only getter SentryAndroidOptions overrides;
every other platform swap is installed in AndroidOptionsInitializer.
Those are user-replaceable options, though, and this one is internal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ref(android): Make AndroidElapsedRealtimeClock a singleton
It took a public constructor to match `new SentryAndroidDateProvider()`
on the line beside it in AndroidOptionsInitializer. That line is gone
now that SentryAndroidOptions overrides the getter, so the odd one out
was the clock rather than the neighbour.
With getInstance() it matches the two JVM clocks, and the field it was
stored in disappears: both overrides are now the same single line
returning a singleton.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(android): Drop the elapsed-real-time clock options test (JAVA-571)
* ref(time): Rename Deadline.in to Deadline.after (JAVA-571)
`in` is a Kotlin hard keyword, so every Kotlin call site had to spell it
`Deadline.`in`(...)`. Tests in this repo are Kotlin, and Kotlin callers
are expected in the Android and Kotlin integration modules, so the
backticks would have spread rather than stayed in one test file.
`after` is a plain identifier in both languages and pairs with the
existing `hasPassed()` and `isAfter()` vocabulary.
* ref(time): Update API dump for the Deadline.after rename (JAVA-571)
The Deadline.in to Deadline.after rename did not regenerate
sentry/api/sentry.api, so :sentry:apiCheck failed on CI.
* ref(time): Collapse the clocks into a single MonotonicClock (JAVA-571)
Two interfaces existed so that a call site could name which suspend
behaviour it needed, but only ANR detection wanted the one that excludes
deep sleep, and neither ANR path can be fooled by a suspend: ANRWatchDog
reports only once ActivityManager confirms NOT_RESPONDING, and
AnrProfilingIntegration parks its thread while backgrounded and resets
its baseline on wake.
That leaves one guarantee worth naming. Ticker, UptimeClock and
JavaUptimeClock are gone, and ElapsedRealtimeClock becomes
MonotonicClock, backed by elapsedRealtimeNanos() on Android and
nanoTime() on the JVM.
* ref(time): Rename MonotonicClock to MonotonicTicker (JAVA-571)
The type promises less than "clock" suggests: a tick carries no unit and
no epoch, and only differences between two ticks from the same instance
mean anything. "Ticker" keeps it from reading like a source of wall-clock
time.
Deadline.after now rejects a negative amount, since a deadline that
starts out in the past is a sign error at the call site and passed()
already expresses that case deliberately.
Also drops the "state that has not been populated yet" framing from the
Deadline.passed javadoc, covers isAfter on two equal deadlines, and
rewrites the tick-zero test comment to name the last-updated-timestamp
pattern it is about, rather than leaving "sentinel of 0" to be read as a
deadline of 0.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 38be688 commit 4cc93b7
13 files changed
Lines changed: 408 additions & 0 deletions
File tree
- sentry-android-core
- api
- src/main/java/io/sentry/android/core
- internal/time
- sentry-test-support/src/main/kotlin/io/sentry/time
- sentry
- api
- src
- main/java/io/sentry
- time
- test/java/io/sentry/time
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
11 | 15 | | |
12 | 16 | | |
13 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
414 | 414 | | |
415 | 415 | | |
416 | 416 | | |
| 417 | + | |
417 | 418 | | |
418 | 419 | | |
419 | 420 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| 21 | + | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| |||
889 | 891 | | |
890 | 892 | | |
891 | 893 | | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
892 | 900 | | |
893 | 901 | | |
894 | 902 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3740 | 3740 | | |
3741 | 3741 | | |
3742 | 3742 | | |
| 3743 | + | |
3743 | 3744 | | |
3744 | 3745 | | |
3745 | 3746 | | |
| |||
7597 | 7598 | | |
7598 | 7599 | | |
7599 | 7600 | | |
| 7601 | + | |
| 7602 | + | |
| 7603 | + | |
| 7604 | + | |
| 7605 | + | |
| 7606 | + | |
| 7607 | + | |
| 7608 | + | |
| 7609 | + | |
| 7610 | + | |
| 7611 | + | |
| 7612 | + | |
| 7613 | + | |
| 7614 | + | |
| 7615 | + | |
| 7616 | + | |
| 7617 | + | |
| 7618 | + | |
| 7619 | + | |
| 7620 | + | |
| 7621 | + | |
| 7622 | + | |
| 7623 | + | |
7600 | 7624 | | |
7601 | 7625 | | |
7602 | 7626 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
| |||
3059 | 3061 | | |
3060 | 3062 | | |
3061 | 3063 | | |
| 3064 | + | |
| 3065 | + | |
| 3066 | + | |
| 3067 | + | |
| 3068 | + | |
| 3069 | + | |
| 3070 | + | |
| 3071 | + | |
| 3072 | + | |
| 3073 | + | |
| 3074 | + | |
| 3075 | + | |
3062 | 3076 | | |
3063 | 3077 | | |
3064 | 3078 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
0 commit comments