ref(android): Measure ANR thresholds on the monotonic clock (JAVA-579) - #6041
Draft
runningcode wants to merge 4 commits into
Draft
ref(android): Measure ANR thresholds on the monotonic 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
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
runningcode
force-pushed
the
no/java-576-checkin-stopwatch
branch
from
September 3, 2026 15:11
9829cc8 to
c11fec4
Compare
runningcode
force-pushed
the
no/java-579-anr-uptime-clock
branch
from
September 3, 2026 15:11
2065803 to
498932f
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 named no clock, so a call site could not tell what it was measuring, and the arithmetic -- now minus the last tick, compared against a threshold -- was spelled out inline. MonotonicClock and Deadline replace both: the clock is a named type, and the watchdog asks the question it actually cares about, which is whether the main thread has missed its window. The clock counts deep sleep, which uptimeMillis() did not, so a suspend between posting the ticker and checking it now looks like a missed window. It cannot fabricate an ANR: the watchdog reports only once ActivityManager confirms the process is NOT_RESPONDING, and on resume the main thread runs the ticker that is already queued.
Same reasoning as the watchdog: the suspicion and ANR thresholds are now read from a named clock rather than SystemClock, and injecting it lets the tests drive it directly instead of going through Robolectric's shadow clock. Deep sleep cannot inflate the measurement here either, because the polling thread parks itself while the app is backgrounded and resets the baseline when it wakes.
runningcode
force-pushed
the
no/java-576-checkin-stopwatch
branch
from
September 3, 2026 15:41
c11fec4 to
0d1d4c9
Compare
runningcode
force-pushed
the
no/java-579-anr-uptime-clock
branch
from
September 3, 2026 15:41
498932f to
ae9d046
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 theMonotonicClockinterface from #6028.ANRWatchDogtakes aMonotonicClockinstead of anICurrentDateProviderlambda, and tracks themain thread's responsiveness window as a
Deadlinerather than alastKnownActiveUiTimestampMslong plus subtraction.
AnrProfilingIntegrationreads its suspicion and ANR thresholds from an injectedMonotonicClockinstead of calling
SystemClockinline, and times stack capture with aStopwatch.TestMonotonicClock's tick is now@Volatile, because these tests advance the clock from the testthread while the watchdog thread reads it.
Both resolve
AndroidMonotonicClock.getInstance(), so the readings move fromSystemClock.uptimeMillis()toSystemClock.elapsedRealtimeNanos()— millisecond to nanosecondprecision, and deep sleep now counted. Nothing serialized is touched; these values only decide
whether a poll trips a threshold.
💡 Motivation and Context
The old
ICurrentDateProviderfield type named no clock, so nothing at the call site said what wasbeing measured, and the threshold arithmetic — now minus the last tick, compared against a constant
— was spelled out inline. A named clock plus
Deadlinereplaces both.Why counting deep sleep is safe here. The concern with a boot-relative clock is that a suspend
between posting the ticker and checking it looks like a frozen main thread. Neither detector can
turn that into a reported ANR:
ANRWatchDograises nothing untilActivityManager.getProcessesInErrorState()reports the processas
NOT_RESPONDING, and on resume the main thread runs the ticker that is already queued.AnrProfilingIntegrationparks its polling thread onwait()while the app is backgrounded andruns its updater again on wake, so a suspend outside the foreground is not measured at all.
The watchdog is also not used for background ANRs, which is where a long suspend would otherwise be
expected.
💚 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.