GH-3257: Preserve configured bindings when StreamBridge evicts a channel - #3266
Open
adityaanikam wants to merge 1 commit into
Open
GH-3257: Preserve configured bindings when StreamBridge evicts a channel#3266adityaanikam wants to merge 1 commit into
adityaanikam wants to merge 1 commit into
Conversation
…victs a channel StreamBridge caps its channel cache at dynamic-destination-cache-size and, on eviction, removes the evicted binding from BindingServiceProperties so that bindings it created for dynamic destinations do not accumulate. That removal is unconditional, so it also discards bindings the application configured itself. Once a configured binding is gone, the next call to BindingServiceProperties#getBindingProperties recreates an empty entry and defaults its destination to the binding name, so messages then go to the binding name instead of the configured destination. An application with more bindings than the cache size hits this as soon as enough distinct bindings are used. Capture the binding names present when StreamBridge is constructed -- those are the ones the application configured -- and skip only those during eviction. Destinations resolved on demand are still removed, so the cleanup added in 07eb699 keeps working and bindingsAreRemovedWithCache passes unchanged. Signed-off-by: adityaanikam <adityanikam9502@gmail.com>
kdelay
reviewed
Sep 3, 2026
kdelay
left a comment
Contributor
There was a problem hiding this comment.
The fix and its negative control both reproduce here.
The new test lives in core/spring-cloud-stream-integration-tests, which core/pom.xml keeps commented out of the reactor (line 20), so the mvn clean install in ci-pr.yml never compiles or runs it. The guard sits outside the gate that would catch a re-break.
The map assertion is also a proxy for the reported symptom. Asserting delivery is closer: with the first message drained, output.receive(1000, "fooDestination") after a second bridge.send("foo-out-0", ...) returns null on a reverted StreamBridge, and the message with the fix.
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.
Fixes gh-3257
Problem
StreamBridge's channel cache evicts its eldest entry once it exceedsdynamic-destination-cache-size, and since 07eb699 the eviction also removes the binding fromBindingServiceProperties:Removing bindings the bridge created for dynamic destinations is the point of that change, but the removal is unconditional, so it also discards bindings the application configured.
The consequence is not just a missing map entry.
BindingServiceProperties#getBindingPropertiesrecreates a missing entry and then defaults its destination to the binding name:So after eviction, a binding configured as
silently resolves to destination
foo-out-0, and messages go there instead. Any application with more bindings thandynamic-destination-cache-size(default 10) hits this once enough distinct bindings are used, which matches the regression reported against 5.0.3.Fix
Capture the binding names present when
StreamBridgeis constructed. At that pointBindingServicePropertieshas been bound from configuration and the bridge has not resolved any dynamic destination yet, so those names are exactly the application's own. Eviction then skips them:Dynamic destinations are still removed, so the cleanup 07eb699 added keeps working. The set is a
TreeSetwithString.CASE_INSENSITIVE_ORDERto match the key semantics of thebindingsmap itself.unbindProducersis left as-is for every eviction; it predates the regression and is not implicated here.Testing
configuredBindingsAreNotRemovedWithCacheinStreamBridgeTestsconfigures one binding with an explicit destination, sets the cache size to 1, then sends through enough further destinations to evict it, and asserts the configured binding and its destination survive.Verified with a negative control: reverting only
StreamBridge.java(keeping the test, and reinstalling the module so the test ran against the reverted code) fails it withwhich is the reported symptom. The existing
bindingsAreRemovedWithCachepasses in both directions, confirming dynamic-binding cleanup is unaffected.One note on running these:
spring-cloud-stream-integration-testsis currently commented out ofcore/pom.xml, and the module does not compile as it stands —PollableSourceTestshas an ambiguousassertThatoverload andKotlinConfigurationTestscannot resolveKotlinTestConfiguration. Both are unrelated to this change and predate it. I ran the module directly with-f core/spring-cloud-stream-integration-tests/pom.xml, with those two files temporarily set aside, to execute the test above. I put the new test besidebindingsAreRemovedWithCachesince it covers the same eviction path, but happy to move it if you would rather it live somewhere that currently runs in CI.