spike: MAUI idle transactions over Activities — shim battle-test + direct prototype (experiment, do not merge)#5384
Draft
jamescrosswell wants to merge 61 commits into
Draft
Conversation
…inished transactions
Resolves 5116: - #5116
…Span test Without this, StartNavigationSpan always returned null and the test never verified the navigation span finishing behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rface impl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aui-idle-over-shim
Merges #5138 (maui-trace-ui-5116) onto the Activity shim spike and proves the MAUI automatic trace instrumentation works over the shim WITHOUT ANY CHANGES to MauiEventsBinder itself. The shim needed four additions: - The idle timeout rides the same fused side-channel as the custom sampling context: Hub passes it to the shim factory, the shim fuses it onto the Activity before Start, and the processor hands it to Hub.StartTransaction so the shadow tracer runs the real #5138 idle machinery (timer, pause-while-children-active, discard-if-empty, end-time trimming). - ActivityTransactionShim implements IAutoTimeoutTracer, delegating ResetIdleTimeout to the shadow tracer (MauiEventsBinder casts for this). - TransactionTracer gains an internal OnFinished callback, letting the shim stop the backing Activity when the idle timer finishes the transaction out-of-band (capture or discard). Without this the Activity leaks and - worse - stays Activity.Current, silently re-parenting later spans. - ActivityTransactionShim.Create detaches from the ambient Activity so a new Sentry-API transaction is an independent root: a second UI click must start a new trace, not become an implicit child of the still-idling first activity (Activity.Current parenting would otherwise kick in). Also adds SentryOptions.IdleTimerFactory (internal) so tests can inject MockTimer through the real Hub. MauiActivityShimTests (real Hub + listener + MauiEventsBinder, 5 scenarios): activity-backed idle transaction with correct timeout; idle-fire captures a trimmed transaction with the ui.load child and stops the Activity; ResetIdleTimeout flows through the shim; discard-if-empty stops the Activity and clears the scope; second click starts an independent trace. Verified: Sentry.Maui.Tests 164/164, Sentry.Tests 2410, DiagnosticSource tests 127/127 (net10.0). #skip-changelog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he shim)
Answers the second question: converting the MAUI tracing from the shim to
direct Activity instrumentation is mechanical. MauiUiActivityTracing is the
direct-Activity equivalent of MauiEventsBinder's tracing methods, and
MauiDirectActivityTests runs the same five behavioral scenarios as the shim
battle-tests with identical assertions - both implementations produce the
same captured transactions.
The conversion table (documented in MauiUiActivityTracing):
internalHub.StartTransaction(context, idleTimeout) -> Source.StartRootActivity(op, name, idleTimeout)
autoTimeoutTracer.ResetIdleTimeout() -> activity.ResetIdleTimeout()
parentSpan.StartChild("ui.load", name) -> Source.StartActivity("ui.load") + DisplayName
navSpan.Finish(SpanStatus.Ok) -> activity.Stop(SpanStatus.Ok)
Supporting pieces:
- ActivityTracingExtensions: the API surface Sentry integrations use when
instrumenting directly with Activities - StartRootActivity (detach from
ambient + idle timeout via the fused side-channel), ResetIdleTimeout, and
Stop(SpanStatus) for rich statuses. The idle mechanics themselves (timer,
pause-while-children-active, discard-if-empty, end-time trimming) are
unchanged: they run on the shadow tracer, driven by the fused idle timeout.
- The stop-the-Activity-on-out-of-band-finish hook moved from the shim into
SentryActivityProcessor.CreateRootSpan, where it now covers every
activity-derived transaction (shim-created or direct).
Gotcha discovered: ActivityListener.ShouldListenTo fires from inside the
ActivitySource constructor, so a listener filter comparing against a static
ActivitySource field by reference races that field's initialization (it
reads null and permanently declines the source). Filters must compare by
name; documented on MauiUiActivityTracing.SourceName.
Verified: Sentry.Maui.Tests 169/169 (5 shim battle-tests + 5 direct-Activity
tests + all #5138 tests), Sentry.Tests 2410, DiagnosticSource tests 127/127;
core + standalone build clean on all TFMs.
#skip-changelog
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## spike/activity-tracing-shim #5384 +/- ##
===============================================================
+ Coverage 73.73% 73.79% +0.05%
===============================================================
Files 515 518 +3
Lines 18777 18987 +210
Branches 3673 3746 +73
===============================================================
+ Hits 13846 14011 +165
- Misses 4031 4055 +24
- Partials 900 921 +21 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Third spike in the Activity-tracing series, stacked on #5383 (which stacks on #5382). This one merges #5138 (MAUI automatic trace instrumentation) onto the shim branch and answers the two questions posed for it — as two commits, one per question:
Q1: Does #5138 work as-is over the shim? — Yes (commit 1)
MauiEventsBinderneeded zero changes. All idle-transaction mechanics — idle timeout, reset-on-interaction, pause-while-children-active, discard-if-empty, end-time trimming — work while every transaction and span is backed by an Activity. The shim needed four small additions:customSamplingContext): Hub → shim factory → fused onto the Activity beforeStart()→ processor hands it toHub.StartTransaction→ the shadow tracer runs feat: Automatic trace instrumentation for MAUI #5138's real idle machinery unmodified.ActivityTransactionShimimplementsIAutoTimeoutTracer, delegatingResetIdleTimeout()(the binder casts for this).TransactionTracer.OnFinished(internal callback): when the idle timer finishes the transaction out-of-band (capture or discard), the backing Activity gets stopped. Without this the Activity leaks — and worse, staysActivity.Currentand silently re-parents later spans.StartTransactionexplicitly detaches from any ambient Activity, so a second UI click is an independent trace, not an implicit child of the still-idling first activity.MauiActivityShimTests(real Hub + listener + realMauiEventsBinder+MockTimer, 5 scenarios) proves it end-to-end, including the trimmed end-timestamp andui.loadchild span in the captured transaction.Q2: How hard is removing the shim afterwards? — Mechanical (commit 2)
MauiUiActivityTracingis the direct-Activity rewrite of the binder's tracing methods, andMauiDirectActivityTestsruns the same five scenarios with identical assertions — both implementations produce the same captured transactions. The conversion table:internalHub.StartTransaction(context, idleTimeout)Source.StartRootActivity(op, name, idleTimeout)autoTimeoutTracer.ResetIdleTimeout()activity.ResetIdleTimeout()parentSpan.StartChild("ui.load", name)Source.StartActivity("ui.load")+DisplayNamenavSpan.Finish(SpanStatus.Ok)activity.Stop(SpanStatus.Ok)The Sentry-specific semantics raw
ActivitySourcecalls can't express live in a small internal extension class (ActivityTracingExtensions): starting an independent root, idle timeouts, rich span statuses. The idle mechanics stay on the shadow tracer either way — no reimplementation was needed for the direct path (the stop-Activity-on-out-of-band-finish hook generalized from the shim into the processor, covering both).Findings worth remembering
TransactionTracer.OnFinished) — otherwise staleActivity.Currentsilently re-parents subsequent spans. This generalizes beyond MAUI to anything that auto-finishes transactions.ActivityListener.ShouldListenTofires from inside theActivitySourceconstructor, so a filter comparing a staticActivitySourcefield by reference races that field's initialization (reads null, permanently declines the source). Compare by name.UnsampledTransaction) — their Activity stays current until the next interaction detaches. Harmless today, but the real implementation should stop those Activities too.Test evidence (local, macOS, net10.0)
Sentry.Maui.Tests: 169/169 — 5 shim battle-tests, 5 direct-Activity tests, plus all of feat: Automatic trace instrumentation for MAUI #5138's own tests unchangedSentry.Tests: 2410 passed (includes feat: Automatic trace instrumentation for MAUI #5138'sTransactionTracerTests) ·Sentry.DiagnosticSource.Tests: 127/127#skip-changelog
🤖 Generated with Claude Code