Copilot commented on code in PR #13791:
URL: https://github.com/apache/ignite/pull/13791#discussion_r4501187336
##########
modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/internal/ConnectionClientPool.java:
##########
@@ -355,33 +401,67 @@ public GridCommunicationClient reserveClient(ClusterNode
node, int connIdx) thro
else
fut = oldFut;
- long clientReserveWaitTimeout = registry != null ?
registry.getSystemWorkerBlockedTimeout() / 3
- : cfg.connectionTimeout() / 3;
+ if (ownClient != null)
+ client = ownClient;
+
+ if (client == null) {
+ long perWaitTimeout = registry != null ?
+ registry.getSystemWorkerBlockedTimeout() / 3 :
cfg.connectionTimeout() / 3;
- long currTimeout = System.currentTimeMillis();
+ long currTimeout = System.currentTimeMillis();
- // This cycle will eventually quit when future is
completed by concurrent thread reserving client.
- while (true) {
- try {
- client = fut.get(clientReserveWaitTimeout,
TimeUnit.MILLISECONDS);
+ // This cycle will eventually quit when future is
completed by concurrent thread reserving client.
+ while (true) {
+ // A concurrent thread may have established the
client while this thread was
+ // waiting: return it instead of waiting further
or giving up, even if the
+ // reserve budget has already elapsed — the budget
bounds waiting for a
+ // connection, not the availability of an existing
client.
+ GridCommunicationClient[] curClients0 =
clients.get(nodeId);
- break;
- }
- catch (IgniteFutureTimeoutCheckedException ignored) {
- currTimeout += clientReserveWaitTimeout;
-
- if (log.isDebugEnabled()) {
- log.debug(
- "Still waiting for reestablishing
connection to node " +
- "[nodeId=" + node.id() + ",
waitingTime=" + currTimeout + "ms]"
- );
+ GridCommunicationClient curClient = curClients0 !=
null && connIdx < curClients0.length ?
+ curClients0[connIdx] : null;
+
+ if (curClient != null) {
+ client = curClient;
+
+ break;
Review Comment:
This shortcut can return a client after `stop()` has completed the shared
future exceptionally. The creator can publish through `addNodeClient` after
shutdown, letting a waiter take the client here before the creator rolls back
publication and closes it. Coordinate publication and future completion with
shutdown so a client whose future completed with a stop error is never exposed,
and cover a concurrent waiter in the shutdown regression test.
##########
modules/core/src/main/java/org/apache/ignite/spi/communication/tcp/internal/ConnectionClientPool.java:
##########
@@ -355,33 +401,67 @@ public GridCommunicationClient reserveClient(ClusterNode
node, int connIdx) thro
else
fut = oldFut;
- long clientReserveWaitTimeout = registry != null ?
registry.getSystemWorkerBlockedTimeout() / 3
- : cfg.connectionTimeout() / 3;
+ if (ownClient != null)
+ client = ownClient;
+
+ if (client == null) {
+ long perWaitTimeout = registry != null ?
+ registry.getSystemWorkerBlockedTimeout() / 3 :
cfg.connectionTimeout() / 3;
- long currTimeout = System.currentTimeMillis();
+ long currTimeout = System.currentTimeMillis();
- // This cycle will eventually quit when future is
completed by concurrent thread reserving client.
- while (true) {
- try {
- client = fut.get(clientReserveWaitTimeout,
TimeUnit.MILLISECONDS);
+ // This cycle will eventually quit when future is
completed by concurrent thread reserving client.
+ while (true) {
+ // A concurrent thread may have established the
client while this thread was
+ // waiting: return it instead of waiting further
or giving up, even if the
+ // reserve budget has already elapsed — the budget
bounds waiting for a
+ // connection, not the availability of an existing
client.
+ GridCommunicationClient[] curClients0 =
clients.get(nodeId);
- break;
- }
- catch (IgniteFutureTimeoutCheckedException ignored) {
- currTimeout += clientReserveWaitTimeout;
-
- if (log.isDebugEnabled()) {
- log.debug(
- "Still waiting for reestablishing
connection to node " +
- "[nodeId=" + node.id() + ",
waitingTime=" + currTimeout + "ms]"
- );
+ GridCommunicationClient curClient = curClients0 !=
null && connIdx < curClients0.length ?
+ curClients0[connIdx] : null;
+
+ if (curClient != null) {
+ client = curClient;
+
+ break;
+ }
+
+ // Recompute the remaining budget on every
iteration and cap the individual
+ // wait by it, so that the total reserve budget is
enforced even when a single
+ // wait would overshoot it. When the per-wait
timeout is not configured (0),
+ // e.g. because the system worker blocked timeout
is set to a non-positive
+ // value, wait the full remaining budget instead
of aborting before it has
+ // elapsed.
+ long remainingReserveBudget = totalReserveTimeout
- reserveElapsedMillis(reserveStartNanos);
+
+ long clientReserveWaitTimeout = perWaitTimeout > 0
?
+ Math.min(perWaitTimeout,
remainingReserveBudget) : remainingReserveBudget;
+
+ if (clientReserveWaitTimeout <= 0)
+ throw reserveTimeoutException(node,
totalReserveTimeout);
Review Comment:
When client creation fails after consuming the reservation budget, this
check throws a generic reservation timeout before reading the failure already
stored in `fut`, losing the original exception and its causes. It can likewise
hide a stop error when a slow creator returns after shutdown. Resolve an
already-completed future before rejecting a further wait for lack of budget.
##########
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:
This unbounded `get()` can keep the creating thread blocked after the
establishment deadline expires: closing the channel does not complete the
future until the NIO worker processes registration. A live worker stalled in a
synchronous callback is enough to trigger this; the reservation budget does not
protect the creator. Bound the future wait by the remaining establishment
deadline and safely clean up any session that completes after the wait expires.
--
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]