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 c5486a37c1c IGNITE-25868 Add client name configuration (#6645)
c5486a37c1c is described below

commit c5486a37c1c4c85efebebc944dba411cfdfb7cb7
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Fri Sep 26 07:23:28 2025 +0300

    IGNITE-25868 Add client name configuration (#6645)
    
    * Add `IgniteClientConfiguration#name`
    * Propagate to `Ignite#name` and JMX bean name
    * Generate with `AtomicLong` if not specified by the user, for example: 
"client_1", "jdbc_client_2"
      * Fixes JMX conflict when multiple clients exist in one JVM
---
 .../org/apache/ignite/client/IgniteClient.java     | 22 +++++++-
 .../ignite/client/IgniteClientConfiguration.java   | 12 ++++
 .../client/IgniteClientConfigurationImpl.java      | 22 ++++++--
 .../ignite/internal/client/TcpIgniteClient.java    | 25 +++++++--
 .../apache/ignite/client/ClientMetricsTest.java    | 65 +++++++++++++++++++++-
 .../apache/ignite/client/ConfigurationTest.java    |  1 +
 .../org/apache/ignite/client/RetryPolicyTest.java  |  2 +-
 .../apache/ignite/client/TestLoggerFactory.java    |  5 ++
 .../ignite/internal/jdbc/JdbcConnection.java       |  5 +-
 .../ignite/internal/metrics/MetricManagerImpl.java |  8 ++-
 10 files changed, 147 insertions(+), 20 deletions(-)

diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
index 92a0e254464..099833a59f1 100644
--- a/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
+++ b/modules/client/src/main/java/org/apache/ignite/client/IgniteClient.java
@@ -113,6 +113,8 @@ public interface IgniteClient extends Ignite, AutoCloseable 
{
 
         private int sqlPartitionAwarenessMetadataCacheSize = 
DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE;
 
+        private @Nullable String name;
+
         /**
          * Sets the addresses of Ignite server nodes within a cluster. An 
address can be an IP address or a hostname, with or without port.
          * If port is not set then Ignite will use the default one - see 
{@link IgniteClientConfiguration#DFLT_PORT}.
@@ -387,6 +389,23 @@ public interface IgniteClient extends Ignite, 
AutoCloseable {
             return this;
         }
 
+        /**
+         * Sets the client name. Default is {@code null}, which means that 
Ignite will generate a unique name automatically.
+         *
+         * <p>Client name is used for identifying clients in JMX metrics. The 
name is only used locally and is not sent to the server.
+         *
+         * <p>If multiple clients with the same exist in the same JVM, JMX 
metrics will be exposed only for one of them.
+         * Others will log an error.
+         *
+         * @param name Client name.
+         * @return This instance.
+         */
+        public Builder name(@Nullable String name) {
+            this.name = name;
+
+            return this;
+        }
+
         /**
          * Builds the client.
          *
@@ -416,7 +435,8 @@ public interface IgniteClient extends Ignite, AutoCloseable 
{
                     metricsEnabled,
                     authenticator,
                     operationTimeout,
-                    sqlPartitionAwarenessMetadataCacheSize
+                    sqlPartitionAwarenessMetadataCacheSize,
+                    name
             );
 
             return TcpIgniteClient.startAsync(cfg);
diff --git 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
index 98e48cf60ad..ef708867caa 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/client/IgniteClientConfiguration.java
@@ -210,4 +210,16 @@ public interface IgniteClientConfiguration {
      * @return Cache size, in number of entries.
      */
     int sqlPartitionAwarenessMetadataCacheSize();
+
+    /**
+     * Gets the client name. Default is {@code null}, which means that Ignite 
will generate a unique name automatically.
+     *
+     * <p>Client name is used for identifying clients in JMX metrics. The name 
is only used locally and is not sent to the server.
+     *
+     * <p>If multiple clients with the same exist in the same JVM, JMX metrics 
will be exposed only for one of them.
+     * Others will log an error.
+     *
+     * @return Client name.
+     */
+    @Nullable String name();
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
index 9c95a95af05..2be085ee1ea 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/IgniteClientConfigurationImpl.java
@@ -31,7 +31,7 @@ import org.jetbrains.annotations.Nullable;
  */
 public final class IgniteClientConfigurationImpl implements 
IgniteClientConfiguration {
     /** Address finder. */
-    private final IgniteClientAddressFinder addressFinder;
+    private final @Nullable IgniteClientAddressFinder addressFinder;
 
     /** Addresses. */
     private final String[] addresses;
@@ -43,7 +43,7 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
     private final long backgroundReconnectInterval;
 
     /** Async continuation executor. */
-    private final Executor asyncContinuationExecutor;
+    private final @Nullable Executor asyncContinuationExecutor;
 
     /** Heartbeat interval. */
     private final long heartbeatInterval;
@@ -66,6 +66,8 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
 
     private final int sqlPartitionAwarenessMetadataCacheSize;
 
+    private final @Nullable String name;
+
     /**
      * Constructor.
      *
@@ -83,13 +85,14 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
      * @param authenticator Authenticator.
      * @param operationTimeout Operation timeout.
      * @param sqlPartitionAwarenessMetadataCacheSize Size of the cache to 
store partition awareness metadata.
+     * @param name Client name.
      */
     public IgniteClientConfigurationImpl(
-            IgniteClientAddressFinder addressFinder,
+            @Nullable IgniteClientAddressFinder addressFinder,
             String[] addresses,
             long connectTimeout,
             long backgroundReconnectInterval,
-            Executor asyncContinuationExecutor,
+            @Nullable Executor asyncContinuationExecutor,
             long heartbeatInterval,
             long heartbeatTimeout,
             @Nullable RetryPolicy retryPolicy,
@@ -98,7 +101,8 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
             boolean metricsEnabled,
             @Nullable IgniteClientAuthenticator authenticator,
             long operationTimeout,
-            int sqlPartitionAwarenessMetadataCacheSize
+            int sqlPartitionAwarenessMetadataCacheSize,
+            @Nullable String name
     ) {
         this.addressFinder = addressFinder;
 
@@ -117,11 +121,12 @@ public final class IgniteClientConfigurationImpl 
implements IgniteClientConfigur
         this.authenticator = authenticator;
         this.operationTimeout = operationTimeout;
         this.sqlPartitionAwarenessMetadataCacheSize = 
sqlPartitionAwarenessMetadataCacheSize;
+        this.name = name;
     }
 
     /** {@inheritDoc} */
     @Override
-    public IgniteClientAddressFinder addressesFinder() {
+    public @Nullable IgniteClientAddressFinder addressesFinder() {
         return addressFinder;
     }
 
@@ -200,4 +205,9 @@ public final class IgniteClientConfigurationImpl implements 
IgniteClientConfigur
     public int sqlPartitionAwarenessMetadataCacheSize() {
         return sqlPartitionAwarenessMetadataCacheSize;
     }
+
+    @Override
+    public @Nullable String name() {
+        return name;
+    }
 }
diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
index a46dbe2ebce..8b1c206fde5 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpIgniteClient.java
@@ -22,6 +22,7 @@ import static 
org.apache.ignite.lang.ErrorGroups.Client.CONNECTION_ERR;
 
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
 import org.apache.ignite.catalog.IgniteCatalog;
 import org.apache.ignite.client.IgniteClient;
 import org.apache.ignite.client.IgniteClientConfiguration;
@@ -41,6 +42,7 @@ import 
org.apache.ignite.internal.marshaller.ReflectionMarshallersProvider;
 import org.apache.ignite.internal.metrics.MetricManager;
 import org.apache.ignite.internal.metrics.MetricManagerImpl;
 import org.apache.ignite.internal.metrics.exporters.jmx.JmxExporter;
+import org.apache.ignite.internal.tostring.S;
 import org.apache.ignite.lang.ErrorGroups;
 import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.network.ClusterNode;
@@ -56,6 +58,8 @@ import org.jetbrains.annotations.TestOnly;
  * Implementation of {@link IgniteClient} over TCP protocol.
  */
 public class TcpIgniteClient implements IgniteClient {
+    private static final AtomicLong GLOBAL_CONN_ID_GEN = new AtomicLong();
+
     /** Configuration. */
     private final IgniteClientConfiguration cfg;
 
@@ -86,11 +90,11 @@ public class TcpIgniteClient implements IgniteClient {
     /** Cluster. */
     private final ClientCluster cluster;
 
-    /**
-     * Cluster name.
-     */
+    /** Cluster name. */
     private String clusterName;
 
+    private final String clientName;
+
     /**
      * Constructor.
      *
@@ -114,6 +118,11 @@ public class TcpIgniteClient implements IgniteClient {
 
         this.cfg = cfg;
 
+        String cfgName = cfg.name();
+        clientName = cfgName != null
+                ? cfgName
+                : "client_" + GLOBAL_CONN_ID_GEN.incrementAndGet(); // Use 
underscores for JMX compat.
+
         metrics = new ClientMetricSource();
         ch = new ReliableChannel(chFactory, cfg, metrics, 
observableTimeTracker);
         tables = new ClientTables(ch, marshallers, 
cfg.sqlPartitionAwarenessMetadataCacheSize());
@@ -130,7 +139,7 @@ public class TcpIgniteClient implements IgniteClient {
             return null;
         }
 
-        var metricManager = new MetricManagerImpl(ClientUtils.logger(cfg, 
MetricManagerImpl.class));
+        var metricManager = new MetricManagerImpl(ClientUtils.logger(cfg, 
MetricManagerImpl.class), clientName);
 
         metricManager.registerSource(metrics);
         metricManager.enable(metrics);
@@ -234,7 +243,7 @@ public class TcpIgniteClient implements IgniteClient {
     /** {@inheritDoc} */
     @Override
     public String name() {
-        return "thin-client";
+        return clientName;
     }
 
     /** {@inheritDoc} */
@@ -249,6 +258,12 @@ public class TcpIgniteClient implements IgniteClient {
         return ch.connections();
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return S.toString(TcpIgniteClient.class.getSimpleName(), "name", 
clientName, "clusterName", clusterName);
+    }
+
     /**
      * Returns the name of the cluster to which this client is connected to.
      *
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java 
b/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
index 5e7b125f7b7..6eeb24bda12 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/ClientMetricsTest.java
@@ -67,10 +67,11 @@ import org.junit.jupiter.params.provider.ValueSource;
 public class ClientMetricsTest extends BaseIgniteAbstractTest {
     private TestServer server;
     private IgniteClient client;
+    private IgniteClient client2;
 
     @AfterEach
     public void afterEach() throws Exception {
-        closeAll(client, server);
+        closeAll(client2, client, server);
     }
 
     @Test
@@ -165,6 +166,7 @@ public class ClientMetricsTest extends 
BaseIgniteAbstractTest {
                 () -> "handshakesFailedTimeout: " + 
metrics().handshakesFailedTimeout());
     }
 
+    @SuppressWarnings("resource")
     @Test
     public void testRequestsMetrics() throws InterruptedException {
         Function<Integer, Boolean> shouldDropConnection = requestIdx -> 
requestIdx == 5;
@@ -304,10 +306,10 @@ public class ClientMetricsTest extends 
BaseIgniteAbstractTest {
     @ValueSource(booleans = {true, false})
     public void testJmxExport(boolean metricsEnabled) throws Exception {
         server = AbstractClientTest.startServer(1000, new FakeIgnite());
-        client = clientBuilder().metricsEnabled(metricsEnabled).build();
+        client = 
clientBuilder().metricsEnabled(metricsEnabled).name("testJmxExport").build();
         client.tables().tables();
 
-        String beanName = "org.apache.ignite:type=metrics,name=client";
+        String beanName = 
"org.apache.ignite:nodeName=testJmxExport,type=metrics,name=client";
         MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
 
         ObjectName objName = new ObjectName(beanName);
@@ -331,6 +333,63 @@ public class ClientMetricsTest extends 
BaseIgniteAbstractTest {
         assertEquals("java.lang.Long", attribute.getType());
     }
 
+    @Test
+    public void testJmxExportTwoClients() throws Exception {
+        server = AbstractClientTest.startServer(1000, new FakeIgnite());
+
+        // Client names are auto-generated, unless explicitly specified.
+        client = clientBuilder().metricsEnabled(true).build();
+        client2 = clientBuilder().metricsEnabled(true).build();
+
+        client.tables().tables();
+        client2.tables().tables();
+
+        for (var clientName : new String[]{client.name(), client2.name()}) {
+            String beanName = "org.apache.ignite:nodeName=" + clientName + 
",type=metrics,name=client";
+            MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+            ObjectName objName = new ObjectName(beanName);
+            boolean registered = mbeanSrv.isRegistered(objName);
+
+            assertTrue(registered, "Unexpected MBean state: [name=" + beanName 
+ ", registered=" + registered + ']');
+
+            DynamicMBean bean = 
MBeanServerInvocationHandler.newProxyInstance(mbeanSrv, objName, 
DynamicMBean.class, false);
+            assertEquals(1L, bean.getAttribute("ConnectionsActive"));
+            assertEquals(1L, bean.getAttribute("ConnectionsEstablished"));
+        }
+    }
+
+    @Test
+    public void testJmxExportTwoClientsSameName() throws Exception {
+        server = AbstractClientTest.startServer(1000, new FakeIgnite());
+
+        var loggerFactory1 = new TestLoggerFactory("client1");
+        var loggerFactory2 = new TestLoggerFactory("client2");
+
+        String clientName = "testJmxExportTwoClientsSameName";
+        client = 
clientBuilder().metricsEnabled(true).name(clientName).loggerFactory(loggerFactory1).build();
+        client2 = 
clientBuilder().metricsEnabled(true).name(clientName).loggerFactory(loggerFactory2).build();
+
+        client.tables().tables();
+        client2.tables().tables();
+
+        String beanName = "org.apache.ignite:nodeName=" + clientName + 
",type=metrics,name=client";
+        MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+        ObjectName objName = new ObjectName(beanName);
+        boolean registered = mbeanSrv.isRegistered(objName);
+
+        assertTrue(registered, "Unexpected MBean state: [name=" + beanName + 
", registered=" + registered + ']');
+
+        DynamicMBean bean = 
MBeanServerInvocationHandler.newProxyInstance(mbeanSrv, objName, 
DynamicMBean.class, false);
+        assertEquals(1L, bean.getAttribute("ConnectionsActive"));
+        assertEquals(1L, bean.getAttribute("ConnectionsEstablished"));
+
+        // Error is logged, but the client is functional.
+        loggerFactory2.waitForLogContains("MBean for metric set can't be 
created [name=client].", 3_000);
+        loggerFactory1.assertLogDoesNotContain("MBean for metric set can't be 
created");
+    }
+
     private Table oneColumnTable() {
         if (server.ignite().tables().table(TABLE_ONE_COLUMN) == null) {
             ((FakeIgniteTables) 
server.ignite().tables()).createTable(TABLE_ONE_COLUMN);
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java 
b/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
index b306462360f..f37d7c848d8 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/ConfigurationTest.java
@@ -87,6 +87,7 @@ public class ConfigurationTest extends AbstractClientTest {
                 .addresses(addr)
                 .connectTimeout(1234)
                 .addressFinder(() -> new String[]{addr})
+                .name("thin-client")
                 .build();
 
         // Builder can be reused, and it won't affect already created clients.
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java 
b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
index 56db449b17e..573f0915b02 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/RetryPolicyTest.java
@@ -242,7 +242,7 @@ public class RetryPolicyTest extends BaseIgniteAbstractTest 
{
     @Test
     public void testRetryReadPolicyAllOperationsSupported() {
         var plc = new RetryReadPolicy();
-        var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, null, 0, 
0, null, null, null, false, null, 0, 1024);
+        var cfg = new IgniteClientConfigurationImpl(null, null, 0, 0, null, 0, 
0, null, null, null, false, null, 0, 1024, null);
 
         for (var op : ClientOperationType.values()) {
             var ctx = new RetryPolicyContextImpl(cfg, op, 0, null);
diff --git 
a/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java 
b/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
index a8f3f138667..ff2fc9f5934 100644
--- 
a/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
+++ 
b/modules/client/src/test/java/org/apache/ignite/client/TestLoggerFactory.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.client;
 
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.lang.System.Logger;
@@ -46,6 +47,10 @@ public class TestLoggerFactory implements LoggerFactory {
         assertTrue(logContains(msg), this::log);
     }
 
+    void assertLogDoesNotContain(String msg) {
+        assertFalse(logContains(msg), this::log);
+    }
+
     void waitForLogContains(String msg, long timeoutMillis) throws 
InterruptedException {
         assertTrue(
                 IgniteTestUtils.waitForCondition(() -> logContains(msg), 
timeoutMillis),
diff --git 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
index f87e0faa4db..ade01948936 100644
--- 
a/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
+++ 
b/modules/jdbc/src/main/java/org/apache/ignite/internal/jdbc/JdbcConnection.java
@@ -79,6 +79,8 @@ public class JdbcConnection implements Connection {
     /** Network timeout permission. */
     private static final String SET_NETWORK_TIMEOUT_PERM = "setNetworkTimeout";
 
+    private static final AtomicLong GLOBAL_CONN_ID_GEN = new AtomicLong();
+
     /** Statements modification mutex. */
     private final Object stmtsMux = new Object();
 
@@ -187,7 +189,8 @@ public class JdbcConnection implements Connection {
                 false,
                 extractAuthenticationConfiguration(connProps),
                 IgniteClientConfiguration.DFLT_OPERATION_TIMEOUT,
-                
IgniteClientConfiguration.DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE
+                
IgniteClientConfiguration.DFLT_SQL_PARTITION_AWARENESS_METADATA_CACHE_SIZE,
+                "jdbc_client_" + GLOBAL_CONN_ID_GEN.getAndIncrement() // Use 
underscores for JMX compat.
         );
 
         return (TcpIgniteClient) sync(TcpIgniteClient.startAsync(cfg, 
observableTimeTracker));
diff --git 
a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
 
b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
index f681c0b900e..7232da5a606 100644
--- 
a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
+++ 
b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManagerImpl.java
@@ -55,6 +55,7 @@ import 
org.apache.ignite.internal.metrics.exporters.configuration.LogPushExporte
 import 
org.apache.ignite.internal.metrics.exporters.configuration.LogPushExporterView;
 import org.apache.ignite.internal.metrics.exporters.log.LogPushExporter;
 import org.apache.ignite.internal.util.IgniteSpinBusyLock;
+import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.VisibleForTesting;
 
 /**
@@ -80,13 +81,13 @@ public class MetricManagerImpl implements MetricManager {
 
     private volatile Supplier<UUID> clusterIdSupplier;
 
-    private volatile String nodeName;
+    private volatile @Nullable String nodeName;
 
     /**
      * Constructor.
      */
     public MetricManagerImpl() {
-        this(Loggers.forClass(MetricManagerImpl.class));
+        this(Loggers.forClass(MetricManagerImpl.class), null);
     }
 
     /**
@@ -94,9 +95,10 @@ public class MetricManagerImpl implements MetricManager {
      *
      * @param log Logger.
      */
-    public MetricManagerImpl(IgniteLogger log) {
+    public MetricManagerImpl(IgniteLogger log, @Nullable String nodeName) {
         registry = new MetricRegistry();
         this.log = log;
+        this.nodeName = nodeName;
     }
 
     @Override

Reply via email to