fix(api): MultiBuilder rejects silently-dropped per-route consumer-wide settings#199
Merged
eschizoid merged 2 commits intoJun 20, 2026
Conversation
…de settings MultiBuilder.start() folds N per-topic routes into a single underlying KPipeConsumer, but only replays each route's pipeline (buildPipeline / addBatchRoute) — the route's consumer-level config (withRetry, withDeadLetterTopic, withErrorHandler, withPollTimeout, withMetrics, withTracer, withCircuitBreaker, withBackpressure) was silently dropped. DefaultStream.applyCommonConsumerConfig is never called from the multi path. The previous rejectPerRouteConsumerWideSettings guard only caught withProcessingMode and withKeyOrderedMaxKeys; everything else fell on the floor and produced a consumer with neither retry NOR DLQ wired and no error. This is a §12 silent-failure: the misconfig surfaces as missing behavior at runtime instead of a loud rejection at construction. Extend the guard to throw IllegalArgumentException for every silently- dropped per-route setter. For settings with a symmetric MultiBuilder mirror (withMetrics, withTracer, withCircuitBreaker), the message points at it; for settings without a mirror yet (withRetry, withBackpressure, withDeadLetterTopic, withErrorHandler, withPollTimeout), the message points at the explicit KPipeConsumer. Builder escape hatch and notes the deferred follow-up. Tests pin one rejection per setting plus a smoke test that the existing consumer-wide MultiBuilder setters still build cleanly.
Owner
Author
|
@copilot please review |
Copilot stopped work on behalf of
eschizoid due to an error
June 19, 2026 23:22
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #199 +/- ##
============================================
+ Coverage 77.82% 78.50% +0.68%
- Complexity 732 747 +15
============================================
Files 68 66 -2
Lines 2800 2815 +15
Branches 346 356 +10
============================================
+ Hits 2179 2210 +31
+ Misses 462 444 -18
- Partials 159 161 +2 ☔ View full report in Codecov by Harness. |
Owner
Author
|
@copilot please review |
Copilot stopped work on behalf of
eschizoid due to an error
June 20, 2026 00:01
…tion Reviewer flagged exception-type inconsistency: the pre-existing withProcessingMode / withKeyOrderedMaxKeys checks threw IllegalStateException while the eight new rejections in this PR use IllegalArgumentException. Same failure mode (route-configurator misconfig at construction). Promote both legacy checks to IllegalArgumentException so callers can catch one type. Updates the two assertions in KPipeFacadeBuildTest that pinned the old type.
4abf19e to
b1f8f06
Compare
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.
Summary
MultiBuilder.start()was a §12 silent-failure: per-route consumer-widesettings like
withRetry,withDeadLetterTopic,withErrorHandler,withPollTimeout,withMetrics,withTracer,withCircuitBreaker,and
withBackpressurewere quietly dropped when a route configuratorset them. The previous
rejectPerRouteConsumerWideSettingsguard onlycaught
withProcessingMode+withKeyOrderedMaxKeys; everything elsefell on the floor.
The bug
MultiBuilder.start()only invokesds.buildPipeline()/addBatchRoute(...)for each route.DefaultStream.applyCommonConsumerConfig(which propagates retry / backpressure / metrics / errorHandler /
dlqTopic / pollTimeout / tracer onto the underlying
KPipeConsumer.Builder)is never called from the multi path. Every per-route consumer-wide
setting other than the two already guarded was silently lost.
Reproduction (before this PR)
…built a consumer with neither retry NOR DLQ wired, and emitted no
error. The misconfig surfaced only as missing behavior at runtime —
exactly the overloaded-null / silent-drop footgun §12 exists to prevent.
Fix
Extend
MultiBuilder.rejectPerRouteConsumerWideSettingsto throwIllegalArgumentExceptionwhen a per-route configurator sets anyconsumer-wide value. Two message shapes:
MultiBuilder.with*mirror (withMetrics,withTracer,withCircuitBreaker): error points users at theconsumer-level setter.
withRetry,withBackpressure,withDeadLetterTopic,withErrorHandler,withPollTimeout):error points users at the explicit
KPipeConsumer.Builderescape hatch and notes the deferred multi-builder mirror follow-up.
Deferred follow-up
Adding consumer-wide mirrors on
MultiBuilderforwithRetry/withDeadLetterTopic/withErrorHandler/withPollTimeout/withBackpressureis a separate change — they need design decisions(e.g. should DLQ be a consumer-wide single topic or per-route routing?).
This PR's rejection message documents the gap so users hit a loud error
with a workable next step, instead of a silently-broken consumer.
Test plan
withRetry,withBackpressure,withDeadLetterTopic,withErrorHandler,withPollTimeout,withMetrics,withTracer,withCircuitBreaker. Each asserts thethrown
IllegalArgumentExceptionnames the offending setter and theoffending route topic, and (for mirrored settings) the message points
at the consumer-level
MultiBuilder.with*setter.MultiBuildersetters(
withMetrics,withTracer,withCircuitBreaker) still buildcleanly together with the existing
withProcessingMode/withKeyOrderedMaxKeyschain../gradlew :lib:kpipe-api:test— all 21 cases inKPipeFacadeBuildTestpass; full module suite green.