Skip to content

ref(android): Measure ANR thresholds on the monotonic clock (JAVA-579) - #6041

Open
runningcode wants to merge 6 commits into
mainfrom
no/java-579-anr-uptime-clock
Open

ref(android): Measure ANR thresholds on the monotonic clock (JAVA-579)#6041
runningcode wants to merge 6 commits into
mainfrom
no/java-579-anr-uptime-clock

Conversation

@runningcode

@runningcode runningcode commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📜 Description

Moves the ANR integrations to use the MonotonicTicker abstraction. This also makes them more testable since we aren't hard coding a direct access to SystemClock.

A note: We previously used SystemClock.uptimeMillis which does not include time spent in sleep. The implementation of MonotonicTicker on Android uses SystemClock.elapsedRealtimeNanos(). Since we aren't using these to measure background ANRs, nothing changes.

Also ANRWatchdog was previously using a ICurrentDateProvider. 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.

  • resolves: JAVA-579

💚 How did you test it?

./gradlew :sentry-android-core:testReleaseUnitTest :sentry-android-core:apiCheck — green.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

@linear-code

linear-code Bot commented Sep 2, 2026

Copy link
Copy Markdown

JAVA-579

@sentry

sentry Bot commented Sep 2, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.56.0 (1) release

⚙️ sentry-android Build Distribution Settings

@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from edd22f0 to fef63c7 Compare September 2, 2026 13:35
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from cabb812 to 77eb8c9 Compare September 2, 2026 13:35
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from fef63c7 to 9829cc8 Compare September 2, 2026 13:37
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 77eb8c9 to 2065803 Compare September 2, 2026 13:37
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from 9829cc8 to c11fec4 Compare September 3, 2026 15:11
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 2065803 to 498932f Compare September 3, 2026 15:11
@runningcode runningcode changed the title ref(android): Measure ANR thresholds on the uptime clock (JAVA-579) ref(android): Measure ANR thresholds on the monotonic clock (JAVA-579) Sep 3, 2026
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from c11fec4 to 0d1d4c9 Compare September 3, 2026 15:41
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 498932f to ae9d046 Compare September 3, 2026 15:41
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from 0d1d4c9 to 6861150 Compare September 4, 2026 08:57
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from ae9d046 to 46f3c83 Compare September 4, 2026 08:58
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from 6861150 to b35959c Compare September 4, 2026 15:11
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 46f3c83 to 2c9adb8 Compare September 4, 2026 15:11
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from b35959c to 1eb13ab Compare September 8, 2026 13:27
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 2c9adb8 to d570570 Compare September 8, 2026 13:32
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from 1eb13ab to 5b8105e Compare September 8, 2026 15:10
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from d570570 to 51e0083 Compare September 8, 2026 15:10
@runningcode
runningcode force-pushed the no/java-576-checkin-stopwatch branch from 5b8105e to 67379d3 Compare September 9, 2026 15:44
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 51e0083 to a2620aa Compare September 9, 2026 15:51
@runningcode
runningcode changed the base branch from no/java-576-checkin-stopwatch to main September 9, 2026 15:51
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 97ad063 to 76d5e07 Compare September 10, 2026 07:25
private final boolean reportInDebug;
private final ANRListener anrListener;
private final MainLooperHandler uiHandler;
private final ICurrentDateProvider timeProvider;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

runningcode and others added 6 commits September 11, 2026 17:17
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>
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 50973fd to fd94017 Compare September 11, 2026 15:19
@runningcode
runningcode marked this pull request as ready for review September 11, 2026 15:21

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bugbot

Reviewed by Cursor Bugbot for commit fd94017. Configure here.

@runningcode runningcode added the sanity-check PR needs a lightweight review for obvious issues label Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sanity-check PR needs a lightweight review for obvious issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant