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);
}