Repository navigation
Conversation
spring-cloudGH-3158 changed the bindings map back to a TreeMap with a case-insensitive comparator to restore case-insensitive binding names. That also dropped the thread safety added by spring-cloudgh-3011 and kept by spring-cloudgh-3076. The map is mutated on read: getBindingDestination(), getBindingProperties(), getProducerProperties() and getConsumerProperties() add a missing binding on first use (bindIfNecessary -> bindToDefault -> bindings.put). StreamBridge, BindingService and the binders call these from many threads without any lock. Concurrent first use of binding names corrupts the red-black tree: entries are lost, size() no longer matches the iteration, a NullPointerException is thrown from TreeMap.rotateRight (the failure reported in spring-cloudgh-3011) or a thread spins forever in TreeMap.successor while holding whatever monitor its caller holds, which stalls every thread that needs the bindings. Use a ConcurrentSkipListMap with String.CASE_INSENSITIVE_ORDER. It keeps what spring-cloudGH-3158 needs (case-insensitive lookup, original key casing, sorted iteration, same as the TreeMap) and is safe for concurrent use. A ConcurrentHashMap cannot be used because it is case-sensitive. The ConcurrentSkipListMap of spring-cloudgh-3011 was replaced by spring-cloudgh-3076 because of slower start-up. That issue's thread does not show the map as the cause: the reporter saw no improvement with the ConcurrentHashMap build and traced the slowdown to a Spring Boot change in how child contexts are created, plus binding properties placed under spring.cloud.config instead of spring.cloud.stream, which made every first getBinder() call take the bindToDefault() path. The new test, in BindingServiceTests, fails on the TreeMap (entries are lost or the map throws) and passes with this change. Fixes spring-cloudgh-3321 See spring-cloudgh-3011, spring-cloudgh-3076, spring-cloudgh-3158 Signed-off-by: Akash Bhogar <55949790+AkashB23@users.noreply.github.com>
AkashB23
force-pushed
the
gh-bindings-thread-safe
branch
from
November 2, 2026 09:30
e9ac12e to
5d66d26
Compare
Author
|
@olegz can you please check |
This branch has not been deployed
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 #3321
BindingServiceProperties.bindingsbecame a plainTreeMapagain in GH-3158, which dropped the thread safety added by #3011. The map is written on read (bindIfNecessary->bindToDefault->bindings.put) byStreamBridge,BindingServiceand the binders, from many threads and without a lock, so concurrent first use of binding names corrupts it (lost entries,size()not matching the iteration, aNullPointerExceptioninTreeMap.rotateRight, or a thread spinning forever inTreeMap.successor). Details, the history of the field and the affected versions are in #3321.This changes the map to
new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER):TreeMap;ConcurrentHashMapis not an option, since it is case-sensitive (the reason for Spring Cloud Stream binding names in properties have become case sensitive #3158).#3076 replaced the
ConcurrentSkipListMapfor performance, but the reporter saw no improvement with theConcurrentHashMapbuild and traced the slowdown to a Spring Boot child-context change and a misplacedoutput-bindingsproperty, so the map was not shown to be the cause (see #3321 for the details and a measurement of the per-lookup cost).Tests
BindingServiceTests#concurrentFirstUseOfBindingsKeepsTheMapConsistent(new): 16 threads callgetBindingPropertiesfor the same 1000 new names in different orders, for 5 rounds, and the test checks that nothing hangs or throws and that no entry is lost.I ran this method in two batches of 30 against the unmodified class, where it failed 29 and 27 times (mostly
NullPointerExceptions from the corrupted tree, otherwise lost entries or a thread that never finished), and 30 times against the patched class, where it passed every time. I did that with the 5.0.2 jars outside Maven, because I could not run the Maven build ofmainlocally (its snapshot parent POM was not resolvable for me). So I have not run the rest of the module's tests, includingExplicitBindingTests, which GH-3158 touched; I am relying on CI for those.