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 3f709393f05 IGNITE-27522 .NET: handle multiple endpoints for the same 
node (#7848)
3f709393f05 is described below

commit 3f709393f052be366e8fe80c17d2a01084153f29
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Tue Mar 24 15:37:08 2026 +0100

    IGNITE-27522 .NET: handle multiple endpoints for the same node (#7848)
    
    * Log a warning, ensure correct cleanup
    * Do not close "duplicate" connection because it might be in use by 
transactions or queries
---
 .../ClientFailoverSocketTests.cs                   | 38 ++++++++++++++++++++++
 .../dotnet/Apache.Ignite.Tests/IgniteServerBase.cs |  6 ++++
 .../Apache.Ignite/Internal/ClientFailoverSocket.cs | 13 +++++++-
 .../dotnet/Apache.Ignite/Internal/LogMessages.cs   |  9 +++++
 4 files changed, 65 insertions(+), 1 deletion(-)

diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/ClientFailoverSocketTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/ClientFailoverSocketTests.cs
index 41f0a8ad60d..6a580901fb3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/ClientFailoverSocketTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ClientFailoverSocketTests.cs
@@ -19,7 +19,9 @@ namespace Apache.Ignite.Tests;
 
 using System;
 using System.Threading.Tasks;
+using Common;
 using Internal;
+using Microsoft.Extensions.Logging;
 using NUnit.Framework;
 
 /// <summary>
@@ -51,4 +53,40 @@ public class ClientFailoverSocketTests
         Assert.AreEqual(0, tables.Count);
         Assert.AreEqual(1, client.GetConnections().Count);
     }
+
+    [Test]
+    public async Task TestMultipleEndpointsSameNodeLogsWarning()
+    {
+        ClientFailoverSocket.ResetGlobalEndpointIndex();
+
+        using var server = new FakeServer(nodeName: "test-node")
+        {
+            AllowMultipleConnections = true
+        };
+
+        var logger = new ListLoggerFactory([LogLevel.Warning]);
+
+        var clientCfg = new IgniteClientConfiguration
+        {
+            Endpoints = { $"127.0.0.1:{server.Port}", 
$"localhost:{server.Port}" },
+            LoggerFactory = logger,
+            ReconnectInterval = TimeSpan.Zero
+        };
+
+        using var client = await IgniteClient.StartAsync(clientCfg);
+
+        client.WaitForConnections(2);
+        server.WaitForConnections(2);
+
+        var log = logger.GetLogString();
+        StringAssert.Contains("Multiple distinct endpoints resolve to the same 
server node", log);
+        StringAssert.Contains("test-node", log);
+
+        // ReSharper disable once DisposeOnUsingVariable
+        client.Dispose();
+
+        // Ensure that duplicate connections are cleaned up properly.
+        client.WaitForConnections(0);
+        server.WaitForConnections(0);
+    }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServerBase.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServerBase.cs
index a8cb7682a36..750afe130a1 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServerBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServerBase.cs
@@ -20,12 +20,14 @@ namespace Apache.Ignite.Tests;
 using System;
 using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
 using Internal.Buffers;
 using NUnit.Framework;
+using NUnit.Framework.Internal;
 
 [SuppressMessage("Design", "CA1034:Nested types should not be visible", 
Justification = "Tests.")]
 public abstract class IgniteServerBase : IDisposable
@@ -69,6 +71,8 @@ public abstract class IgniteServerBase : IDisposable
         set => _dropNewConnections = value;
     }
 
+    public int ConnectionCount => _handlers.Keys.Count(x => x.Connected);
+
     protected Socket Listener => _listener;
 
     public void DropExistingConnection()
@@ -79,6 +83,8 @@ public abstract class IgniteServerBase : IDisposable
         }
     }
 
+    public void WaitForConnections(int count) => TestUtils.WaitForCondition(() 
=> ConnectionCount == count, 5000);
+
     public void Dispose()
     {
         Dispose(true);
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
index 307ba622f63..cbeca14e367 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
@@ -778,10 +778,21 @@ namespace Apache.Ignite.Internal
                         $"Cluster ID mismatch: expected={_clusterId}, 
actual={socket.ConnectionContext.ClusterIds.StringJoin()}");
                 }
 
+                // Check if another endpoint already connected to this node.
+                var nodeName = socket.ConnectionContext.ClusterNode.Name;
+                if (_endpointsByName.TryGetValue(nodeName, out var 
existingEndpoint) && existingEndpoint != endpoint)
+                {
+                    _logger.LogMultipleEndpointsSameNodeWarn(
+                        nodeName,
+                        socket.ConnectionContext.ClusterNode.Id,
+                        existingEndpoint.EndPoint,
+                        endpoint.EndPoint);
+                }
+
                 // First update mapping, then set socket:
                 // - GetConnections does not lock and should not return 
connections that are not in the map.
                 // - GetSocketAsync uses a lock and will not see a null/old 
socket.
-                _endpointsByName[socket.ConnectionContext.ClusterNode.Name] = 
endpoint;
+                _endpointsByName[nodeName] = endpoint;
 
                 endpoint.Socket = socket;
 
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs
index ff6c8149a2d..267993eec58 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/LogMessages.cs
@@ -279,4 +279,13 @@ internal static partial class LogMessages
         Level = LogLevel.Trace,
         EventId = 1038)]
     internal static partial void LogEndpointListUpdatedTrace(this ILogger 
logger, string added, string removed);
+
+    [LoggerMessage(
+        Message = "Multiple distinct endpoints resolve to the same server node 
[nodeName={NodeName}, nodeId={NodeId}, " +
+                  "existingEndpoint={ExistingEndpoint}, 
newEndpoint={NewEndpoint}]. This represents a misconfiguration. " +
+                  "Both connections will remain active to avoid disrupting 
ongoing operations.",
+        Level = LogLevel.Warning,
+        EventId = 1039)]
+    internal static partial void LogMultipleEndpointsSameNodeWarn(
+        this ILogger logger, string nodeName, Guid nodeId, EndPoint 
existingEndpoint, EndPoint newEndpoint);
 }

Reply via email to