Skip to content

feat(profiling): drop profiler context from transactions when profiler gets rate-limited - #5993

Draft
markushi wants to merge 3 commits into
mainfrom
feat/drop-invalid-profiler-ids
Draft

feat(profiling): drop profiler context from transactions when profiler gets rate-limited#5993
markushi wants to merge 3 commits into
mainfrom
feat/drop-invalid-profiler-ids

Conversation

@markushi

Copy link
Copy Markdown
Member

📜 Description

Transactions are tagged with the continuous profiler's profiler_id as soon as they start, but the OS only tells us whether a Perfetto profile actually exists later. When ProfilingManager rejects the request — usually rate limiting, reported within a few milliseconds — no chunk is ever produced, and the transaction still ships a profiler_id that resolves to nothing.

This adds a notification path so anything already tagged with a dead id can drop it before being sent:

  • IProfilingCanceledCallback (internal) with onProfilingCanceled(SentryId), plus register/unregister on IContinuousProfiler. Only PerfettoContinuousProfiler implements them; the legacy Android and JVM profilers have no equivalent signal and no-op.
  • PerfettoProfiler gained setOnCanceledCallback, fired once per instance on every path that ends without a trace file: an OS error code, a null result path, a missing or empty trace file, the result timeout, and endAndCollect without start. The error path fires as soon as the OS reports, rather than waiting up to 60s for the chunk to end.
  • PerfettoContinuousProfiler tears the live chunk down on cancellation, so transactions started afterwards cannot pick up an id that is already known dead, and notifies its listeners.
  • SentryTracer implements the callback: it removes its ProfileContext, clears SpanDataConvention.PROFILER_ID from the root and every child span, and stops tagging spans started after that point.

💡 Motivation and Context

Rate limiting is the common case on API 35+, and it is reported roughly 1 ms after the request. Without this, a rate-limited session produces transactions that link to profiles the backend never receives, which surfaces in the UI as dangling profile references.

💚 How did you test it?

Unit tests, 24 added across three suites:

  • PerfettoProfilerTest — the callback fires on each of the five no-trace-file paths, fires only once when a failure is followed by endAndCollect, and does not fire when a trace file is produced.
  • PerfettoContinuousProfilerTest — listeners get the profiler id of the running chunk, the id exists before the callback is installed, cancellation tears the chunk down and resets the id, start() returning false resets and notifies, close(true) notifies then drops listeners while close(false) keeps them, and a throwing listener neither escapes nor costs the others their notification.
  • SentryTracerTest / DefaultSpanFactoryTest — register and unregister lifecycle, the waitForChildren case where finish() must not unregister yet, the profile context and span data being dropped on a matching id, and left alone on a non-matching one.

📝 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

Known gaps left deliberately, happy to fold any of them in:

  • Cancellation is scoped to the profiler id, not the chunk. Since the id survives restarts, one failed chunk also invalidates transactions covered by an earlier chunk that was sent. Losing a valid link was preferred over shipping one that resolves to nothing.
  • A cancellation landing between the tracer reading the profiler id and registering itself is not observed. The window is a few instructions and both other interleavings are handled by the chunk teardown.
  • finish() unregisters unconditionally, and tracers that are never finished stay registered until close(true).

@github-actions

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 5281e02

@sentry

sentry Bot commented Aug 25, 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