ref(android): Measure ANR thresholds on the uptime clock (JAVA-579) - #6041
Draft
runningcode wants to merge 4 commits into
Draft
ref(android): Measure ANR thresholds on the uptime clock (JAVA-579)#6041runningcode wants to merge 4 commits into
runningcode wants to merge 4 commits into
Conversation
📲 Install BuildsAndroid
|
This was referenced Sep 2, 2026
runningcode
force-pushed
the
no/java-576-checkin-stopwatch
branch
from
September 2, 2026 13:35
edd22f0 to
fef63c7
Compare
runningcode
force-pushed
the
no/java-579-anr-uptime-clock
branch
from
September 2, 2026 13:35
cabb812 to
77eb8c9
Compare
The ANR tests advance the clock from the test thread while the watchdog thread reads it, which without volatile is a data race that can leave the watchdog looking at a stale tick forever.
The watchdog took its readings from an ICurrentDateProvider lambda over SystemClock.uptimeMillis(). The type said nothing about which clock it carried, so nothing stopped a caller from handing it an elapsed-real-time provider — and that substitution is not benign here: elapsed real time counts deep sleep, so a device suspended between posting the ticker and checking it would report the suspended interval as a blocked main thread and fabricate an ANR. UptimeClock names the guarantee the detection depends on, and Deadline replaces the hand-rolled "now minus last tick" arithmetic with the question actually being asked: has the main thread missed its window. System.nanoTime() is CLOCK_MONOTONIC on Android, the same clock behind SystemClock.uptimeMillis(), so the thresholds are unchanged.
…AVA-579) Same reasoning as the watchdog: the suspicion and ANR thresholds must not count time the device spent suspended. The clock is now injected rather than read from SystemClock, so the tests drive it directly instead of going through Robolectric's shadow clock.
runningcode
force-pushed
the
no/java-576-checkin-stopwatch
branch
from
September 2, 2026 13:37
fef63c7 to
9829cc8
Compare
runningcode
force-pushed
the
no/java-579-anr-uptime-clock
branch
from
September 2, 2026 13:37
77eb8c9 to
2065803
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Stack (Clock semantics hardening)
📜 Description
Moves both ANR detectors off
SystemClock.uptimeMillis()readings behind an untypedICurrentDateProviderand onto theUptimeClockinterface from #6028.ANRWatchDogtakes anUptimeClockinstead of anICurrentDateProviderlambda, and tracks themain thread's responsiveness window as a
Deadlinerather than alastKnownActiveUiTimestampMslong plus subtraction.
AnrProfilingIntegrationreads its suspicion and ANR thresholds from an injectedUptimeClockinstead of calling
SystemClockinline, and times stack capture with aStopwatch.TestTicker's tick is now@Volatile, because these tests advance the clock from the test threadwhile the watchdog thread reads it.
JavaUptimeClockisSystem.nanoTime(), which isCLOCK_MONOTONICon Android — the same clockbehind
SystemClock.uptimeMillis(). The thresholds are therefore unchanged; only the precision ofthe reading and the name on the type change. Nothing serialized is touched.
💡 Motivation and Context
ANR detection is the one place in the SDK where picking the wrong clock invents user-visible events.
uptimeMillis()excludes deep sleep, which is exactly "time the CPU was available to the mainthread". On an elapsed-real-time clock, a 30 s suspend between posting the ticker and checking it
would look like a 30 s frozen UI and report an ANR that never happened. The old
ICurrentDateProviderfield type could not tell those two clocks apart;UptimeClockcannot behanded the wrong one without failing to compile.
This is also what gives
UptimeClocka permanent consumer rather than only the behaviour-freeze onein #6032.
💚 How did you test it?
./gradlew :sentry-android-core:testReleaseUnitTest :sentry-android-core:apiCheck— green.New regression test
a device suspend does not trip the ANR threshold: the watchdog polls forhundreds of milliseconds of wall time while the uptime clock stands still, and no ANR is reported.
That is precisely the event an elapsed-real-time clock would fabricate. Its counterpart, the
existing
when ANR is detectedtest, advances the same clock and does report one.The
AnrProfilingIntegrationstate-machine tests now drive the injected clock instead ofRobolectric's shadow
SystemClock, asserting the same IDLE → SUSPICIOUS → ANR_DETECTED transitionsat the same offsets.
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
PR 6 of the stack deprecates the legacy
ICurrentDateProviderfamily and the date-reading helpersnow that the internal consumers have moved off them.