Skip to content

ref(core): Hold the hostname cache on SentryOptions (JAVA-579) - #6117

Draft
runningcode wants to merge 4 commits into
no/java-579-c6-hostname-cache-ttlfrom
no/java-579-c6b-hostname-cache-on-options
Draft

runningcode wants to merge 4 commits into
no/java-579-c6-hostname-cache-ttlfrom
no/java-579-c6b-hostname-cache-on-options

Conversation

@runningcode

Copy link
Copy Markdown
Contributor

📜 Description

HostnameCache was a process-wide static singleton (HostnameCache.getInstance()) whose private constructor reached for JavaMonotonicTicker.getInstance() directly.

SentryOptions now holds one instance, built from getMonotonicTicker() the same way RateLimiter(SentryOptions) is. It is wrapped in a LazyEvaluator because resolving the hostname blocks on InetAddress.getLocalHost(), which no Sentry.init should pay for up front — deferring also means the SentryAndroidOptions ticker override is in effect by the time the ticker is read.

MainEventProcessor still closes the cache, and now drops both its own reference and the options-held one. Both the processor and the options survive Scopes.close(isRestarting = true), so a closed cache left in either place would never refresh the hostname again.

MetricsApi and LoggerApi read the cache off the options they already hold.

Stacked on #6100, which this depends on for the HostnameCache(Callable, MonotonicTicker) constructor. It targets that branch so the diff here is just this change; GitHub will retarget to main once #6100 merges.

💡 Motivation and Context

Hard-coding JavaMonotonicTicker meant the hostname cache measured its 5-hour TTL on System.nanoTime() even where options supply an elapsedRealtimeNanos()-backed ticker, so it ignored time the device spent in deep sleep. This was the last place in the SDK hard-coding a ticker instead of taking one from options. Android sets attachServerName = false by default, so the wrong-clock behavior was latent rather than actively broken.

Removing the singleton also let MainEventProcessorTest drop a Mockito.mockStatic(HostnameCache::class.java) hack in favor of a plain setter.

  • resolves: JAVA-579

💚 How did you test it?

:sentry:test and :sentry:apiCheck. New SentryOptionsTest cases cover the lazy construction (asserting that constructing options does not resolve the hostname), instance reuse, resetHostnameCache(), and that the cache takes its ticker from getMonotonicTicker().

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

getHostnameCache(), setHostnameCache() and resetHostnameCache() are @ApiStatus.Internal, and the removed HostnameCache.getInstance() was on an @ApiStatus.Internal class — so sentry.api changes without a public contract change.

🔮 Next steps

None.

🤖 Generated with Claude Code

HostnameCache was a process-wide static singleton whose constructor reached
for JavaMonotonicTicker.getInstance() directly. That was the last place in the
SDK hard-coding a ticker rather than taking one from options, so the cache
measured its TTL on System.nanoTime() even where options supply an
elapsedRealtimeNanos()-backed ticker that counts deep sleep.

SentryOptions now holds one instance, built from getMonotonicTicker() the same
way RateLimiter(SentryOptions) is. It is wrapped in a LazyEvaluator because
resolving the hostname blocks on InetAddress.getLocalHost(), which no
Sentry.init should pay for up front; deferring also means the
SentryAndroidOptions override is in effect by the time the ticker is read.

MainEventProcessor still closes the cache, and now drops both its own
reference and the options-held one. Both it and the options outlive
Scopes.close(isRestarting = true), so a closed cache left in either place
would never refresh the hostname again.

MainEventProcessorTest no longer needs Mockito.mockStatic to intercept
getInstance(); it sets the cache on options instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 15, 2026

Copy link
Copy Markdown

JAVA-579

@sentry

sentry Bot commented Sep 15, 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 and others added 2 commits September 15, 2026 17:02
HostnameCache reads exactly one collaborator, so it now takes a
MonotonicTicker rather than a SentryOptions it would only call
getMonotonicTicker() on. This follows
RateLimiter.create(MonotonicTicker, RateLimiterConfig), which names the
collaborators a rate limiter actually reads for the same reason; a config
interface is unnecessary here because there is only the one.

Also drops SentryOptions.setHostnameCache(), which had no production caller.
Unlike setDateProvider(), which AndroidOptionsInitializer uses, it was only a
test seam, so MainEventProcessorTest overrides getHostnameCache() instead —
the way CheckInUtilsTest already overrides getMonotonicTicker().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cache's executor is a single daemon thread with allowCoreThreadTimeOut(true)
and a 30 second keep-alive, so it self-terminates once idle and never holds up
process exit. close() was switching off something that switches itself off.
Scopes.close() already leaves the timer executor running for this reason.

Dropping it removes the reason SentryOptions.resetHostnameCache() existed.
That method was only there because MainEventProcessor closed a cache the
options own, which a re-init with the same options object would then keep
handing out shut down. Nothing closes the cache now, so nothing has to undo it.

MainEventProcessor no longer implements Closeable, and no longer memoizes the
cache either: the options build it on first use, so the field and
ensureHostnameCache() were duplicating LazyEvaluator. Both removed methods are
on @ApiStatus.Internal types.

SentryClientTest's `when client is closed, hostname cache is closed` went with
them; it asserted isClosed() on a processor that had never resolved a hostname,
which returned true because the cache was still null.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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