fix(batching): run direct + batch (and multiple batch) handlers under Separated mode (supersedes #3075)#3077
Merged
Conversation
…mode When a message type has both a direct Handle(T) handler and a BatchMessagesOf<T>() configuration, the direct handler silently shadowed the batch: routing (LocalRouting.FindRoutes) and executor resolution (Executor.Build / ExecutorFactory.BuildFor) only consulted the batch definitions when there was no direct handler for the element type, so the batch handler never ran. Under MultipleHandlerBehavior.Separated, treat the per-message handler and the batch handler as independent consumers of the element type: move the batch onto a dedicated '-batch' local queue so it no longer collides with the direct handler's queue, fan the element type out to both queues for in-process publishes, and relay to both local queues for messages arriving from an external transport listener.
…ed mode Under MultipleHandlerBehavior.Separated, when the batched array type has more than one Handle(T[]) handler, Wolverine splits them onto per-handler sticky queues. The BatchingProcessor re-enqueues a single produced batch onto the batch's own execution queue, which is none of those sticky queues, so executor resolution threw NoHandlerForEndpointException and no batch handler ran. Resolve the batch's execution queue to a fan-out handler that relays the produced batch (T[] or a custom batch message type) to every sticky Handle(T[]) queue, so each batch handler runs independently. Works both with and without a direct Handle(T) handler (the dedicated -batch queue case). Classic mode is unchanged: multiple Handle(T[]) handlers are still combined into one chain.
… out of /Bugs Follow-up refactor on top of the Separated-mode batching fix. No behavior change. - WolverineRuntime.Routing.cs: extract LocalRouting.FindSeparatedBatchEndpoint(...) for the work that locates an element type's dedicated batch queue, so FindRoutes reads as "discover senders, then add the batch endpoint if there is one." - Wolverine.ExecutorFactory.cs: extract the three inline Separated batch checks in BuildFor into intention-named helpers — tryBuildDedicatedBatchQueueHandler, tryBuildExternalArrivalFanoutHandler, tryBuildMultipleBatchHandlerFanout — so the method body is a short null-coalescing resolution chain. (HandlerGraph.BuildFanoutHandler and the HostService reassignBatchQueuesThatCollideWithHandlers extraction already landed in the original commits.) - Move the two regression files out of CoreTests/Bugs into the feature suites: end-to-end behavior -> CoreTests.Acceptance.batching_with_separated_handlers (alongside batch_processing); routing/executor-resolution mechanics -> CoreTests.Runtime.Routing.separated_batch_routing (alongside the routing suite). The shared handler/message types live with the batching suite and are reused by the routing suite. Co-Authored-By: Geoffrey MARC <geoffrey.marc@consulting-for.edenred.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #3075 by @outofrange-consulting (Geoffrey MARC), preserving the original commits and co-authorship, and adds a refactoring pass + test reorganization on top.
Original fix (from #3075)
Under
MultipleHandlerBehavior.Separated, a message typeTwith both a directHandle(T)and aBatchMessagesOf<T>()batch handler silently shadowed the batch — the direct handler won the single local-queue executor slot and the batch never ran. A companion case: a batched array type with multipleHandle(T[])handlers threwNoHandlerForEndpointExceptionbecause the produced batch re-enqueued onto a queue none of the per-handler sticky queues owned.The fix (Separated mode only; Classic unchanged):
reassignBatchQueuesThatCollideWithHandlersinWolverineRuntime.HostService): when an element type has both a direct handler and a batch definition, move the batch onto its own<convention>-batchqueue.LocalRouting.FindRoutes): fan the element type out to the batch queue in addition to the direct handler's queue.BuildFor): the dedicated batch queue always resolves to theBatchingProcessor; external-listener arrivals relay to both local queues; a produced batch with multiple Separated handlers fans out to every sticky queue.HandlerGraph.BuildFanoutHandler: sharedFanoutMessageHandler<T>construction.Docs: "Combining a direct handler with batching" section in
docs/guide/handlers/batching.md.What this PR adds on top
Refactoring (no behavior change):
WolverineRuntime.Routing.cs— extractedLocalRouting.FindSeparatedBatchEndpoint(...)soFindRoutesreads as "discover senders, then add the batch endpoint if there is one."Wolverine.ExecutorFactory.cs— the three inline Separated batch checks inBuildForare now intention-named helpers (tryBuildDedicatedBatchQueueHandler,tryBuildExternalArrivalFanoutHandler,tryBuildMultipleBatchHandlerFanout), reducingBuildForto a short null-coalescing resolution chain. (HandlerGraph.BuildFanoutHandlerand theHostServiceextraction already landed in the original commits.)Test reorganization — moved the two regression files out of
CoreTests/Bugsinto the feature suites:CoreTests.Acceptance.batching_with_separated_handlers(alongsidebatch_processing)CoreTests.Runtime.Routing.separated_batch_routing(alongside the routing suite)Verification
6 relocated tests pass; 52-test batching/routing/separated/sticky regression passes;
wolverine.slnx -c Releasebuilds clean (0 warnings, 0 errors).Closes #3075
🤖 Generated with Claude Code