Skip to content

fix(core): Mark transactions that outlive their deadline as deadline_exceeded (JAVA-642) - #6599

Draft
runningcode wants to merge 5 commits into
mainfrom
no/java-642-deadline-after-sleep
Draft

runningcode wants to merge 5 commits into
mainfrom
no/java-642-deadline-after-sleep

Conversation

@runningcode

@runningcode runningcode commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

📜 Description

After an Android device sleeps, or after the process is frozen, ui.load transactions can arrive with TTID, TTFD and app start values that are hours long, most of them with status ok.

The deadline timer counts on System.nanoTime(), which stops while the device is suspended. After Doze, the timer still has time left when the app wakes up; after a freeze, it is overdue and races the main thread. Either way, the first frame can finish the TTID, TTFD and app start spans before the timer runs, and the idle timeout then finishes the transaction as ok.

This keeps every timestamp, duration and measurement as it is, and changes only the status. SentryTracer also measures its deadline with a Deadline on options.getMonotonicTicker(), which counts suspend on Android. Once that deadline has passed, the transaction and any span that finishes after it get the status the deadline timer gives: the transaction's own status, or DEADLINE_EXCEEDED if it has none. The rule is in Span.finish, which the transaction's root span and every child go through. That covers TTID, TTFD, the app start, network spans, the idle timeout, finish(...), forceFinish(...) and app code. Nothing changes before the deadline.

The point is that span.status:ok then filters these transactions out. Closed #5755 dropped late transactions instead; this keeps them.

Side effect: AppStartExtension.getExtendedEndTime() ignores an extended app start span with status DEADLINE_EXCEEDED, so the extended app start vital is suppressed when that span finishes after the deadline.

No .api changes.

💡 Motivation and Context

In one customer's data, 84% of the ui.load transactions over 60s were ok with this signature, so filtering on span.status:ok didn't remove them.

💚 How did you test it?

Unit tests in SentryTracerTest and ActivityLifecycleIntegrationTest use TestMonotonicTicker to pass the deadline while the timer hasn't run. Each checks that timestamps and measurements match main and that only the status changes. Each fails on main, except the two that check nothing changes before the deadline. Not tried on a device.

📝 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

  • The standalone app start transaction (enableStandaloneAppStartTracing) has no deadline, so a first frame drawn after a sleep still makes it hours long with status ok.
  • SentryTracer can capture a transaction twice when two threads finish it at the same moment, for example the deadline timer and onActivityDestroyed. That race is already on main.

🤖 Generated with Claude Code

runningcode and others added 2 commits November 12, 2026 14:57
…JAVA-642)

The deadline timer counts on System.nanoTime(), which stops while an
Android device is suspended. It ran hours late, and after a process
freeze it raced the main thread, so ui.load transactions ended hours
after the deadline, or measured TTID, TTFD and the app start to a late
frame and finished as ok.

SentryTracer now measures the deadline on the monotonic ticker, which
counts suspend, and ends the transaction and its open spans at the wall
time the deadline was reached. Once it has passed, every way of
finishing the transaction takes that outcome. ActivityLifecycleIntegration
applies it before handling a late first frame, full display or destroy,
and PerformanceAndroidEventProcessor adds no app start measurement when
the app start span was cut short.

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

linear-code Bot commented Nov 12, 2026

Copy link
Copy Markdown

JAVA-642

@sentry

sentry Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.64.0 (1) release

⚙️ sentry-android Build Distribution Settings

runningcode and others added 3 commits November 12, 2026 15:24
Starting a span after the deadline finished and captured the whole
transaction on the thread that started the span, which may hold a lock.
Return the no-op span only: the overdue deadline timer ends the
transaction at the deadline right after.

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

Timings now stay exactly as before: the deadline timer ends spans at
now() again, spans started after the deadline are recorded, and the
ActivityLifecycleIntegration and PerformanceAndroidEventProcessor
changes are reverted.

Instead, once the monotonic-ticker deadline has passed, Span.finish
gives the transaction and any span that finishes after it the status
the deadline timer gives: the transaction's own status, or
DEADLINE_EXCEEDED. Late frames after a device sleep then no longer
report ok, so span.status:ok filters them out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@runningcode runningcode changed the title fix(core): End transactions at the deadline after the device sleeps (JAVA-642) fix(core): Mark transactions that outlive their deadline as deadline_exceeded (JAVA-642) Nov 12, 2026

This branch has not been deployed

No deployments
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.

Incorrect app start transaction duration when app goes in sleep

1 participant