Skip to content

Scheduled sends to non-native-scheduling transports lose context fields (TenantId, SagaId, CorrelationId, UserName) #2571

@ArieGato

Description

@ArieGato

Summary

When an envelope is scheduled to a transport that does not support native scheduled-send (RabbitMQ, Kafka, SharedMemory, ...), MessageRoute wraps it via ForScheduledSend. The wrap eagerly serializes the inner envelope into outer.Data before MessageBus.TrackEnvelopeCorrelation stamps context-derived fields. When the scheduled time fires and the inner is forwarded over the transport, the receive side reconstructs an envelope with TenantId = null, SagaId = null, etc.

Impact

Most visible on multi-tenanted sagas that use cascading TimeoutMessage:

  • Saga runs under tenant red and cascades a timeout.
  • The timeout is routed to RabbitMQ (no native scheduled-send) → wrapped.
  • The inner envelope in outer.Data has no TenantId.
  • On fire, the listener receives the timeout with no tenant.
  • Under Marten conjoined multi-tenancy the saga lookup lands on the default tenant, misses, and NotFound runs silently — the saga is never completed.

The same loss affects CorrelationId, UserName, and SagaId. TenantId and SagaId cause correctness issues; CorrelationId/UserName cause observability/audit gaps.

Reproduction

End-to-end in the RabbitMQ test suite: scheduled_saga_timeout_preserves_tenant — saga starts under "red", cascaded TimeoutMessage routes through Rabbit, pre-fix the handler never runs (silent drop).

In-process in CoreTests: inner_envelope_is_stamped_before_serialization — publishes a scheduled message via the SharedMemoryTopic wrap path; pre-fix the delivered envelope has TenantId = null.

Affected transports

Any sender with SupportsNativeScheduledSend == false. Confirmed: RabbitMQ, Kafka, SharedMemory. Native schedulers (Azure Service Bus, SQS with delay, etc.) are unaffected.

Proposed fix

  • Drop the eager Data = EnvelopeSerializer.Serialize(envelope) from ForScheduledSend so serialization is deferred to EnvelopeReaderWriter.Write — by which time stamping has happened.
  • Extract MessageBus.StampEnvelope (virtual) containing the idempotent, context-derived stamps: Source, CorrelationId, TenantId, UserName. MessageContext overrides to add SagaId.
  • In TrackEnvelopeCorrelation, call StampEnvelope(outbound); when the envelope is a scheduled wrap, also call StampEnvelope(inner).

PR: #TBD

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions