ref(core): Measure two wall-clock TTLs on a monotonic ticker (JAVA-579) - #6099
Closed
runningcode wants to merge 2 commits into
Closed
ref(core): Measure two wall-clock TTLs on a monotonic ticker (JAVA-579)#6099runningcode wants to merge 2 commits into
runningcode wants to merge 2 commits into
Conversation
Both of these asked the wall clock how much time had passed, so a device time change lengthened or shortened them: - HostnameCache kept an absolute expiry built from currentTimeMillis, so a backward step extended the 5h TTL by the size of the step and a forward step expired it early. - DefaultCompositePerformanceCollector decided a transaction had been collecting for 30s by subtracting two dateProvider readings, which are wall-clock on every platform: SentryNanotimeDate.nanoTimestamp() returns its unix millis, not its nanoTime component. Both now hold a Deadline on a MonotonicTicker. Neither value is serialized, so this changes only when the SDK stops waiting. Two side effects worth noting for review: the 30s boundary is now inclusive, where the old strict `>` let a sample land exactly at 30s; and addDataAndCheckTimeout no longer takes the shared clock reading, since each transaction owns its own deadline. TestMonotonicTicker's field is now volatile, as the collector's timer thread reads a ticker the test thread advances. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Contributor
Author
|
Split into two PRs, since
Same changes, same tests; the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
Audit finding §C6 lists four wall-clock TTL/cleanup sites. Two of them are genuine interval measurements and are fixed here; the other two turn out to have no monotonic remedy, and I explain why below rather than leave them looking forgotten.
Fixed —
HostnameCache5h TTL. It stored an absolute expiry built fromSystem.currentTimeMillis()and compared it against a fresh reading. A backward clock step extended the TTL by the size of the step; a forward step expired the cache early. Now aDeadlineon aMonotonicTicker. The field also no longer starts at0, which on a boot-relative ticker reads as "freshly set" rather than "unset" — it starts asDeadline.passed, so nothing is treated as cached before the first resolve.Fixed —
DefaultCompositePerformanceCollector30s auto-stop. It decided a transaction had been collecting for 30 seconds by subtracting twooptions.getDateProvider()readings. Those are wall-clock on every platform, including Android:SentryNanotimeDate.nanoTimestamp()returnsmillisToNanos(unixDateMillis), not itsnanoTimecomponent, so thenanoTimeprecision that type exists for never entered this comparison. Now aDeadlineperCompositeData.Not fixable — the two file-mtime sites. Both compare against
File.lastModified():Sentry.java,f.lastModified() < classCreationTimestamp - 5minCacheStrategy,Arrays.sort(files, comparing lastModified())A filesystem mtime is a wall-clock value, recorded by a different process at a time we never observed. There is no monotonic quantity to compare it against, so moving either of these to a ticker is not possible — it would mean comparing a tick to an mtime, which is worse than what is there now. Fixing them properly would mean not relying on mtimes at all (writing our own timestamp alongside each file), which is a storage-format change and well outside this item. Leaving them as they are.
Neither fixed value is serialized — they only decide when the SDK stops waiting — so this is not gated behind the v9 work.
💡 Motivation and Context
Audit finding §C6 from the clock-usage audit.
💚 How did you test it?
HostnameCacheTest: the hostname is re-resolved once the cache duration has elapsed and not before, driven by advancing aTestMonotonicTickerrather than by sleeping for five hours. This path had no test at all previously, because it was not reachable without a real clock.DefaultCompositePerformanceCollectorTestnow advance a ticker instead of stubbingdateProvider.now()with a positional sequence of four return values (thenReturn(dates[0], dates[0], dates[0], dates[1])), which was brittle against any change in how often the date provider gets called.:sentrymodule suite: 249 suites, 3548 tests, 0 failures.:sentry-android-coreSpanFrameMetricsCollectorTestandMainEventProcessorTest(the otherHostnameCacheconsumer) also pass.📝 Checklist
sendDefaultPIIis enabled.Two behavior details worth a reviewer's eye:
Deadline.hasPassed()is>=, where the old comparison was a strict>. A sample landing exactly on 30.000s now ends collection instead of being kept.addDataAndCheckTimeoutno longer takes a sharednowNanos. That parameter existed so one clock reading was shared across every transaction in a collection round; with a per-transactionDeadlinethere is nothing to share, and the nanosecond spread across one loop cannot matter to a 30-second budget. Say the word if you would rather keep the shared reading.TestMonotonicTicker's backing field is now@Volatile, because the collector's timer thread reads a ticker that the test thread advances.🔮 Next steps
§C6 also mentions the 100ms sampling loop running on
java.util.Timer, whose deadlines are wall-clock and whoseObject.wait()does not progress in Android deep sleep. That is a scheduling change rather than a TTL one, it needs either a periodic API onISentryExecutorServiceor a self-rescheduling task, and six tests inDefaultCompositePerformanceCollectorTestassert directly against an injected mockTimer. It deserves its own PR.🤖 Generated with Claude Code