Skip to content

Run deliveries through one core DeliveryDispatcher behind an IDeliveryPort, and move the test harness onto it - #422

Merged
Vulthil merged 1 commit into
mainfrom
refactor/core-delivery-dispatch
Sep 30, 2026
Merged

Vulthil merged 1 commit into
mainfrom
refactor/core-delivery-dispatch

Conversation

@Vulthil

@Vulthil Vulthil commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Summary

Adds one core delivery-dispatch module to Vulthil.Messaging.Transport and moves the in-memory test harness onto it.
The RabbitMQ transport does not change here. It moves onto the same module in a follow-up PR.

Core module (additive public API)

  • DeliveryDispatcher runs one delivery through its handlers with these rules:
    • Handlers run in rounds. Each later round re-runs only the handlers that failed.
    • Every handler attempt gets its own DI scope.
    • Each handler retries under its own policy. Once it has failed for good, its fault is published.
    • Handlers that retry in the same round share one wait: the longest back-off their policies ask for. When the
      port can redeliver later and no retrying policy is in-memory, the outcome is RedeliverLater instead.
    • A request consumer runs once and replies.
    • When the delivery token ends the delivery, the dispatcher sends no fault and no reply, and the outcome is
      Abandon. A consumer's own OperationCanceledException while the token is live is an ordinary failure.
  • IDeliveryPort is the part a transport implements for each delivery: the receive context, the in-process retry
    wait, the fault route and the reply route.
  • DeliveryHandlerFactory builds DeliveryHandlers for MessageExecutionRegistry<DeliveryHandler>.
    DeliveryOutcome and DeliverySettlement tell the transport how to settle.
  • AddMessaging registers the dispatcher as a singleton.
  • The IMessageHandlerFactory retryPolicy docs are fixed. A null policy means the handler fails for good on its
    first failure, not "inherit the queue default".

Test harness

  • InMemoryHandlerFactory and InMemoryHandler are deleted. The harness implements the port.
  • These behavior changes make the harness match the broker transport:
    • Retries run in rounds. They still run back-to-back, with no delay.
    • Every consumer attempt gets its own scope.
    • A Fault<T> is still captured in Published<Fault<T>>() and still runs Handle<Fault<T>> stubs. It is no
      longer delivered to an IConsumer<Fault<T>>, because the broker never delivers it there either.
    • The fault carries the round it failed on and the correlation id of the delivery.
    • A consumer's own OperationCanceledException is retried like any failure.
    • When the caller's token ends a delivery, publish and send throw OperationCanceledException, and a request
      returns a Messaging.Request.Cancelled failure.
    • A message that a filter stops before the consumer runs is still not captured as consumed.

Docs

  • "Writing a Custom Transport" now builds on the dispatcher and the port.
  • The testing page and the two package pages describe the harness delivery rules.

Verification

  • Full solution build: 0 warnings, 0 errors.
  • All test projects pass on net10.0 and net9.0. This includes Vulthil.IntegrationTests (Testcontainers, 68 tests)
    and the Aspire messaging integration tests (net10.0, 13 tests).
  • New tests: 21 in DeliveryDispatcherTests and 9 in HarnessDeliveryTests.
  • Mutation checks:
    • Re-running every handler in every round fails 3 tests.
    • Cancellation that never ends the delivery fails 2 tests.
    • Delivering faults to fault consumers again fails 1 test.
  • dotnet pack of Vulthil.Messaging and Vulthil.Messaging.TestHarness passes package validation against the
    1.2.0 baseline (additive only).

Backport to v1.0: no

@Vulthil
Vulthil merged commit b8ceee7 into main Sep 30, 2026
7 checks passed
@Vulthil
Vulthil deleted the refactor/core-delivery-dispatch branch September 30, 2026 18:05
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