Skip to content

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

Draft
runningcode wants to merge 4 commits into
no/java-576-checkin-stopwatchfrom
no/java-579-anr-uptime-clock
Draft

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

Conversation

@runningcode

@runningcode runningcode commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

PR Stack (Clock semantics hardening)


📜 Description

Moves both ANR detectors off SystemClock.uptimeMillis() readings behind an untyped
ICurrentDateProvider and onto the MonotonicClock interface from #6028.

  • ANRWatchDog takes a MonotonicClock instead of an ICurrentDateProvider lambda, and tracks the
    main thread's responsiveness window as a Deadline rather than a lastKnownActiveUiTimestampMs
    long plus subtraction.
  • AnrProfilingIntegration reads its suspicion and ANR thresholds from an injected MonotonicClock
    instead of calling SystemClock inline, and times stack capture with a Stopwatch.
  • TestMonotonicClock's tick is now @Volatile, because these tests advance the clock from the test
    thread while the watchdog thread reads it.

Both resolve AndroidMonotonicClock.getInstance(), so the readings move from
SystemClock.uptimeMillis() to SystemClock.elapsedRealtimeNanos() — millisecond to nanosecond
precision, and deep sleep now counted. Nothing serialized is touched; these values only decide
whether a poll trips a threshold.

💡 Motivation and Context

The old ICurrentDateProvider field type named no clock, so nothing at the call site said what was
being measured, and the threshold arithmetic — now minus the last tick, compared against a constant
— was spelled out inline. A named clock plus Deadline replaces 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:

  • ANRWatchDog raises nothing until ActivityManager.getProcessesInErrorState() reports the process
    as NOT_RESPONDING, and on resume the main thread runs the ticker that is already queued.
  • AnrProfilingIntegration parks its polling thread on wait() while the app is backgrounded and
    runs 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.

  • resolves: JAVA-579

💚 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 for
hundreds 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 detected test, advances the same clock and does report one.

The AnrProfilingIntegration state-machine tests now drive the injected clock instead of
Robolectric's shadow SystemClock, asserting the same IDLE → SUSPICIOUS → ANR_DETECTED transitions
at the same offsets.

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

🔮 Next steps

PR 6 of the stack deprecates the legacy ICurrentDateProvider family and the date-reading helpers
now that the internal consumers have moved off them.

⚠️ Merge this PR using a merge commit (not squash), so the rest of the stack keeps a clean history.

@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.54.0 (1) release

⚙️ sentry-android Build Distribution Settings

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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant