This is an automated email from the ASF dual-hosted git repository.

sk0x50 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 0015ec4f1a1 IGNITE-26624 Improve error message when thin client 
connects to not initialized cluster (#6755)
0015ec4f1a1 is described below

commit 0015ec4f1a1b066a12560a7739a296be94656f30
Author: Egor <[email protected]>
AuthorDate: Tue Oct 14 20:35:50 2025 +0400

    IGNITE-26624 Improve error message when thin client connects to not 
initialized cluster (#6755)
    
    Co-authored-by: Egor Kuts <[email protected]>
---
 .../ignite/internal/client/TcpClientChannel.java   |  16 ++-
 .../ItThinClientUninitializedClusterTest.java      | 124 +++++++++++++++++++++
 2 files changed, 137 insertions(+), 3 deletions(-)

diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
index 50a00ea8283..bda28cdd2b9 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/TcpClientChannel.java
@@ -156,6 +156,9 @@ class TcpClientChannel implements ClientChannel, 
ClientMessageHandler, ClientCon
     /** Last receive operation timestamp. */
     private volatile long lastReceiveMillis;
 
+    /** Whether tcp connection was established. */
+    private volatile boolean tcpConnectionEstablished;
+
     /**
      * Constructor.
      *
@@ -194,6 +197,8 @@ class TcpClientChannel implements ClientChannel, 
ClientMessageHandler, ClientCon
                         log.debug("Connection established [remoteAddress=" + 
s.remoteAddress() + ']');
                     }
 
+                    tcpConnectionEstablished = true;
+
                     ClientTimeoutWorker.INSTANCE.registerClientChannel(this);
 
                     sock = s;
@@ -264,8 +269,14 @@ class TcpClientChannel implements ClientChannel, 
ClientMessageHandler, ClientCon
         }
 
         for (TimeoutObjectImpl pendingReq : pendingReqs.values()) {
-            pendingReq.future().completeExceptionally(
-                    new IgniteClientConnectionException(CONNECTION_ERR, 
"Channel is closed", endpoint(), cause));
+            if (tcpConnectionEstablished && lastReceiveMillis == 0) {
+                pendingReq.future().completeExceptionally(
+                        new IgniteClientConnectionException(CONNECTION_ERR,
+                                "Channel is closed, cluster might not have 
been initialised", endpoint(), cause));
+            } else {
+                pendingReq.future().completeExceptionally(
+                        new IgniteClientConnectionException(CONNECTION_ERR, 
"Channel is closed", endpoint(), cause));
+            }
         }
 
         for (CompletableFuture<PayloadInputChannel> handler : 
notificationHandlers.values()) {
@@ -665,7 +676,6 @@ class TcpClientChannel implements ClientChannel, 
ClientMessageHandler, ClientCon
                         metrics.handshakesFailedTimeoutIncrement();
                         throw new 
IgniteClientConnectionException(CONNECTION_ERR, "Handshake timeout", 
endpoint(), err);
                     }
-
                     metrics.handshakesFailedIncrement();
                     throw new IgniteClientConnectionException(CONNECTION_ERR, 
"Handshake error", endpoint(), err);
                 });
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientUninitializedClusterTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientUninitializedClusterTest.java
new file mode 100644
index 00000000000..2cf623583a5
--- /dev/null
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientUninitializedClusterTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.runner.app.client;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.lang.IgniteException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+
+/**
+ * Tests thin client connection behavior when the cluster is not initialized.
+ * This test simulates a scenario similar to a docker container proxy that 
accepts connections
+ * and immediately drops them since the node's process starts listening on the 
port only after initialization.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItThinClientUninitializedClusterTest extends 
BaseIgniteAbstractTest {
+    private static final int CLIENT_PORT = 10809;
+    private EventLoopGroup bossGroup;
+    private EventLoopGroup workerGroup;
+    private Channel serverChannel;
+
+    @BeforeEach
+    void startDummyNettyServer() throws InterruptedException {
+        // Set up a mock tcp socket listener at CLIENT_PORT.
+        bossGroup = new NioEventLoopGroup(1);
+
+        workerGroup = new NioEventLoopGroup();
+
+        ServerBootstrap bootstrap = new ServerBootstrap()
+                .group(bossGroup, workerGroup)
+                .channel(NioServerSocketChannel.class)
+                .childHandler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel ch) {
+                        // Immediately close every new connection.
+                        ch.pipeline().addLast(new 
ChannelInboundHandlerAdapter() {
+                            @Override
+                            public void channelActive(ChannelHandlerContext 
ctx) {
+                                ctx.close();
+                            }
+                        });
+                    }
+                })
+                .childOption(ChannelOption.SO_KEEPALIVE, false);
+
+        ChannelFuture bindFuture = bootstrap.bind("127.0.0.1", 
CLIENT_PORT).sync();
+        serverChannel = bindFuture.channel();
+    }
+
+    @Test
+    void testThinClientConnectionToNotInitializedClusterThrowsException() {
+        IgniteException ex = assertThrows(
+                IgniteException.class,
+                () -> {
+                    try (IgniteClient client = IgniteClient.builder()
+                            .addresses("127.0.0.1:" + CLIENT_PORT)
+                            .build()) {
+                        // Client construction should fail after tcp handshake.
+                    }
+                }
+        );
+
+        // Check that the cause chain contains the helpful error message.
+        boolean foundMessage = false;
+        Throwable cause = ex;
+        while (cause != null) {
+            if (cause.getMessage() != null && 
cause.getMessage().contains("cluster might not have been initialised")) {
+                foundMessage = true;
+                break;
+            }
+            cause = cause.getCause();
+        }
+
+        assertTrue(foundMessage, "Expected to find 'cluster might not have 
been initialised' in exception cause chain");
+    }
+
+    @AfterEach
+    void stopDummyNettyServer() throws InterruptedException {
+        // Stop the listener.
+        if (serverChannel != null) {
+            serverChannel.close().sync();
+        }
+        if (workerGroup != null) {
+            workerGroup.shutdownGracefully().sync();
+        }
+        if (bossGroup != null) {
+            bossGroup.shutdownGracefully().sync();
+        }
+    }
+}

Reply via email to