This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 85da7c71ea7 IGNITE-27521 Java client: handle multiple endpoints for
the same node (#7745)
85da7c71ea7 is described below
commit 85da7c71ea734ed43c9756fe5bfd8622c8e20251
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Wed Mar 11 14:20:11 2026 +0100
IGNITE-27521 Java client: handle multiple endpoints for the same node
(#7745)
---
.../ignite/internal/client/ReliableChannel.java | 20 +++++++++-
.../ignite/client/ClientDnsDiscoveryTest.java | 43 +++++++++++++++++++++-
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git
a/modules/client/src/main/java/org/apache/ignite/internal/client/ReliableChannel.java
b/modules/client/src/main/java/org/apache/ignite/internal/client/ReliableChannel.java
index 9a6feff1995..f5774ea0b03 100644
---
a/modules/client/src/main/java/org/apache/ignite/internal/client/ReliableChannel.java
+++
b/modules/client/src/main/java/org/apache/ignite/internal/client/ReliableChannel.java
@@ -229,8 +229,14 @@ public final class ReliableChannel implements
AutoCloseable {
*/
public List<ClusterNode> connections() {
List<ClusterNode> res = new ArrayList<>(channels.size());
+ Set<ClientChannelHolder> set = new HashSet<>();
+
+ for (var holder : channels) {
+ if (!set.add(holder)) {
+ // Duplicate address in config.
+ continue;
+ }
- for (var holder : nodeChannelsByName.values()) {
var chFut = holder.chFut;
if (chFut != null) {
@@ -961,6 +967,18 @@ public final class ReliableChannel implements
AutoCloseable {
ClusterNode newNode = ch.protocolContext().clusterNode();
+ // Check if another endpoint already connected to this
node.
+ ClientChannelHolder existingHolder =
nodeChannelsByName.get(newNode.name());
+ if (existingHolder != null && existingHolder != this) {
+ log.warn("Multiple distinct endpoints resolve to the
same server node [nodeName={}, nodeId={}, "
+ + "existingEndpoint={}, newEndpoint={}]. This
represents a misconfiguration. "
+ + "Both connections will remain active to
avoid disrupting ongoing operations.",
+ newNode.name(),
+ newNode.id(),
+ existingHolder.chCfg.getAddress(),
+ chCfg.getAddress());
+ }
+
// There could be multiple holders map to the same
serverNodeId if user provide the same
// address multiple times in configuration.
nodeChannelsByName.put(newNode.name(), this);
diff --git
a/modules/client/src/test/java/org/apache/ignite/client/ClientDnsDiscoveryTest.java
b/modules/client/src/test/java/org/apache/ignite/client/ClientDnsDiscoveryTest.java
index 07101010a2c..eb02a0fc835 100644
---
a/modules/client/src/test/java/org/apache/ignite/client/ClientDnsDiscoveryTest.java
+++
b/modules/client/src/test/java/org/apache/ignite/client/ClientDnsDiscoveryTest.java
@@ -37,7 +37,9 @@ import org.apache.ignite.internal.client.ReliableChannel;
import org.apache.ignite.internal.client.TcpIgniteClient;
import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
+import org.apache.ignite.lang.LoggerFactory;
import org.apache.ignite.network.ClusterNode;
+import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -234,10 +236,49 @@ class ClientDnsDiscoveryTest extends
BaseIgniteAbstractTest {
}
}
+ @Test
+ void testMultipleEndpointsSameNodeLogsWarning() throws
InterruptedException {
+ // Two distinct IPs that both resolve to the same node (server3).
+ AtomicReference<String[]> resolvedAddressesRef = new
AtomicReference<>(new String[]{loopbackAddress, hostAddress});
+ TestLoggerFactory loggerFactory = new TestLoggerFactory("test-client");
+ String[] addresses = {"my-cluster:" + server3.port()};
+
+ var cfg = getClientConfiguration(addresses, 0L, resolvedAddressesRef,
loggerFactory);
+
+ List<?> channelHolders;
+
+ try (var client = TcpIgniteClient.startAsync(cfg).join()) {
+ assertDoesNotThrow(() -> client.tables().tables());
+ loggerFactory.waitForLogMatches(".*Multiple distinct endpoints
resolve to the same server node.*", 5000);
+
+ List<ClusterNode> connections = client.connections();
+ assertEquals(2, connections.size());
+ assertEquals("server3", connections.get(0).name());
+ assertEquals("server3", connections.get(1).name());
+
+ channelHolders = IgniteTestUtils.getFieldValue(((TcpIgniteClient)
client).channel(), "channels");
+ assertEquals(2, channelHolders.size());
+ }
+
+ for (Object holder : channelHolders) {
+ boolean isClosed = IgniteTestUtils.getFieldValue(holder, "close");
+ assertTrue(isClosed, "Channel holder should be closed after client
close");
+ }
+ }
+
private static IgniteClientConfigurationImpl getClientConfiguration(
String[] addresses,
long backgroundReResolveAddressesInterval,
AtomicReference<String[]> resolvedAddressesRef
+ ) {
+ return getClientConfiguration(addresses,
backgroundReResolveAddressesInterval, resolvedAddressesRef, null);
+ }
+
+ private static IgniteClientConfigurationImpl getClientConfiguration(
+ String[] addresses,
+ long backgroundReResolveAddressesInterval,
+ AtomicReference<String[]> resolvedAddressesRef,
+ @Nullable LoggerFactory loggerFactory
) {
InetAddressResolver addressResolver = (host, port) -> {
if ("my-cluster".equals(host)) {
@@ -258,7 +299,7 @@ class ClientDnsDiscoveryTest extends BaseIgniteAbstractTest
{
50,
50,
new RetryLimitPolicy(),
- null,
+ loggerFactory,
null,
false,
null,