mroeppis commented on code in PR #13791:
URL: https://github.com/apache/ignite/pull/13791#discussion_r4504668170


##########
modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/internal/GridNioServerWrapper.java:
##########
@@ -489,50 +484,82 @@ public GridNioSession createNioSession(ClusterNode node, 
int connIdx) throws Ign
                             throw new IgniteCheckedException("Local node has 
not been started or " +
                                 "fully initialized [isStopping=" + 
stateProvider.isStopping() + ']');
 
+                        // The handshake window, re-verified against the 
strategy's total timeout (the call
+                        // does not advance the backoff state, so the 
per-attempt backoff progression is
+                        // unchanged).
                         timeout = connTimeoutStgy.nextTimeout(timeout);
 
-                        rcvCnt = safeTcpHandshake(ch,
-                            node.id(),
-                            timeout,
-                            sslMeta,
-                            new HandshakeMessage(locNode.id(),
-                                recoveryDesc.incrementConnectCount(),
-                                recoveryDesc.received(),
-                                connIdx));
-
-                        if (rcvCnt == ALREADY_CONNECTED)
-                            return null;
-                        else if (rcvCnt == NODE_STOPPING) {
-                            // Safe to remap on remote node stopping.
-                            throw new ClusterTopologyCheckedException("Remote 
node started stop procedure: " + node.id());
+                        long handshakeTimeout = timeout;
+
+                        // The deadline covers the handshake and the NIO 
session registration: when it
+                        // elapses, the channel is closed, which aborts the 
in-progress handshake and makes
+                        // the registration fail with a ClosedChannelException 
- the latter only once the
+                        // responsible NIO worker processes the queued 
registration operation, so the
+                        // registration wait is bounded only under that 
precondition. It is armed after the
+                        // socket connect, which is bounded by its own connect 
timeout, so the connect does
+                        // not consume the deadline and the handshake keeps 
the full window the strategy
+                        // allocated to it.
+                        HandshakeTimeoutObject timeoutObj = new 
HandshakeTimeoutObject(ch);
+
+                        handshakeTimeoutExecutorService.schedule(timeoutObj, 
handshakeTimeout, TimeUnit.MILLISECONDS);
+
+                        boolean deadlineCancelled = false;
+
+                        try {
+                            rcvCnt = safeTcpHandshake(ch,
+                                node.id(),
+                                sslMeta,
+                                new HandshakeMessage(locNode.id(),
+                                    recoveryDesc.incrementConnectCount(),
+                                    recoveryDesc.received(),
+                                    connIdx));
+
+                            if (rcvCnt == ALREADY_CONNECTED)
+                                return null;
+                            else if (rcvCnt == NODE_STOPPING) {
+                                // Safe to remap on remote node stopping.
+                                throw new 
ClusterTopologyCheckedException("Remote node started stop procedure: " + 
node.id());
+                            }
+                            else if (rcvCnt == UNKNOWN_NODE)
+                                throw new IgniteCheckedException("Remote node 
does not observe current node " +
+                                    "in topology : " + node.id());
+                            else if (rcvCnt == NEED_WAIT) {
+                                // Should wait for discovery lag without 
applying connTimeoutStgy, otherwise cache operations
+                                // may hang on waiting inexistent topology, 
see IgniteClientConnectTest for test
+                                // scenarios with delayed client node join.
+                                if (log.isDebugEnabled())
+                                    log.debug("NEED_WAIT received, handshake 
after delay [node = "
+                                        + node + ", outOfTopologyDelay = " + 
DFLT_NEED_WAIT_DELAY + "ms]");
+
+                                // The peer-requested discovery lag delay is 
not a step of the connection
+                                // establishment: end the deadline before the 
wait, the next attempt arms a
+                                // fresh one.
+                                deadlineCancelled = timeoutObj.cancel();
+
+                                U.sleep(DFLT_NEED_WAIT_DELAY);
+
+                                continue;
+                            }
+                            else if (rcvCnt < 0)
+                                throw new IgniteCheckedException("Unsupported 
negative receivedCount [rcvCnt=" + rcvCnt +
+                                    ", senderNode=" + node + ']');
+
+                            recoveryDesc.onHandshake(rcvCnt);
+
+                            meta.put(CONSISTENT_ID_META, node.consistentId());
+                            meta.put(CONN_IDX_META, connKey);
+                            meta.put(GridNioServer.RECOVERY_DESC_META_KEY, 
recoveryDesc);
+                            meta.put(MSG_SER_CTX.ordinal(), 
buildForPeers(stateProvider.ignite(), node));
+
+                            ses = nioSrv.createSession(ch, meta, false, 
null).get();

Review Comment:
   The call to `get()` has no timeout.
   
   The connection establishment process already has a deadline, and a scheduled 
timeout task closes the underlying channel when that deadline expires.
   
   However, closing the channel does not guarantee that the future returned by 
`createSession()` completes immediately.
   
   Session registration is processed by a NIO worker. If that worker is blocked 
or otherwise unable to process its queued registration operation, the following 
can happen:
   1. The connection thread submits the session registration.
   2. The NIO worker does not process the registration because it is stalled.
   3. The connection establishment deadline expires.
   4. The timeout task closes the channel.
   5. The connection thread remains blocked in `sesFut.get()` because the 
registration future has not completed.
   
   The timeout has closed the socket, but it has not necessarily released the 
thread waiting for the registration result.
   
   This distinction is essential: closing a channel and completing an 
asynchronous operation are not the same thing.
   
   My original implementation therefore does not actually guarantee that 
connection establishment would terminate within its configured deadline.
   
   **Why the finding is valid**: An unbounded wait on a future controlled by 
another worker defeats the connection deadline. The waiting thread needs its 
own bounded wait, based on the remaining deadline.
   
   My revised implementation does exactly that and also closes a session if 
registration completes after the timeout.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to