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/24 14:37:14 UTC

(ignite-3) branch main updated: IGNITE-27522 .NET: handle multiple endpoints for the same node (#7848)

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 <pt...@apache.org>
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);
 }