Skip to content

fix(android): Read the app start type once per event processor pass - #6006

Draft
runningcode wants to merge 2 commits into
mainfrom
no/app-start-type-single-read
Draft

fix(android): Read the app start type once per event processor pass#6006
runningcode wants to merge 2 commits into
mainfrom
no/app-start-type-single-read

Conversation

@runningcode

Copy link
Copy Markdown
Contributor

📜 Description

PerformanceAndroidEventProcessor.process() read AppStartMetrics.getAppStartType() three separate times while building a single transaction:

  1. to pick the measurement key (app_start_cold / app_start_warm)
  2. in attachAppStartSpans to decide whether to attach the cold-start breakdown children
  3. for contexts.app.start_type — which feat(android): Set app start vitals on standalone app start children #6005 now also stamps onto the trace context and every child span as app.vitals.start.type

This snapshots the type once at the top of process() and passes it down, and makes AppStartMetrics.appStartType volatile.

💡 Motivation and Context

appStartType is written on the main thread (activity lifecycle callbacks, the ApplicationStartInfo block) and read by the event processor, which runs on whichever thread finished the transaction — SentryTracer.finish() calls captureTransaction() synchronously, so for idle and deadline timeouts that is the Sentry timer thread, not the main thread. The field was neither volatile nor guarded, so the read was a plain data race.

Beyond the JMM issue, the repeated reads can genuinely disagree. onActivityDestroyed sets appStartType = WARM when the last activity goes away, so backgrounding the app while a transaction finishes on the timer thread can produce a transaction whose measurement says app_start_cold while contexts.app.start_type and every span's app.vitals.start.type say warm. Since #6005 that value is the grouping key for the whole mobile vitals app start breakdown, so a mismatch splits the breakdown rather than just mislabelling one field.

💚 How did you test it?

Two tests in PerformanceAndroidEventProcessorTest asserting that the measurement key, contexts.app.start_type, the trace context and every span agree, for cold and warm standalone app starts. Full :sentry-android-core:testReleaseUnitTest passes.

Note that the race itself is not deterministically unit-testable — there is no seam to flip the type between the reads inside a single process() call — so the tests lock in the invariant rather than reproducing the interleaving.

📝 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

Follow-up to #6005. See also the companion PR removing the AppStartExtension lock-ordering deadlock.

runningcode and others added 2 commits August 26, 2026 15:34
PerformanceAndroidEventProcessor read AppStartMetrics.getAppStartType()
three separate times while building a single transaction: once for the
measurement key, once to decide whether to attach the cold-start
breakdown children, and once for contexts.app.start_type, which is also
what is now stamped on the trace context and every child span as
app.vitals.start.type.

The field is written on the main thread (activity lifecycle callbacks,
ApplicationStartInfo) while the processor runs on whichever thread
finished the transaction, which can be the Sentry timer thread for idle
and deadline timeouts. Backgrounding the app flips the type to WARM in
onActivityDestroyed, so the reads can disagree and ship a transaction
whose measurement says app_start_cold while every span says
app.vitals.start.type = warm.

Snapshot the type once at the top of process() and pass it down, and
make the field volatile so the read is not a data race.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sentry

sentry Bot commented Aug 26, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-android Build Distribution Settings

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