Skip to content

Record transport transaction state explicitly instead of inferring it#1756

Open
dvdstelt wants to merge 2 commits into
masterfrom
refactor/explicit-dispatch-state
Open

Record transport transaction state explicitly instead of inferring it#1756
dvdstelt wants to merge 2 commits into
masterfrom
refactor/explicit-dispatch-state

Conversation

@dvdstelt

@dvdstelt dvdstelt commented Jul 11, 2026

Copy link
Copy Markdown
Member

Why

I always had issues reading the many if-statements on what state/context we're in. So I created this and hope it's more readable from now on.

What

MessageDispatcher.DispatchDefault reverse-engineered the transaction mode by probing which entries happened to be present in the TransportTransaction bag, through an order-dependent if/else chain of extension methods (OutsideOfHandler, IsNoTransaction, IsReceiveOnly, IsSendsAtomicWithReceive, IsTransactionScope). Every TransportTransaction is created by code that already knows the mode, so this PR stamps a TransportTransactionState into the transaction at creation time and turns the dispatcher into a single exhaustive switch.

  • New internal TransportTransactionState enum; all TransportTransactions factory methods record it.
  • DispatchDefault switches on the state, with a comment per case explaining how sends relate to the receive transaction. The two identical "open a dedicated connection" blocks (outside handler and receive-only) collapse into one case.
  • Inference from bag contents remains only as a fallback (InferState) for instances not created by the transport: the empty one core creates for dispatches outside the message processing pipeline, and hand-rolled ones used by external integrations. It mirrors the old chain's semantics, including its ordering.
  • The "SqlTransport.ReceiveOnlyTransactionMode" key was defined in two classes; the writer used one copy and the readers the other, working only because the strings happened to match. It now lives once in TransportTransactionKeys and is still written to the bag for downstream components (e.g. SQL persistence).
  • SendOptionsExtensions hand-assembled user-provided transactions with its own copy of the key strings while PublishOptionsExtensions used the TransportTransactions.UserProvided(...) factory; both now use the factory, and the duplicate key was removed from SettingsKeys.

Behavior notes

No public API changes (API approval tests unchanged). One deliberate alignment: a user-provided connection-only transaction reaching default (non-immediate) dispatch previously got wrapped in a new transaction, while the same bag in isolated dispatch did not. That path is unreachable in practice because UseCustomSqlConnection/UseCustomSqlTransaction force immediate dispatch; both paths now behave identically (dispatch on the user's connection). Everything else is a faithful translation of the old chain.

dvdstelt added 2 commits July 11, 2026 11:51
The dispatcher reverse-engineered the transaction mode by probing which
entries happened to be present in the TransportTransaction, through an
order-dependent if/else chain. Every TransportTransaction is created by
code that already knows the mode, so stamp it explicitly and switch on
it when dispatching. Inference remains only as a fallback for instances
created outside the transport (core's empty one, hand-rolled ones).

Also consolidates the ReceiveOnlyTransactionMode key, which was defined
twice and only worked because both strings happened to match.
SendOptionsExtensions hand-assembled the TransportTransaction with its
own copy of the key strings while PublishOptionsExtensions used the
TransportTransactions factory. Use the factory in both places and drop
the now-unused duplicate key from SettingsKeys.
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