Skip to content

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

Merged
runningcode merged 9 commits into
mainfrom
no/java-579-anr-uptime-clock
Nov 6, 2026
Merged

runningcode merged 9 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(). ANRWatchDog polls in the background too, so right after the device wakes up the deadline can already have passed. That doesn't produce a false ANR: the watchdog only reports when ActivityManager also lists the process as NOT_RESPONDING. The cost is one extra getProcessesInErrorState() call after a wake-up. AnrProfilingIntegration only polls in the foreground and resets its reading on foreground.

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:checkKotlinAbi — 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.63.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
@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.

Stale Bugbot comment from a previous run.

@runningcode runningcode added the sanity-check PR needs a lightweight review for obvious issues label Sep 11, 2026
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from fd94017 to 88946b3 Compare November 2, 2026 13:06
@runningcode
runningcode requested a review from a team as a code owner November 2, 2026 13:06

@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.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4a11cb6. Configure here.

runningcode and others added 8 commits November 6, 2026 12:44
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>
MonotonicTickerProvider only existed so a test could hand
AnrProfilingIntegration a fake ticker. A package-private method taking
the ticker does the same without a new core interface or a change to
the SentryOptions supertype.

Also rename the ANRWatchDog test that claimed to cover a device suspend:
the Android ticker counts suspend, so the test only shows the threshold
is measured on the injected ticker rather than wall time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests spy SentryAndroidOptions to supply a TestMonotonicTicker, so the
package-private installTicker seam is no longer needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@runningcode
runningcode force-pushed the no/java-579-anr-uptime-clock branch from 84203d1 to f05669b Compare November 6, 2026 11:48
… (JAVA-579)

Error Prone does not run on sentry-android-core, so the suppression had no effect. The lambda
field stays: removeCallbacks matches the posted Runnable by identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@markushi markushi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@runningcode
runningcode merged commit 3d1f55e into main Nov 6, 2026
43 of 44 checks passed
@runningcode
runningcode deleted the no/java-579-anr-uptime-clock branch November 6, 2026 12:22
runningcode added a commit that referenced this pull request Nov 10, 2026
9.x.x left ANRWatchDog.monotonicTicker (#6041) and AndroidContinuousProfiler.buildInfoProvider (#6518)
without readers, which Error Prone reports as UnusedVariable. The constructors keep their
parameters, so their signatures don't change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
runningcode added a commit that referenced this pull request Nov 10, 2026
- ANRWatchDog.monotonicTicker is never read (#6041), since the ticker lambda captures the
  constructor parameter. Remove the field and keep the parameter.
- ActivityFramesTracker.lock is protected in a final class; make it package-private.
- LifecycleWatcher reads the session start Date to seed the session window; suppress JavaUtilDate
  there, as the other Date uses are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
runningcode added a commit that referenced this pull request Nov 11, 2026
* ref(android): Fix Error Prone warnings in sentry-android-core

Error Prone never ran on sentry-android-core. Enabling it reports 59 warnings outside Javadoc,
which fail the build under -Werror.

- Suppress FutureReturnValueIgnored, JavaUtilDate, and ReferenceEquality where identity is
  intended, as the sentry module does.
- Keep the AnrProfilingIntegration updater a field, since removeCallbacks needs the instance that
  was posted, and suppress UnnecessaryLambda there.
- Read the ANR thread dump and the app start profiling config as UTF-8, Android's default charset.
- Remove unused fields and parameters, add missing @OverRide, make long-to-double conversions
  explicit, and narrow members that are effectively private or protected in a final class.
- Explain the two catch blocks that ignore their exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ref(android): Fix the Error Prone warnings only main has

- ANRWatchDog.monotonicTicker is never read (#6041), since the ticker lambda captures the
  constructor parameter. Remove the field and keep the parameter.
- ActivityFramesTracker.lock is protected in a final class; make it package-private.
- LifecycleWatcher reads the session start Date to seed the session window; suppress JavaUtilDate
  there, as the other Date uses are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ref(android): Keep the default charset reads as they were

Android's default charset is always UTF-8, so DefaultCharset is
disabled for the Android modules in #6493 instead of being fixed here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ref(android): Drop the UnnecessaryLambda suppressions

#6493 disables UnnecessaryLambda for the Android modules, so the
suppressions on the ANRWatchDog and AnrProfilingIntegration fields
are redundant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ref(android): Drop the updater field comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(android): Make the local-class method comment a plain comment

Error Prone's NotJavadoc flags Javadoc on methods of local classes, which
#6537 added in AndroidConnectionStatusProvider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants