Fix Server.Builder#build() silently overriding an explicit AddressResolver - #11
Open
roydahan wants to merge 2 commits into
Open
Fix Server.Builder#build() silently overriding an explicit AddressResolver#11roydahan wants to merge 2 commits into
roydahan wants to merge 2 commits into
Conversation
…olver withMultipleNodesPerIp(true) always installed a fresh NodePerPortResolver at build() time, discarding any resolver previously (or subsequently, once withMultipleNodesPerIp had already been called) configured via withAddressResolver(...) - regardless of the order builder methods were called in. Change withMultipleNodesPerIp(true) to set the NodePerPortResolver eagerly, at the time it is called, instead of forcing it in build(). This matches upstream datastax/simulacron semantics: normal builder "last call wins" behavior, so a withAddressResolver(...) call made after withMultipleNodesPerIp(true) is honored instead of silently discarded. Found while diagnosing a flaky Simulacron-backed integration test in scylladb/java-driver (scylladb/java-driver#1017), where a caller-supplied collision-safe resolver was being replaced by a NodePerPortResolver that always retries the same two ports (49152/49153).
dkropachev
reviewed
Aug 26, 2026
withMultipleNodesPerIp(false) left whatever resolver a preceding withMultipleNodesPerIp(true) had installed in place: a plain enable-then-disable sequence left a stale NodePerPortResolver behind even though multipleNodesPerIp was now false (inconsistent with the PeerMetadataHandler(false) built alongside it), and withAddressResolver(custom).withMultipleNodesPerIp(true).withMultipleNodesPerIp(false) permanently lost the caller's custom resolver, which is a regression versus pre-fix master (where the override was gated on the final value of multipleNodesPerIp at build() time, so a final false never triggered it). Track the resolver most recently set via withAddressResolver(...) separately from the one withMultipleNodesPerIp(true) auto-installs, so withMultipleNodesPerIp(false) can restore it (or the true default when none was ever set) instead of leaving the NodePerPortResolver in place. Addresses review feedback from dkropachev on PR scylladb#11.
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.
Problem
Server.Builder#build()unconditionally replaced the configuredAddressResolverwith a freshNodePerPortResolverwheneverwithMultipleNodesPerIp(true)had been set, regardless of the order in which builder methods were called:This meant a caller who did:
would have
myCustomResolversilently discarded atbuild()time and get aNodePerPortResolverinstead, even thoughwithAddressResolver(...)was called afterwithMultipleNodesPerIp(true). There was no way to combinewithMultipleNodesPerIp(true)(needed for its peer-metadata behavior viaPeerMetadataHandler(multipleNodesPerIp)) with a custom address/port allocation strategy.Fix
withMultipleNodesPerIp(true)now setsthis.addressResolver = new NodePerPortResolver()eagerly, at the time it is called, instead of the resolver being forced inbuild(). This matches the ordinary "last call wins" semantics of every otherBuildersetter, and matches upstreamdatastax/simulacron's behavior in the same method:With this change, a
withAddressResolver(...)call made afterwithMultipleNodesPerIp(true)is honored, while awithMultipleNodesPerIp(true)call made afterwithAddressResolver(...)still overrides it with the defaultNodePerPortResolver(again matching upstream / normal builder semantics).Tests
Added two unit tests to
ServerTest:testMultipleNodesPerIpShouldNotOverrideAddressResolverConfiguredAfterwards- builds aServerwithwithMultipleNodesPerIp(true)followed by a customwithAddressResolver(...), and asserts the custom resolver is actually used to assign the registered node's address (previously this would have gotten aNodePerPortResolver-assigned address instead).testMultipleNodesPerIpShouldOverrideAddressResolverConfiguredBefore- confirms the reverse call order still installs aNodePerPortResolverby default.Ran the full test suite locally (
mvn testacross all modules): 195 tests, 0 failures.Context
Found while diagnosing a flaky Simulacron-backed integration test in scylladb/java-driver: scylladb/java-driver#1017. That PR was closed in favor of fixing the root cause here rather than working around it with retries on the driver side - retrying doesn't reliably help because every retry recreates a
Server, whosebuild()always re-hardcodedNodePerPortResolverstarting at the same two ports (49152/49153), which doesn't help against a port held inTIME_WAIT. Once this is released, a follow-up PR against java-driver will bump the Simulacron dependency and configure a collision-safe resolver alongsidewithMultipleNodesPerIp(true).