You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2026/03/11 13:20:17 UTC
(ignite-3) branch main updated: IGNITE-27521 Java client: handle multiple endpoints for the same node (#7745)
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 <pt...@apache.org>
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,