Copilot commented on code in PR #7999:
URL: https://github.com/apache/ignite-3/pull/7999#discussion_r3098210896


##########
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/DefaultTablePartitionReplicaProcessor.java:
##########
@@ -1669,17 +1772,23 @@ private <T> CompletableFuture<T> continueResolvingByPk(
      * Appends an operation to prevent the race between commit/rollback and 
the operation execution.
      *
      * @param txId Transaction id.
+     * @param commitPartitionId Commit partition id.
      * @param requestType Request type.
      * @param full {@code True} if a full transaction and can be immediately 
committed.
      * @param op Operation closure.
      * @return A future object representing the result of the given operation.
      */
     private <T> CompletableFuture<T> appendTxCommand(
             UUID txId,
+            @Nullable ZonePartitionId commitPartitionId,
             RequestType requestType,
             boolean full,
             Supplier<CompletableFuture<T>> op
     ) {
+        PendingTxContext txCtx = requestType.isRwRead() ? null :
+                pendingTransactions.computeIfAbsent(txId,
+                        id -> new PendingTxContext(new CompletableFuture<>(), 
commitPartitionId));

Review Comment:
   `appendTxCommand` accepts a `@Nullable` `commitPartitionId`, but for 
non-RW-read requests it is stored into `PendingTxContext` and later used for 
tx-state resolution. Relying on `assert commitPartId != null` in 
`PendingTxContext` is unsafe when assertions are disabled and can lead to NPEs 
at runtime. Consider enforcing non-null explicitly (e.g., 
`Objects.requireNonNull`) when `!requestType.isRwRead()` and/or making the 
parameter non-null for those request types.
   ```suggestion
                   pendingTransactions.computeIfAbsent(txId, id -> new 
PendingTxContext(
                           new CompletableFuture<>(),
                           requireNonNull(commitPartitionId, "commitPartitionId 
must not be null for non-RW-read requests")
                   ));
   ```



##########
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/DefaultTablePartitionReplicaProcessor.java:
##########
@@ -4085,4 +4205,20 @@ public WriteIntentResolutionResult(boolean 
writeIntentReadable, TransactionMeta
             this.transactionMeta = transactionMeta;
         }
     }
+
+    /**
+     * Represents waiting for cleanup transactions context.
+     */
+    private static class PendingTxContext {
+        final CompletableFuture<Void> cleanupFut;
+        final ZonePartitionId commitPartId;
+
+        PendingTxContext(CompletableFuture<Void> cleanupFut, ZonePartitionId 
commitPartId) {
+            assert cleanupFut != null;
+            assert commitPartId != null;
+
+            this.cleanupFut = cleanupFut;
+            this.commitPartId = commitPartId;

Review Comment:
   `PendingTxContext` uses `assert` statements for null checks on constructor 
args. Because assertions may be disabled in production, these checks won’t 
protect against nulls and could result in later NPEs (e.g., during tx-state 
resolution). Prefer hard validation (e.g., `Objects.requireNonNull`) and/or 
non-null annotations on fields/params.
   ```suggestion
               this.cleanupFut = requireNonNull(cleanupFut, "cleanupFut");
               this.commitPartId = requireNonNull(commitPartId, "commitPartId");
   ```



##########
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/DefaultTablePartitionReplicaProcessor.java:
##########
@@ -581,6 +597,80 @@ private void replicaTouch(UUID txId, UUID coordinatorId, 
ZonePartitionId commitP
                 .build());
     }
 
+    private <T> CompletableFuture<T> resolvePendingTransactions(
+            HybridTimestamp requestTime,
+            HybridTimestamp currentSafeTime,
+            Function<HybridTimestamp, CompletableFuture<T>> action
+    ) {
+        // Wait for currentSafeTime >= requestTime to avoid out-of-order 
transactions arrival.
+        if (currentSafeTime.compareTo(requestTime) < 0) {
+            return safeTime.waitFor(requestTime)
+                    .thenComposeAsync(
+                            unused -> resolvePendingTransactions(requestTime, 
safeTime.current(), action),
+                            partitionOperationsExecutor);
+        }
+
+        assert currentSafeTime.compareTo(requestTime) >= 0 : "currentSafeTime 
< lowerBoundTimestamp";
+        assert currentSafeTime.compareTo(safeTime.current()) <= 0 : 
"currentSafeTime > safeTime";
+
+        // Stable committed snapshot is ensured after resolving pending 
transactions state.
+        UUID uppedBoundTxId = TransactionIds.transactionId(currentSafeTime, 
Integer.MAX_VALUE, TxPriority.NORMAL);
+        ConcurrentNavigableMap<UUID, PendingTxContext> txToWait = 
pendingTransactions.headMap(uppedBoundTxId, true);

Review Comment:
   Variable name `uppedBoundTxId` looks like a typo (probably meant 
`upperBoundTxId`). Renaming would improve readability since this value is used 
as an upper bound for `pendingTransactions.headMap(...)`.
   ```suggestion
           UUID upperBoundTxId = TransactionIds.transactionId(currentSafeTime, 
Integer.MAX_VALUE, TxPriority.NORMAL);
           ConcurrentNavigableMap<UUID, PendingTxContext> txToWait = 
pendingTransactions.headMap(upperBoundTxId, true);
   ```



##########
modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransaction.java:
##########
@@ -327,7 +339,12 @@ public CompletableFuture<Void> commitAsync() {
             if (finishFut != null) {
                 return finishFut;
             } else {
-                finishFut = new CompletableFuture<>();
+                if (implicitRollbackFut != null) {
+                    finishFut = nullCompletedFuture();
+                    return implicitRollbackFut;
+                } else {
+                    finishFut = new CompletableFuture<>();
+                }

Review Comment:
   In `commitAsync`, when `implicitRollbackFut` is already set you assign 
`finishFut` to a successfully-completed future and return 
`implicitRollbackFut`. This can make subsequent `commitAsync()` calls complete 
successfully (via `finishFut`) even though the transaction has been rolled 
back/killed, and it can also hide an implicit rollback failure on repeated 
finish calls. Consider making `commitAsync` fail immediately based on the 
current state (rolled back/killed), or have `finishFut` reflect the same 
completion (success/exception) as the implicit rollback instead of 
`nullCompletedFuture()`.



##########
modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/handlers/TxFinishReplicaRequestHandler.java:
##########
@@ -224,12 +224,22 @@ private CompletableFuture<TransactionResult> 
finishAndCleanup(
                 .map(entry -> new EnlistedPartitionGroup(entry.getKey(), 
entry.getValue().tableIds()))
                 .collect(toList());
         return finishTransaction(enlistedPartitionGroups, txId, commit, 
commitTimestamp)
-                .thenCompose(txResult -> {
+                .thenApply(txResult -> {
                     boolean actualCommit = txResult.transactionState() == 
COMMITTED;
                     HybridTimestamp actualCommitTs = 
txResult.commitTimestamp();
 
-                    return txManager.cleanup(replicationGroupId, 
enlistedPartitions, actualCommit, actualCommitTs, txId)
-                            .thenApply(v -> txResult);
+                    try {
+                        txManager.cleanup(replicationGroupId, 
enlistedPartitions, actualCommit, actualCommitTs, txId)
+                                .thenApply(v -> txResult).whenComplete((r, e) 
-> {
+                                    if (e != null) {
+                                        LOG.warn("Failed to cleanup a 
transaction [id=" + txId + ']', e);
+                                    }
+                                });
+                    } catch (Exception e) {
+                        LOG.warn("Failed to cleanup a transaction [id=" + txId 
+ ']', e);

Review Comment:
   Logging style: this introduces string concatenation in `LOG.warn("Failed to 
cleanup a transaction [id=" + txId + ']', e)`. Elsewhere in this module logging 
typically uses parameterized messages with the throwable passed separately 
(e.g. `WriteIntentSwitchRequestHandler`). Using the parameterized form avoids 
eager string building and keeps logs consistent.
   ```suggestion
                                           LOG.warn("Failed to cleanup a 
transaction [id={}]", txId, e);
                                       }
                                   });
                       } catch (Exception e) {
                           LOG.warn("Failed to cleanup a transaction [id={}]", 
txId, e);
   ```



##########
modules/client/src/main/java/org/apache/ignite/internal/client/tx/ClientTransaction.java:
##########
@@ -421,8 +438,15 @@ private CompletableFuture<Void> rollbackAsyncInternal() {
             if (finishFut != null) {
                 return finishFut;
             } else {
-                finishFut = new CompletableFuture<>();
+                if (implicitRollbackFut != null) {
+                    finishFut = nullCompletedFuture();
+                    return implicitRollbackFut;
+                } else {
+                    finishFut = new CompletableFuture<>();
+                }

Review Comment:
   Similar to `commitAsync`, `rollbackAsyncInternal` sets `finishFut` to a 
successfully-completed future when `implicitRollbackFut` is non-null and 
returns `implicitRollbackFut`. If the implicit rollback completes 
exceptionally, subsequent `rollbackAsync()` calls will return `finishFut` and 
appear successful. `finishFut` should not be set to a success placeholder in 
this branch; it should either represent the same outcome as the implicit 
rollback, or `rollbackAsyncInternal` should return an already-failed future 
based on the current tx state.



##########
modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/ReadWriteTransactionImpl.java:
##########
@@ -235,23 +235,23 @@ public CompletableFuture<Void> finish(
      * @param commit Commit flag.
      * @param executionTimestamp The timestamp is the time when the 
transaction is applied to the remote node.
      * @param full Full state transaction marker.
-     * @param isComplete The flag is true if the transaction is completed 
through the public API, false for {@link this#kill()} invocation.
+     * @param isFinishedDirectly {@code True} if the transaction is completed 
through the public API, false for {@link #kill()}.

Review Comment:
   Javadoc: `{@code True}` should be lowercase (`{@code true}`) to match Java 
boolean literals and surrounding documentation style.
   ```suggestion
        * @param isFinishedDirectly {@code true} if the transaction is 
completed through the public API, false for {@link #kill()}.
   ```



-- 
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