Skip to content

Preserve correlation/trace context across Global Partitioning interceptor - #2709

Closed
lyall-sc wants to merge 1 commit into
JasperFx:mainfrom
lyall-sc:fix/global-partitioned-interceptor-correlation
Closed

lyall-sc wants to merge 1 commit into
JasperFx:mainfrom
lyall-sc:fix/global-partitioned-interceptor-correlation

Conversation

@lyall-sc

@lyall-sc lyall-sc commented May 8, 2026

Copy link
Copy Markdown
Contributor

The GlobalPartitionedInterceptor re-routes inbound external messages through Wolverine's routing so they land on the correct internal shard. Previously it built a fresh MessageBus(_runtime) with no inbound envelope, so PropagateHeadersRule.ApplyCorrelation short-circuited (originator.Envelope was null) and the re-published envelope dropped correlation, tenant id, and distributed-trace continuity. Trace context restarted at the interceptor hop and any PropagateIncomingHeadersToOutgoing allowlist was silently ignored for globally-partitioned messages.

The interceptor now constructs a per-receive RouteBus seeded with the inbound envelope, copies CorrelationId/TenantId/GroupId via DeliveryOptions, and brackets the publish in an Activity rooted at envelope.ParentId so the re-routed envelope continues the original trace. Custom headers are not copied wholesale — they flow only via PropagateIncomingHeadersToOutgoing, matching cascade semantics. The interceptor ctor is simplified to (IReceiver, IWolverineRuntime); topologies and logger are derived from runtime.

…ceptor

The GlobalPartitionedInterceptor re-routes inbound external messages
through Wolverine's routing so they land on the correct internal shard.
Previously it built a fresh MessageBus(_runtime) with no inbound envelope,
so PropagateHeadersRule.ApplyCorrelation short-circuited (originator.Envelope
was null) and the re-published envelope dropped correlation, tenant id, and
distributed-trace continuity. Trace context restarted at the interceptor
hop and any PropagateIncomingHeadersToOutgoing allowlist was silently
ignored for globally-partitioned messages.

The interceptor now constructs a per-receive RouteBus seeded with the
inbound envelope, copies CorrelationId/TenantId/GroupId via DeliveryOptions,
and brackets the publish in an Activity rooted at envelope.ParentId so the
re-routed envelope continues the original trace. Custom headers are not
copied wholesale — they flow only via PropagateIncomingHeadersToOutgoing,
matching cascade semantics. The interceptor ctor is simplified to
(IReceiver, IWolverineRuntime); topologies and logger are derived from
runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller

Copy link
Copy Markdown
Member

Using the slightly different #2710, but with attribution

jeremydmiller added a commit that referenced this pull request May 8, 2026
….CopyContextCorrelationFrom()

Supersedes the implementation in #2709 while keeping the test contract
Lyall established (cherry-picked here as the parent commit so attribution
is preserved end-to-end).

Behavioural shape is identical to Lyall's PR — re-routed envelopes
inherit the inbound's correlation, tenant id, and distributed-trace
parent — but the field copying is delegated to the existing
`Envelope.CopyContextCorrelationFrom` helper. That method was added in
GH-2571 / PR #2572 to give the durable scheduled-send unwrap path
(`ScheduledSendEnvelopeHandler`) and the in-memory tracked-session
replay path (`TrackedSession.ReplayAll`) one canonical place to copy
the six context-correlation fields:

    CorrelationId, ConversationId, TenantId, UserName, ParentId, SagaId

The global-partitioning re-route is the same forwarding shape — same
logical message, different shard — so it should reuse the same helper
rather than re-state the field list inline. This PR adds it as the
third caller.

Implementation:

* `RouteBus` (private subclass of `MessageBus`) still seeds
  `Envelope = inbound` so `IEnvelopeRule.ApplyCorrelation` consumers
  like `PropagateHeadersRule` continue to see the originator and
  honour the `PropagateIncomingHeadersToOutgoing` allowlist.

* `RouteBus` overrides `TrackEnvelopeCorrelation` and replaces the
  base bus's narrower per-field pull-from-bus logic with a single
  `outbound.CopyContextCorrelationFrom(_inbound)` call. The base
  copies `CorrelationId` / `TenantId` / `UserName` from the bus's
  own properties and writes a fresh `ParentId` from
  `Activity.Current` — both wrong for a forwarded envelope:
  `ConversationId` would restart, `SagaId` would drop, and the
  trace would re-root at the interceptor hop instead of continuing
  the inbound's parent.

* The interceptor's `DeliveryOptions` is reduced to just `GroupId`,
  which is the only piece routing itself needs to read — every
  context field flows through the bus's overridden tracker.

Test coverage: same suite, lifted from #2709 unchanged. The two
`ShouldNotContainKey` calls were tweaked to disambiguate Shouldly's
`IDictionary` / `IReadOnlyDictionary` overloads (`Envelope.Headers`
is a concrete `Dictionary`); behavioural assertions are otherwise
verbatim.

Tests: full `Runtime.Partitioning` suite green (78/78) on net9.0,
including the 16 GP-interceptor cases with the four new context
behaviours from #2709.

Co-Authored-By: Lyall Guiney <lyall@sportcontext.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants