ref(android): Measure ANR thresholds on the monotonic clock (JAVA-579) - #6041
ref(android): Measure ANR thresholds on the monotonic clock (JAVA-579)#6041runningcode wants to merge 6 commits into
Conversation
📲 Install BuildsAndroid
|
edd22f0 to
fef63c7
Compare
cabb812 to
77eb8c9
Compare
fef63c7 to
9829cc8
Compare
77eb8c9 to
2065803
Compare
9829cc8 to
c11fec4
Compare
2065803 to
498932f
Compare
c11fec4 to
0d1d4c9
Compare
498932f to
ae9d046
Compare
0d1d4c9 to
6861150
Compare
ae9d046 to
46f3c83
Compare
6861150 to
b35959c
Compare
46f3c83 to
2c9adb8
Compare
b35959c to
1eb13ab
Compare
2c9adb8 to
d570570
Compare
1eb13ab to
5b8105e
Compare
d570570 to
51e0083
Compare
5b8105e to
67379d3
Compare
51e0083 to
a2620aa
Compare
97ad063 to
76d5e07
Compare
| private final boolean reportInDebug; | ||
| private final ANRListener anrListener; | ||
| private final MainLooperHandler uiHandler; | ||
| private final ICurrentDateProvider timeProvider; |
There was a problem hiding this comment.
This returns EITHER a monotonic clock or a wall clock depending on the platform. This is a bad abstraction.
On android it gives you a monotonic clock which is what we want for this class, but the fact that it has Date in the name but gives you a monotonic clock is quite confusing.
The ANR tests advance the ticker 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.
…A-579) Both ANR detectors resolved AndroidMonotonicTicker.getInstance() themselves, so each call site named the concrete Android ticker and neither could be pointed at another one. SentryAndroidOptions already knows which ticker the platform wants, but it is final, so a test cannot override the getter. Add a one-method MonotonicTickerProvider that SentryOptions implements. ANRWatchDog gains a static factory that reads its timeout, debug behavior, logger and ticker off options, replacing the telescoping convenience constructor. AnrProfilingIntegration installs the ticker in register(), which drops the two constructors it had grown and leaves the public no-arg constructor and sentry-android-core.api unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drop the JavaMonotonicTicker fallback. On Android nanoTime() stops while the device is suspended, so a default that is never meant to be used would measure main-thread stalls against the wrong clock if it ever were. Start the test ticker at a non-zero origin. A fake that starts at zero makes a reading taken against a different origin indistinguishable from a correct one, which is the mistake this whole change exists to prevent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
50973fd to
fd94017
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fd94017. Configure here.
| reportInDebug, | ||
| return new ANRWatchDog( | ||
| options.getMonotonicTicker(), | ||
| options.getAnrTimeoutIntervalMillis(), |
There was a problem hiding this comment.
ANR clock includes device sleep
Medium Severity
ANR thresholds now come from MonotonicTicker, which on Android is elapsedRealtimeNanos and keeps counting through suspend. ANRWatchDog still polls while backgrounded, so a device sleep can expire uiResponsiveUntil and look like a frozen main thread. The old uptimeMillis path did not include sleep.
Additional Locations (2)
Triggered by project rule: PR Review Guidelines for Cursor Bugbot
Reviewed by Cursor Bugbot for commit fd94017. Configure here.


📜 Description
Moves the ANR integrations to use the
MonotonicTickerabstraction. This also makes them more testable since we aren't hard coding a direct access toSystemClock.A note: We previously used
SystemClock.uptimeMilliswhich does not include time spent in sleep. The implementation ofMonotonicTickeron Android usesSystemClock.elapsedRealtimeNanos(). Since we aren't using these to measure background ANRs, nothing changes.Also
ANRWatchdogwas previously using aICurrentDateProvider. Which has two different implementations, a wall clock and a monotonic clock. It happens to work by chance on Android because on Android it should always return a monotonic clock but this is quite brittle.💡 Motivation and Context
Fixing clocks and making the code more sane.
💚 How did you test it?
./gradlew :sentry-android-core:testReleaseUnitTest :sentry-android-core:apiCheck— green.📝 Checklist
sendDefaultPIIis enabled.