Repository navigation
Description
The project plan holds back every breaking clock change until v9. No other open issue in this project tracks those changes. This one does.
Each item below is breaking. It either changes a critical customer metric or breaks the public API.
1. Span timing on one AnchoredClock per transaction
Today each span reads the wall clock for its own start (Span calls options.getDateProvider().now()). Its end is that start plus a System.nanoTime() difference. So the starts of two spans are two separate wall-clock readings. If the wall clock steps between them, a child span can start before its parent.
The fix is the design that #6045 shipped as API only. Create one AnchoredClock per transaction in SentryTracer, and project every span timestamp from it. Nothing calls AnchoredClock yet.
Why it's breaking:
- Durations start counting deep sleep on Android. The ticker there is
CLOCK_BOOTTIME.System.nanoTime()isCLOCK_MONOTONIC, which stops in deep sleep. Any span or transaction that crosses a deep sleep gets longer. This matches iOS, which is a project goal, but it changes span and transaction durations customers chart and alert on. - Child span
start_timestamps move by less than a millisecond. Root spans don't change.
Two more sites move with this:
SpanFrameMetricsCollector.toNanoTime()reads the wall-to-nanoTimeoffset again each time a span finishes (Remaining low-priority clock issues and clock-step regression coverage #5582 §C5). Draft fix(android): Anchor the frame-metrics time projection (JAVA-579) #6098 anchors it once at SDK init. Fold that fix in here instead of landing it on main (decided 2026-11-12), because it changes the slow, frozen and delayed frame counts on app-start spans.sentry-android-sqlite'sDriverSpans.computeNanoStartTimestampForChild()builds a child's start from its parent'sSentryNanotimeDateplusSystem.nanoTime(). That is the same anchor, built by hand.
2. Decide whether app start follows
TimeSpan runs on SystemClock.uptimeMillis(), which also stops in deep sleep. It drives the app start, process init, Application.onCreate, content provider and activity lifecycle spans.
Open question: should app start count deep sleep too? #5752 argued that launches with a Doze in the middle don't reflect what users actually experience. If app start does switch, it's breaking twice:
- Metric: a launch where the device sleeps before the first frame would include the sleep instead of leaving it out, so app-start p95/p99 go up. Incorrect app start transaction duration when app goes in sleep #5752 was closed without an SDK change, because these launches are filtered out in the dashboard instead.
- API:
TimeSpanis insentry-android-core.api. Its methods are named for uptime (setStartedAt(uptimeMs),getStartUptimeMs()), andInternalSentrySdkserializes it for the hybrid SDKs.
#6104's AndroidEnvelopeCache call site doesn't depend on this decision. It can take its own boottime reading at SDK init.
3. Remove ICurrentDateProvider
ICurrentDateProviderandCurrentDateProviderare@ApiStatus.Internalbut listed insentry.api. Neither is deprecated yet.- The public
ReplayIntegrationconstructors take anICurrentDateProvider. The sentry-dart Android plugin calls the four-argument constructor withCurrentDateProvider.getInstance(), so removing it breaks Flutter. Coordinate the change with sentry-dart. AndroidCurrentDateProvideris internal and not in the.apifile (deprecated by Deprecate AndroidCurrentDateProvider in favor of MonotonicTicker #6102). Delete it once Clean up the remaining AndroidCurrentDateProvider usages #6104 has moved its callers.RateLimiter(ICurrentDateProvider, SentryOptions)is public API too (sentry.api). Remove it along with the interface.- Remove the members that Deprecate the legacy clock and date-provider APIs #6586 deprecates on main.
Other v9 clock tickets
- End a backgrounded session when its timeout ran out, not when the device wakes #6585: end a backgrounded session when its timeout ran out, not when the device wakes.
- Session seq is raw epoch millis; duration is masked with Math.abs #5580: session
seqand theMath.abssession duration.
Done when
- Spans and transactions take their timestamps from one
AnchoredClockper transaction. - The app-start decision is made, and either implemented or explicitly deferred.
ICurrentDateProvideris gone fromsentry.api, and sentry-dart has been updated.- The v9 changelog lists each change.
Agent notes
Verified on main at 1b8668ae2b (2026-11-06).
- Span start:
sentry/src/main/java/io/sentry/Span.java:68,:86. Span end:SentryNanotimeDate.laterDateNanosTimestampByDiff, serialized inprotocol/SentrySpan.java:73-75andprotocol/SentryTransaction.java:62-68. - Android date provider:
SentryAndroidDateProvider→SentryNanotimeDate(System.currentTimeMillis()+System.nanoTime()). - Ticker:
AndroidMonotonicTicker.tickNanos()=SystemClock.elapsedRealtimeNanos(). Epoch:SentryOptions.getEpochClock(). AnchoredClocklives insentry/src/main/java/io/sentry/time/AnchoredClock.java. Nothing outsideio.sentry.timeuses it.- Constraint for item 1:
Choreographerframe timestamps are onCLOCK_MONOTONIC.SpanFrameMetricsCollectorhas to convert between the two clocks instead of subtracting across them. TimeSpan:sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java. Serialized for hybrid SDKs inInternalSentrySdk.java:366-370.- sentry-dart caller:
packages/flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt, around line 293. SpanFrameMetricsCollector.java:317-330(toNanoTime). fix(android): Anchor the frame-metrics time projection (JAVA-579) #6098 is the draft fix.DriverSpans.kt:112-116(computeNanoStartTimestampForChild).RateLimiterpublic constructor:sentry/api/sentry.api:7832.- The three items above were checked on
mainat812682636b(2026-11-12).
Activity
linear-code commentedon Nov 6, 2026
JAVA-827