Copilot commented on code in PR #7779:
URL: https://github.com/apache/ignite-3/pull/7779#discussion_r2930944032
##########
modules/client-handler/src/main/java/org/apache/ignite/client/handler/ClientResourceRegistry.java:
##########
@@ -138,6 +182,18 @@ public void close() {
res.clear();
+ for (var cleaner : txCleaners.values()) {
+ try {
+ cleaner.clean().join();
+ } catch (Throwable e) {
+ if (ex == null) {
+ ex = new IgniteInternalException(e);
+ } else {
+ ex.addSuppressed(e);
+ }
+ }
+ }
Review Comment:
`ClientResourceRegistry.close()` is called from Netty `channelInactive`
(event loop thread), but it blocks by doing `cleaner.clean().join()` for every
tracked tx. This can stall the event loop during disconnect storms or slow
cleanup, impacting other connections and potentially deadlocking if cleanup
completion depends on the same event loop. Prefer non-blocking cleanup
(fire-and-forget with logging), or aggregate futures and run/await them on a
separate executor with a timeout.
##########
modules/client/src/test/java/org/apache/ignite/client/fakes/FakeTxManager.java:
##########
@@ -295,6 +296,11 @@ public int lockRetryCount() {
return 0;
}
+ @Override
+ public RemotelyTriggeredResourceRegistry resourceRegistry() {
+ return null;
+ }
Review Comment:
`TxManager.resourceRegistry()` is now part of the interface and is used by
server-side code (e.g., direct-tx cleanup registration). Returning `null` here
can lead to NPEs if any test paths exercise that logic. Provide a non-null stub
implementation (for example, a new `RemotelyTriggeredResourceRegistry`) or
throw `UnsupportedOperationException` if the fake is not meant to be used in
such scenarios.
##########
modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/table/ClientTableCommon.java:
##########
@@ -538,6 +539,24 @@ public static TableNotFoundException
tableIdNotFoundException(Integer tableId) {
MESSAGE_TX_ALREADY_FINISHED_DUE_TO_TIMEOUT + " [tx=" + remote + "].");
}
+ // Track this remote enlistment for cleanup if
client disconnects.
+ try {
+ resources.addTxCleaner(txId, tableId,
commitPart, txManager, (IgniteTablesInternal) tables);
Review Comment:
`commitPart` here is the transaction *commit* partition sent by the client,
not the partition that this request actually operates on. Recording enlistments
using `commitPart` will make cleanup target the wrong replication group on most
direct-mapping operations (multi-partition tx), and can even fail cleanup on
disconnect if the local node doesn't host that partition. The cleaner should
record the *actual* partition/group that was enlisted on this node (e.g.,
derive it from the current request's target replication group / computed
partition from the key(s)), not the commit partition.
##########
modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/tx/ClientTxPartitionEnlistmentCleaner.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * 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.client.handler.requests.tx;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.replicator.ZonePartitionId;
+import org.apache.ignite.internal.table.IgniteTablesInternal;
+import org.apache.ignite.internal.table.TableViewInternal;
+import org.apache.ignite.internal.tx.PendingTxPartitionEnlistment;
+import org.apache.ignite.internal.tx.TxManager;
+import org.apache.ignite.internal.tx.impl.EnlistedPartitionGroup;
+
+/**
+ * Helper class to clean up direct transaction enlistments on the client side.
Review Comment:
The class-level Javadoc says "clean up ... on the client side", but this
helper lives in `client-handler` and runs on the server to clean up server-side
state for a client connection. Update the wording to avoid confusion when
maintaining the cleanup flow.
##########
modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/tx/ClientTxPartitionEnlistmentCleaner.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * 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.client.handler.requests.tx;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.replicator.ZonePartitionId;
+import org.apache.ignite.internal.table.IgniteTablesInternal;
+import org.apache.ignite.internal.table.TableViewInternal;
+import org.apache.ignite.internal.tx.PendingTxPartitionEnlistment;
+import org.apache.ignite.internal.tx.TxManager;
+import org.apache.ignite.internal.tx.impl.EnlistedPartitionGroup;
+
+/**
+ * Helper class to clean up direct transaction enlistments on the client side.
+ */
+public class ClientTxPartitionEnlistmentCleaner {
+ private final UUID txId;
+
+ private final TxManager txManager;
+
+ private final IgniteTablesInternal igniteTables;
+
+ private final Map<ZonePartitionId, PendingTxPartitionEnlistment>
enlistedPartitions = new HashMap<>();
+
+ /**
+ * Creates a new instance of the transaction partition enlistment cleaner.
+ *
+ * @param txId Transaction ID.
+ * @param txManager Transaction manager.
+ * @param igniteTables Ignite tables.
+ */
+ public ClientTxPartitionEnlistmentCleaner(UUID txId, TxManager txManager,
IgniteTablesInternal igniteTables) {
+ this.txId = txId;
+ this.txManager = txManager;
+ this.igniteTables = igniteTables;
+ }
+
+ /**
+ * Adds a partition enlistment for the given table and partition.
+ *
+ * @param tableId Table ID.
+ * @param partId Partition ID.
+ */
+ public void addEnlistment(int tableId, int partId) {
+ TableViewInternal table = igniteTables.cachedTable(tableId);
+
+ if (table != null) {
+ ZonePartitionId replicationGroupId =
table.internalTable().targetReplicationGroupId(partId);
+ enlistedPartitions.computeIfAbsent(replicationGroupId, k -> new
PendingTxPartitionEnlistment(null, 0))
+ .addTableId(tableId);
+ }
Review Comment:
`ClientTxPartitionEnlistmentCleaner` is stored per-connection and can be
updated from multiple concurrent partition-operation threads (client requests
are executed on `partitionOperationsExecutor`). However `enlistedPartitions` is
a plain `HashMap` with non-atomic updates, so concurrent `addEnlistment` calls
can race and corrupt state. Make this structure thread-safe (e.g., use
`ConcurrentHashMap` and concurrent sets for tableIds, or synchronize
`addEnlistment`/`clean`) to avoid lost/invalid enlistments and cleanup failures.
##########
modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientTransactionCleanupTest.java:
##########
@@ -0,0 +1,106 @@
+/*
+ * 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.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static
org.apache.ignite.internal.runner.app.client.ItThinClientTransactionsTest.generateKeysForNode;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.internal.app.IgniteImpl;
+import org.apache.ignite.internal.client.table.ClientTable;
+import org.apache.ignite.internal.client.tx.ClientLazyTransaction;
+import org.apache.ignite.internal.tx.LockManager;
+import org.apache.ignite.network.ClusterNode;
+import org.apache.ignite.table.Tuple;
+import org.apache.ignite.table.partition.Partition;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for client transaction cleanup on disconnect.
+ */
+@SuppressWarnings({"resource", "DataFlowIssue"})
+public class ItThinClientTransactionCleanupTest extends
ItAbstractThinClientTest {
+ /**
+ * Tests that locks are released when client disconnects with a
transaction having direct enlistments.
+ */
+ @Test
+ void testClientDisconnectReleasesTxLocksFast() {
+ try (IgniteClient client =
IgniteClient.builder().addresses(getClientAddresses().toArray(new
String[0])).build()) {
+ var table = (ClientTable) client.tables().table(TABLE_NAME);
+ Map<Partition, ClusterNode> map =
table.partitionDistribution().primaryReplicas();
+
+ IgniteImpl server0 = unwrapIgniteImpl(server(0));
+ IgniteImpl server1 = unwrapIgniteImpl(server(1));
+
+ List<Tuple> tuples0 = generateKeysForNode(300, 1, map,
server0.cluster().localNode(), table);
+ List<Tuple> tuples1 = generateKeysForNode(310, 1, map,
server1.cluster().localNode(), table);
+
+ // Perform a mix of operations to trigger direct tx logic.
+ Map<Tuple, Tuple> data = new HashMap<>();
+
+ data.put(tuples0.get(0), val(tuples0.get(0).intValue(0) + ""));
+ data.put(tuples1.get(0), val(tuples1.get(0).intValue(0) + ""));
+
+ ClientLazyTransaction tx0 = (ClientLazyTransaction)
client().transactions().begin();
Review Comment:
The test opens a new IgniteClient in try-with-resources, but starts the
transaction via `client().transactions().begin()` (the shared client from the
base class) instead of the local `client`. Closing the local client then
doesn't necessarily disconnect the transaction being used by the operations, so
the test can pass/fail for the wrong reason. Use the locally created `client`
to begin the transaction (and to perform the operations) so the disconnect
being tested matches the transaction under test.
--
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]