GH-3974: let a consumer ask which message types are handled, and how a batch is shaped - #3977
Merged
Merged
Conversation
…a batch is shaped Two related gaps, both of which forced consumers to re-derive something Wolverine already knows. 1. "Will this message type have a handler?" could not be asked. Discovery and the static TypeLoadMode registry both materialize AFTER options time, so an extension or app-level convention that installs FALLBACK handlers -- a relay, a batch forwarder, a catch-all -- had to hand-roll a mirror of Wolverine's own discovery convention to avoid clobbering a real handler. Any reflection-based reimplementation of the framework's convention drifts from it, and the drift is silent. The reported case scanned a single assembly, so moving two handlers into a second assembly made them invisible to the mirror and the sweep installed a bare relay OVER a real handler -- the exact defect the guard existed to prevent, with every codegen test still green. Adds WolverineOptions.OnHandlersDiscovered(Action<DiscoveredHandlers>), invoked from HandlerGraph.Compile immediately after Group() -- the earliest point at which the question has a real answer. DiscoveredHandlers exposes Handles<T>() / Handles(Type) and the full resolved set. This is the "hook that runs once discovery has resolved" the issue offers as an acceptable answer to the first half. 2. IMessageBatcher.BatchMessageType is a free-form Type. The default batcher produces T[], but nothing requires that, and the auto-swap in WolverineRuntime.HostService deliberately leaves an application-supplied batcher alone precisely so it can produce its own type. So consumers inferred the handled type from array-ness -- parameters[0].ParameterType.GetElementType() -- which is silently wrong for exactly those batchers. Adds WolverineOptions.TryFindBatchMessageType(elementType, out batchMessageType) and WolverineOptions.BatchMappings. The data already existed on BatchingOptions; it was just internal. The custom-batcher test asserts the mapping reports the batcher's OWN type and that the type is not an array, which is the case an array-ness inference gets wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JG8Un6iNeyXECKJk3jo5uC
This was referenced Aug 18, 2026
This was referenced Aug 26, 2026
Open
This was referenced Sep 3, 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.
Closes #3974. Two related gaps, both of which forced consumers to re-derive something Wolverine already knows.
1. "Will this message type have a handler?"
Discovery and the static
TypeLoadModeregistry both materialize after options time, so an extension or app-level convention that installs fallback handlers — a relay, a batch forwarder, a catch-all — had to hand-roll a mirror of Wolverine's own discovery convention to avoid clobbering a real one.Any reflection-based reimplementation of the framework's convention drifts from it, and the drift is silent. The reported case scanned a single assembly, so moving two handlers into a second one made them invisible to the mirror and the sweep installed a bare relay over a real handler — the exact defect the guard existed to prevent, with every codegen test still green.
OnHandlersDiscoveredfires fromHandlerGraph.Compileimmediately afterGroup()— the earliest point at which the question has a real answer.DiscoveredHandlersexposesHandles<T>(),Handles(Type)and the full resolved set.This is the "hook that runs once discovery has resolved but before the app needs the answer" that the issue offers as an acceptable answer to this half. A genuine options-time query would mean forcing discovery while
WolverineOptionsis still being configured, which would freeze the handler set before later configuration could add to it — a worse failure mode than the one being fixed.2. The batcher's element → batch message mapping
IMessageBatcher.BatchMessageTypeis a free-formType. The default batcher producesT[], but nothing requires that, and the auto-swap inWolverineRuntime.HostServicedeliberately leaves an application-supplied batcher alone precisely so it can produce its own type. So consumers inferred the handled type from array-ness —parameters[0].ParameterType.GetElementType()— which is silently wrong for exactly those batchers.The data already existed on
BatchingOptions; it was justinternal. This is the half the issue notes is "cheap even if the first is not" and would on its own "remove the special case."Tests
Four in
discovering_handled_message_types:T[]Batching regression: 118/118.
Docs
docs/guide/handlers/batching.mdnow warns thatBatchMessageTypeis free-form and shows both query forms next to the custom-batcher sample.🤖 Generated with Claude Code
https://claude.ai/code/session_01JG8Un6iNeyXECKJk3jo5uC