LiteBus
CatalogDispatch

Processor Pipeline Coupling

  • ID: dispatch.processor-coupling
  • Name: Processor pipeline coupling
  • Maturity: GA
  • Summary: Pipelined inbox and outbox processors lease envelopes, invoke the registered dispatcher inside a per-message DI scope, and persist terminal outcomes from dispatch exceptions.

What It Does

PipelinedInboxProcessor and PipelinedOutboxProcessor are the only processor implementations in v6. Each pass leases a batch, fans work to DispatcherConcurrency workers, renews leases on a heartbeat interval, and calls DispatchAsync on the registered dispatcher. Dispatch failures throw; the processor maps exceptions to Failed or DeadLettered store state according to retry policy.

Background services register through the module manifest when EnableInboxProcessor() or EnableOutboxProcessor() runs. Startup fails if no dispatcher is registered. Each dispatched message resolves IInboxDispatcher or IOutboxDispatcher from a per-message scope so scoped handlers (for example DbContext) are isolated.

Hook failure policies differ by dispatcher type. Transport outbox dispatch defaults to CompleteDespiteHookFailure; in-process outbox and inbox processors default to DeadLetter when after-dispatch hooks fail.

Packages

PackageRole
LiteBus.InboxPipelinedInboxProcessor, processor hosting
LiteBus.OutboxPipelinedOutboxProcessor, processor hosting
LiteBus.Inbox.AbstractionsIInboxDispatcher, processor options
LiteBus.Outbox.AbstractionsIOutboxDispatcher, IOutboxDispatcherModule.DefaultHookFailurePolicy
LiteBus.Messaging.AbstractionsProcessorHookFailurePolicy, hook contracts

Requires

  • durable-core.inbox.processor or durable-core.outbox.processor
  • dispatch.registration (a dispatcher must be registered)
  • durable-core.*.storage (lease and state writers)

Invariants

  • At-least-once dispatch: crash between external side effect and terminal persist can produce duplicates.
  • Outbox processors dispatch before terminal published state is persisted; broker ack without persist can republish on lease reclaim.
  • Per-message dispatch failures do not abort sibling workers in the same pass; pass-level abort applies to leasing and shutdown cancellation.
  • In-flight rows may remain Processing until lease expiry on graceful shutdown unless drained.

Non-Goals

  • Does not implement exactly-once side effects or two-phase publish acknowledgment.
  • Does not choose the dispatcher implementation (registration is explicit).
  • Does not return handler results to the original accept/enqueue caller.

Public Surface

inbox.EnableInboxProcessor(options =>
{
    options.DispatcherConcurrency = 4;
    options.LeaseHeartbeatInterval = TimeSpan.FromSeconds(10);
});

IInboxDispatcher.DispatchAsync(InboxEnvelope, CancellationToken)

PackageLiteBus.Inbox.Abstractions
Called byPipelinedInboxProcessor via per-message DI scope
ContractThrow on failure; processor records retry or dead-letter

IOutboxDispatcher.DispatchAsync(OutboxEnvelope, CancellationToken)

PackageLiteBus.Outbox.Abstractions
Called byPipelinedOutboxProcessor via per-message DI scope
ContractThrow on failure; processor records retry or dead-letter

PipelinedInboxProcessor / PipelinedOutboxProcessor

MemberRole
ProcessPendingAsync(CancellationToken)One processor pass: lease batch, dispatch workers, persist outcomes
Constructor optionsInboxProcessorOptions / OutboxProcessorOptions

Key options affecting dispatch coupling:

OptionRole
DispatcherConcurrencyParallel dispatch workers per pass
LeaseHeartbeatIntervalLease renewal during slow dispatch
HookFailurePolicyTerminal state when after-dispatch hooks fail
HonorShutdownTokenOnPersistWhether persist honors shutdown token (duplicate-dispatch trade-off)

IProcessorEnvelopeHook

Before/after hooks around each leased envelope (saga, custom). Hook failures interact with HookFailurePolicy and IOutboxDispatcherModule.DefaultHookFailurePolicy on transport outbox modules.

Observability

SignalNameWhen
Inbox processor statelitebus.inbox.processor.state (Running, Paused, Draining)Processor lifecycle transitions
Outbox processor statelitebus.outbox.processor.stateSame for outbox
Pass counterslitebus.inbox.processor.passes, litebus.outbox.processor.passesEach processor pass start
Success counterslitebus.inbox.processor.succeeded, litebus.outbox.processor.publishedTerminal success after dispatch + hooks
Failure counterslitebus.inbox.processor.failed, litebus.outbox.processor.failedDispatch or hook failure with retry scheduled
Dead-letter counterslitebus.inbox.processor.dead_lettered, litebus.outbox.processor.dead_letteredMax attempts or hook dead-letter policy
Loop errorslitebus.inbox.processor.loop_errors, litebus.outbox.processor.loop_errorsUnexpected pass-level exceptions
Dispatch durationlitebus.inbox.processor.dispatch_duration, litebus.outbox.processor.dispatch_durationHistogram around each DispatchAsync invocation
Lease lostlitebus.inbox.processor.lease_lost, litebus.outbox.processor.lease_lostHeartbeat renewal failure cancels in-flight dispatch
Transport sendsend {destination} activityEmitted by the concrete transport publisher during processor-driven dispatch

No separate OpenTelemetry meter on IInboxDispatcher implementations; all dispatch-axis processor telemetry flows through inbox/outbox processor meters.

Analyzers

  • LB1014: Processor enabled without dispatcher in the same builder scope.

Deep Docs

Test Coverage

Covered

Test methodProject
PipelinedProcessor_WithConcurrencyOne_ShouldProcessAllCommandsLiteBus.Inbox.UnitTests
PipelinedProcessor_WithParallelWorkers_ShouldDispatchConcurrentlyLiteBus.Inbox.UnitTests
PipelinedProcessor_WithHeartbeat_ShouldCompleteSlowHandlerWithoutReclaimLiteBus.Inbox.UnitTests
PipelinedProcessor_when_after_dispatch_hook_fails_should_not_redispatch_handlerLiteBus.Inbox.UnitTests
PipelinedProcessor_when_after_dispatch_hook_fails_should_persist_dead_letter_without_completedLiteBus.Inbox.UnitTests
PipelinedProcessor_when_hook_failure_policy_is_complete_despite_hook_failure_should_mark_completedLiteBus.Inbox.UnitTests
PipelinedProcessor_when_lease_renewal_fails_should_cancel_dispatchLiteBus.Inbox.UnitTests
ProcessPendingAsync_when_after_dispatch_hook_fails_should_dead_letter_from_processingLiteBus.Storage.IntegrationTests (PostgreSql/)
ProcessPendingAsync_parallel_workers_should_produce_single_terminal_state_per_messageLiteBus.Storage.IntegrationTests (PostgreSql/)
PipelinedProcessor_when_after_dispatch_hook_fails_should_not_redispatchLiteBus.Outbox.UnitTests
PipelinedProcessor_when_after_dispatch_hook_fails_should_persist_dead_letter_without_publishedLiteBus.Outbox.UnitTests
PipelinedProcessor_when_hook_failure_policy_is_complete_despite_hook_failure_should_mark_publishedLiteBus.Outbox.UnitTests
ProcessPendingAsync_WhenPersistSkippedAfterPublish_ShouldRepublishOnRetryLiteBus.Durable.IntegrationTests (Dispatch/Outbox/InMemory/)
ProcessPendingAsync_WithConcurrentMessages_ShouldIsolateAndDisposeScopedDbContextsLiteBus.Storage.IntegrationTests (EntityFrameworkCore/Inbox/)
ProcessPendingAsync_WhenShutdownBeginsAfterAmqpPublish_ShouldApplyTerminalPersistPolicyLiteBus.Durable.IntegrationTests (Dispatch/Outbox/Amqp/)
StopAsync_WhenInboxDispatchIsActive_ShouldWaitForCompletionAndPersistTerminalStateLiteBus.Runtime.UnitTests (Runtime/Hosting/)

Out-of-Scope

  • Exactly-once side effects or two-phase publish acknowledgment
  • Automatic dispatcher selection (registration is explicit)
  • Returning handler or mediator results to the original accept/enqueue caller

On this page