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

vpyatkov 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 c32f509ec6e IGNITE-26983 Enrich possible deadlock log message with 
extra information (#7107)
c32f509ec6e is described below

commit c32f509ec6e4dc7cb3c9febc4f3e3a6057528757
Author: Mikhail Efremov <[email protected]>
AuthorDate: Wed Dec 3 17:17:24 2025 +0600

    IGNITE-26983 Enrich possible deadlock log message with extra information 
(#7107)
---
 .../exception/OperationLockException.java          | 43 +++++++++++
 .../network/replication/RequestType.java           | 14 ++++
 .../cpp/tests/odbc-test/transaction_test.cpp       |  2 +-
 .../ignite/internal/table/ItTableScanTest.java     | 41 ++++++----
 .../replicator/PartitionReplicaListener.java       | 57 +++++++++++++-
 .../ignite/internal/table/TxAbstractTest.java      | 19 ++++-
 .../apache/ignite/internal/tx/LockException.java   | 18 +++++
 .../tx/PossibleDeadlockOnLockAcquireException.java | 52 +++++++++++++
 .../ignite/internal/tx/impl/HeapLockManager.java   | 89 ++++++++++++----------
 9 files changed, 271 insertions(+), 64 deletions(-)

diff --git 
a/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/exception/OperationLockException.java
 
b/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/exception/OperationLockException.java
new file mode 100644
index 00000000000..2383cc262b5
--- /dev/null
+++ 
b/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/exception/OperationLockException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.partition.replicator.exception;
+
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+
+import 
org.apache.ignite.internal.partition.replicator.network.replication.RequestType;
+import org.apache.ignite.internal.tx.LockException;
+
+/**
+ * Exception that wraps one of root cause {@link LockException} but provides 
an operation type that failed to acquire, release or downgrade
+ * a lock.
+ */
+public class OperationLockException extends LockException {
+    /**
+     * Constructor.
+     *
+     * @param requestOperationType Request's operation type that faced with 
failure to acquire, release or downgrade a lock.
+     * @param cause Detail cause of the failure.
+     */
+    public OperationLockException(RequestType requestOperationType, 
LockException cause) {
+        super(
+                cause.code(),
+                format("Lock acquiring failed during request handling 
[requestOperationType={}].", requestOperationType),
+                cause
+        );
+    }
+}
diff --git 
a/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/network/replication/RequestType.java
 
b/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/network/replication/RequestType.java
index 094a9a31acb..8ee9d01fb39 100644
--- 
a/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/network/replication/RequestType.java
+++ 
b/modules/partition-replicator/src/main/java/org/apache/ignite/internal/partition/replicator/network/replication/RequestType.java
@@ -107,6 +107,20 @@ public enum RequestType {
         }
     }
 
+    /**
+     * Returns {@code true} if the operation is a read-only.
+     */
+    public boolean isReadOnly() {
+        switch (this) {
+            case RO_GET:
+            case RO_GET_ALL:
+            case RO_SCAN:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     /**
      * Returns the enumerated value from its id.
      *
diff --git a/modules/platforms/cpp/tests/odbc-test/transaction_test.cpp 
b/modules/platforms/cpp/tests/odbc-test/transaction_test.cpp
index 75e5dba56a8..fe03ec36e6c 100644
--- a/modules/platforms/cpp/tests/odbc-test/transaction_test.cpp
+++ b/modules/platforms/cpp/tests/odbc-test/transaction_test.cpp
@@ -591,7 +591,7 @@ TEST_F(transaction_test, transaction_error) {
             try {
                 insert_test_value(conn2.m_statement, 2, "test_2");
             } catch (const odbc_exception &err) {
-                EXPECT_THAT(err.message, testing::HasSubstr("Failed to acquire 
a lock due to a possible deadlock"));
+                EXPECT_THAT(err.message, testing::HasSubstr("Lock acquiring 
failed during request handling"));
                 EXPECT_EQ(err.sql_state, "25000");
                 throw;
             }
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItTableScanTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItTableScanTest.java
index 36d29a02beb..83b85c02f6e 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItTableScanTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItTableScanTest.java
@@ -27,6 +27,7 @@ import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.runRace;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static 
org.apache.ignite.internal.testframework.flow.TestFlowUtils.subscribeToPublisher;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.apache.ignite.internal.util.ExceptionUtils.unwrapRootCause;
 import static org.apache.ignite.internal.wrapper.Wrappers.unwrapNullable;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -35,6 +36,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -78,6 +80,7 @@ import 
org.apache.ignite.internal.storage.index.impl.TestSortedIndexStorage;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.PendingTxPartitionEnlistment;
+import org.apache.ignite.internal.tx.PossibleDeadlockOnLockAcquireException;
 import org.apache.ignite.lang.ErrorGroups.Transactions;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.table.KeyValueView;
@@ -89,6 +92,7 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -538,11 +542,8 @@ public class ItTableScanTest extends 
BaseSqlIntegrationTest {
 
         assertFalse(scanned.isDone());
 
-        IgniteTestUtils.assertThrowsWithCode(
-                TransactionException.class,
-                Transactions.ACQUIRE_LOCK_ERR,
-                () -> kvView.put(null, Tuple.create().set("key", 3), 
Tuple.create().set("valInt", 3).set("valStr", "New_3")),
-                "Failed to acquire a lock due to a possible deadlock"
+        assertPossibleDeadLockExceptionOnReadWriteSingleRowOperation(
+                () -> kvView.put(null, Tuple.create().set("key", 3), 
Tuple.create().set("valInt", 3).set("valStr", "New_3"))
         );
 
         kvView.put(null, Tuple.create().set("key", 8), 
Tuple.create().set("valInt", 8).set("valStr", "New_8"));
@@ -612,16 +613,13 @@ public class ItTableScanTest extends 
BaseSqlIntegrationTest {
 
         assertEquals(3, scannedRows.size());
 
-        IgniteTestUtils.assertThrowsWithCode(
-                TransactionException.class,
-                Transactions.ACQUIRE_LOCK_ERR,
-                () -> kvView.put(null, Tuple.create().set("key", 8), 
Tuple.create().set("valInt", 8).set("valStr", "New_8")),
-                "Failed to acquire a lock due to a possible deadlock");
-        IgniteTestUtils.assertThrowsWithCode(
-                TransactionException.class,
-                Transactions.ACQUIRE_LOCK_ERR,
-                () -> kvView.put(null, Tuple.create().set("key", 9), 
Tuple.create().set("valInt", 9).set("valStr", "New_9")),
-                "Failed to acquire a lock due to a possible deadlock");
+        assertPossibleDeadLockExceptionOnReadWriteSingleRowOperation(
+                () -> kvView.put(null, Tuple.create().set("key", 8), 
Tuple.create().set("valInt", 8).set("valStr", "New_8"))
+        );
+
+        assertPossibleDeadLockExceptionOnReadWriteSingleRowOperation(
+                () -> kvView.put(null, Tuple.create().set("key", 9), 
Tuple.create().set("valInt", 9).set("valStr", "New_9"))
+        );
 
         Publisher<BinaryRow> publisher1 = new RollbackTxOnErrorPublisher<>(
                 tx,
@@ -1076,4 +1074,17 @@ public class ItTableScanTest extends 
BaseSqlIntegrationTest {
 
         return tx;
     }
+
+    private static void 
assertPossibleDeadLockExceptionOnReadWriteSingleRowOperation(Executable 
operation) {
+        TransactionException transactionException = (TransactionException) 
IgniteTestUtils.assertThrowsWithCode(
+                TransactionException.class,
+                Transactions.ACQUIRE_LOCK_ERR,
+                operation,
+                "Lock acquiring failed during request handling"
+        );
+
+        Throwable rootCause = unwrapRootCause(transactionException);
+        assertInstanceOf(PossibleDeadlockOnLockAcquireException.class, 
rootCause);
+        assertThat(rootCause.getMessage(), containsString("Failed to acquire a 
lock due to a possible deadlock"));
+    }
 }
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
index a0cd0633951..b34d1156bfe 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
@@ -43,6 +43,8 @@ import static 
org.apache.ignite.internal.util.CompletableFutures.emptyCollection
 import static 
org.apache.ignite.internal.util.CompletableFutures.emptyListCompletedFuture;
 import static 
org.apache.ignite.internal.util.CompletableFutures.isCompletedSuccessfully;
 import static 
org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture;
+import static org.apache.ignite.internal.util.ExceptionUtils.hasCause;
+import static org.apache.ignite.internal.util.ExceptionUtils.sneakyThrow;
 import static org.apache.ignite.internal.util.ExceptionUtils.unwrapCause;
 import static org.apache.ignite.internal.util.IgniteUtils.findAny;
 import static org.apache.ignite.internal.util.IgniteUtils.findFirst;
@@ -63,6 +65,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -98,6 +101,7 @@ import 
org.apache.ignite.internal.partition.replicator.ReplicaPrimacyEngine;
 import org.apache.ignite.internal.partition.replicator.ReplicaTableProcessor;
 import 
org.apache.ignite.internal.partition.replicator.ReplicationRaftCommandApplicator;
 import 
org.apache.ignite.internal.partition.replicator.TableAwareReplicaRequestPreProcessor;
+import 
org.apache.ignite.internal.partition.replicator.exception.OperationLockException;
 import 
org.apache.ignite.internal.partition.replicator.network.PartitionReplicationMessagesFactory;
 import org.apache.ignite.internal.partition.replicator.network.TimedBinaryRow;
 import 
org.apache.ignite.internal.partition.replicator.network.command.TimedBinaryRowMessage;
@@ -173,6 +177,7 @@ import 
org.apache.ignite.internal.table.distributed.replicator.handlers.BuildInd
 import org.apache.ignite.internal.table.metrics.TableMetricSource;
 import org.apache.ignite.internal.tx.DelayedAckException;
 import org.apache.ignite.internal.tx.Lock;
+import org.apache.ignite.internal.tx.LockException;
 import org.apache.ignite.internal.tx.LockKey;
 import org.apache.ignite.internal.tx.LockManager;
 import org.apache.ignite.internal.tx.LockMode;
@@ -1916,7 +1921,7 @@ public class PartitionReplicaListener implements 
ReplicaListener, ReplicaTablePr
 
                     int idx = 0;
 
-                    for (Map.Entry<RowId, BinaryRow> entry : 
rowsToInsert.entrySet()) {
+                    for (Entry<RowId, BinaryRow> entry : 
rowsToInsert.entrySet()) {
                         insertLockFuts[idx++] = 
takeLocksForInsert(entry.getValue(), entry.getKey(), txId);
                     }
 
@@ -3459,9 +3464,21 @@ public class PartitionReplicaListener implements 
ReplicaListener, ReplicaTablePr
 
         try {
             return processOperationRequest(senderId, request, replicaPrimacy, 
opStartTsIfDirectRo)
-                    .whenComplete((unused, throwable) -> {
+                    .handle((res, ex) -> {
                         unlockLwmIfNeeded(txIdLockingLwm, request);
                         
indexBuildingProcessor.decrementRwOperationCountIfNeeded(request);
+
+                        if (ex != null) {
+                            if (hasCause(ex, LockException.class)) {
+                                RequestType failedRequestType = 
getRequestOperationType(request);
+
+                                sneakyThrow(new 
OperationLockException(failedRequestType, (LockException) unwrapCause(ex)));
+                            }
+
+                            sneakyThrow(ex);
+                        }
+
+                        return res;
                     });
         } catch (Throwable e) {
             try {
@@ -3479,6 +3496,42 @@ public class PartitionReplicaListener implements 
ReplicaListener, ReplicaTablePr
         }
     }
 
+    private static RequestType getRequestOperationType(ReplicaRequest request) 
{
+        RequestType requestOperationType = null;
+
+        if (request instanceof ReadWriteSingleRowReplicaRequest) {
+            requestOperationType = ((ReadWriteSingleRowReplicaRequest) 
request).requestType();
+
+        } else if (request instanceof ReadWriteSingleRowPkReplicaRequest) {
+            requestOperationType = ((ReadWriteSingleRowPkReplicaRequest) 
request).requestType();
+
+        } else if (request instanceof ReadWriteMultiRowReplicaRequest) {
+            requestOperationType = ((ReadWriteMultiRowReplicaRequest) 
request).requestType();
+
+        } else if (request instanceof ReadWriteMultiRowPkReplicaRequest) {
+            requestOperationType = ((ReadWriteMultiRowPkReplicaRequest) 
request).requestType();
+
+        } else if (request instanceof ReadWriteSwapRowReplicaRequest) {
+            requestOperationType = ((ReadWriteSwapRowReplicaRequest) 
request).requestType();
+
+        } else if (request instanceof 
ReadWriteScanRetrieveBatchReplicaRequest) {
+            requestOperationType = RW_SCAN;
+        }
+
+        assert requestOperationType != null : format(
+                "Unexpected replica request without transaction operation type 
[requestType={}].",
+                request.getClass().getSimpleName()
+        );
+
+        assert !requestOperationType.isReadOnly() : format(
+                "Unexpected replica request with read-only operation type 
[requestType={}, requestOperationType={}].",
+                request.getClass().getSimpleName(),
+                requestOperationType
+        );
+
+        return requestOperationType;
+    }
+
     /**
      * Generates a fake transaction ID that will only be used to identify one 
direct RO operation for purposes of locking and unlocking LWM.
      * It should not be used as a replacement for a real transaction ID in 
other contexts.
diff --git 
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
 
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
index 08b345b4939..b9c574c5cd3 100644
--- 
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
+++ 
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/TxAbstractTest.java
@@ -25,10 +25,12 @@ import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThr
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCode;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
+import static org.apache.ignite.internal.util.ExceptionUtils.unwrapRootCause;
 import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
 import static 
org.apache.ignite.lang.ErrorGroups.Transactions.TX_ALREADY_FINISHED_ERR;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -614,7 +616,7 @@ public abstract class TxAbstractTest extends 
TxInfrastructureTest {
 
         Exception err = assertThrows(Exception.class, () -> 
table.upsertAll(tx2, rows));
 
-        assertTrue(err.getMessage().contains("Failed to acquire a lock"), 
err.getMessage());
+        assertTransactionLockException(err);
 
         tx1.commit();
     }
@@ -851,7 +853,7 @@ public abstract class TxAbstractTest extends 
TxInfrastructureTest {
         // TODO asch IGNITE-15937 fix exception model.
         Exception err = assertThrows(Exception.class, () -> table.upsert(tx2, 
makeValue(1, valTx + 1)));
 
-        assertTrue(err.getMessage().contains("Failed to acquire a lock"), 
err.getMessage());
+        assertTransactionLockException(err);
 
         // Write in tx1
         table2.upsert(tx1, makeValue(1, valTx2 + 1));
@@ -1005,7 +1007,7 @@ public abstract class TxAbstractTest extends 
TxInfrastructureTest {
         txAcc.upsert(tx2, makeValue(2, 400.));
 
         Exception err = assertThrows(Exception.class, () -> txAcc.getAll(tx2, 
List.of(makeKey(2), makeKey(1))));
-        assertTrue(err.getMessage().contains("Failed to acquire a lock"), 
err.getMessage());
+        assertTransactionLockException(err);
 
         validateBalance(txAcc2.getAll(tx1, List.of(makeKey(2), makeKey(1))), 
200., 300.);
         validateBalance(txAcc2.getAll(tx1, List.of(makeKey(1), makeKey(2))), 
300., 200.);
@@ -1791,7 +1793,7 @@ public abstract class TxAbstractTest extends 
TxInfrastructureTest {
 
                             ops.increment();
                         } catch (Exception e) {
-                            assertTrue(e.getMessage().contains("Failed to 
acquire a lock"), e.getMessage());
+                            assertTransactionLockException(e);
 
                             tx.rollback();
 
@@ -2383,6 +2385,15 @@ public abstract class TxAbstractTest extends 
TxInfrastructureTest {
         }
     }
 
+    private static void assertTransactionLockException(Exception e) {
+        assertInstanceOf(TransactionException.class, e);
+        assertThat(e.getMessage(), containsString("Lock acquiring failed 
during request handling"));
+
+        Throwable rootCause = unwrapRootCause(e);
+        assertInstanceOf(LockException.class, rootCause);
+        assertThat(rootCause.getMessage(), containsString("Failed to acquire a 
lock"));
+    }
+
     private static class SingleRequestSubscriber<T> implements 
Flow.Subscriber<T> {
         private final AtomicReference<Throwable> errorRef;
 
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockException.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockException.java
index 0e4d0760f18..9086e88c4c9 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockException.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/LockException.java
@@ -33,4 +33,22 @@ public class LockException extends 
TransactionInternalCheckedException implement
     public LockException(int code, String msg) {
         super(code, msg);
     }
+
+    /**
+     * Creates a new instance of LockException with the given message.
+     *
+     * @param code Full error code. {{@link 
org.apache.ignite.lang.ErrorGroups.Transactions#ACQUIRE_LOCK_ERR},
+     *     {@link 
org.apache.ignite.lang.ErrorGroups.Transactions#ACQUIRE_LOCK_TIMEOUT_ERR},
+     * @param msg The detail message.
+     * @param cause Cause of the exception.
+     */
+    protected LockException(int code, String msg, Throwable cause) {
+        super(code, msg, cause);
+    }
+
+    @Override
+    public synchronized Throwable fillInStackTrace() {
+        // Omits redundant stacktrace.
+        return this;
+    }
 }
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/PossibleDeadlockOnLockAcquireException.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/PossibleDeadlockOnLockAcquireException.java
new file mode 100644
index 00000000000..46c5723d5a1
--- /dev/null
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/PossibleDeadlockOnLockAcquireException.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tx;
+
+import static org.apache.ignite.lang.ErrorGroups.Transactions.ACQUIRE_LOCK_ERR;
+
+import java.util.UUID;
+
+/**
+ * This exception is thrown when a lock cannot be acquired due to possible 
deadlock situation.
+ */
+public class PossibleDeadlockOnLockAcquireException extends LockException {
+    /**
+     * Constructor.
+     *
+     * @param failedToAcquireLockTxId UUID of a transaction that tried to 
acquire lock, but failed.
+     * @param currentLockHolderTxId UUID of a transaction that currently holds 
the lock.
+     * @param attemptedLockModeToAcquireWith {@link LockMode} that was tried 
to acquire the lock with but failed the attempt.
+     * @param currentlyAcquiredLockMode {@link LockMode} of the lock that is 
already acquired with.
+     */
+    public PossibleDeadlockOnLockAcquireException(
+            UUID failedToAcquireLockTxId,
+            UUID currentLockHolderTxId,
+            LockMode attemptedLockModeToAcquireWith,
+            LockMode currentlyAcquiredLockMode
+    ) {
+        super(
+                ACQUIRE_LOCK_ERR,
+                "Failed to acquire a lock due to a possible deadlock ["
+                        + "failedToAcquireLockTransactionId=" + 
failedToAcquireLockTxId
+                        + ", currentLockHolderTransactionId=" + 
currentLockHolderTxId
+                        + ", attemptedLockModeToAcquireWith=" + 
attemptedLockModeToAcquireWith
+                        + ", currentlyAcquiredLockMode=" + 
currentlyAcquiredLockMode
+                        + "]."
+        );
+    }
+}
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
index 01b03b8b862..1d5dffad434 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/HeapLockManager.java
@@ -57,6 +57,7 @@ import org.apache.ignite.internal.tx.LockException;
 import org.apache.ignite.internal.tx.LockKey;
 import org.apache.ignite.internal.tx.LockManager;
 import org.apache.ignite.internal.tx.LockMode;
+import org.apache.ignite.internal.tx.PossibleDeadlockOnLockAcquireException;
 import org.apache.ignite.internal.tx.Waiter;
 import org.apache.ignite.internal.tx.event.LockEvent;
 import org.apache.ignite.internal.tx.event.LockEventParameters;
@@ -370,18 +371,6 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
         return result;
     }
 
-    /**
-     * Create lock exception with given parameters.
-     *
-     * @param locker Locker.
-     * @param holder Lock holder.
-     * @return Lock exception.
-     */
-    private static LockException lockException(UUID locker, UUID holder) {
-        return new LockException(ACQUIRE_LOCK_ERR,
-                "Failed to acquire a lock due to a possible deadlock [locker=" 
+ locker + ", holder=" + holder + ']');
-    }
-
     /**
      * Create lock exception when lock holder is believed to be missing.
      *
@@ -394,20 +383,6 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
                 "Failed to acquire an abandoned lock due to a possible 
deadlock [locker=" + locker + ", holder=" + holder + ']');
     }
 
-    /**
-     * Create coarse lock exception.
-     *
-     * @param locker Locker.
-     * @param holder Lock holder.
-     * @param abandoned If locker is abandoned.
-     * @return Lock exception.
-     */
-    private static LockException coarseLockException(UUID locker, UUID holder, 
boolean abandoned) {
-        return new LockException(ACQUIRE_LOCK_ERR,
-                "Failed to acquire the intention table lock due to a conflict 
[locker=" + locker + ", holder=" + holder + ", abandoned="
-                        + abandoned + ']');
-    }
-
     /**
      * Common interface for releasing transaction locks.
      */
@@ -536,7 +511,7 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
                                     // Attempt to upgrade to SIX in the 
presence of concurrent transactions. Deny lock attempt.
                                     for (Lock lock : ixlockOwners.values()) {
                                         if (!lock.txId().equals(txId)) {
-                                            return notifyAndFail(txId, 
lock.txId());
+                                            return notifyAndFail(txId, 
lock.txId(), lockMode, lock.lockMode());
                                         }
                                     }
                                 }
@@ -549,7 +524,7 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
                                 for (Lock lock : ixlockOwners.values()) {
                                     // Allow only high priority transactions 
to wait.
                                     if (txComparator.compare(lock.txId(), 
txId) < 0) {
-                                        return notifyAndFail(txId, 
lock.txId());
+                                        return notifyAndFail(txId, 
lock.txId(), lockMode, lock.lockMode());
                                     }
                                 }
                             }
@@ -593,7 +568,7 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
                                     // Attempt to upgrade to SIX in the 
presence of concurrent transactions. Deny lock attempt.
                                     for (Lock lock : slockOwners.values()) {
                                         if (!lock.txId().equals(txId)) {
-                                            return notifyAndFail(txId, 
lock.txId());
+                                            return notifyAndFail(txId, 
lock.txId(), lockMode, lock.lockMode());
                                         }
                                     }
                                 }
@@ -602,8 +577,8 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
                             }
 
                             // IX locks never allowed to wait.
-                            UUID holderTx = 
slockOwners.keySet().iterator().next();
-                            return notifyAndFail(txId, holderTx);
+                            Entry<UUID, Lock> holderEntry = 
slockOwners.entrySet().iterator().next();
+                            return notifyAndFail(txId, holderEntry.getKey(), 
lockMode, holderEntry.getValue().lockMode());
                         } else {
                             Lock lock = new Lock(lockKey, lockMode, txId);
                             Lock prev = ixlockOwners.putIfAbsent(txId, lock); 
// Avoid overwrite existing lock.
@@ -632,14 +607,35 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
         /**
          * Triggers event and fails.
          *
-         * @param txId Tx id.
-         * @param conflictedHolderId Holder tx id.
+         * @param failedToAcquireLockTxId UUID of a transaction that tried to 
acquire lock, but failed.
+         * @param currentLockHolderTxId UUID of a transaction that currently 
holds the lock.
+         * @param attemptedLockModeToAcquireWith {@link LockMode} that was 
tried to acquire the lock with but failed the attempt.
+         * @param currentlyAcquiredLockMode {@link LockMode} of the lock that 
is already acquired with.
          * @return Failed future.
          */
-        CompletableFuture<Lock> notifyAndFail(UUID txId, UUID 
conflictedHolderId) {
-            CompletableFuture<Void> res = fireEvent(LOCK_CONFLICT, new 
LockEventParameters(txId, allLockHolderTxs()));
+        CompletableFuture<Lock> notifyAndFail(
+                UUID failedToAcquireLockTxId,
+                UUID currentLockHolderTxId,
+                LockMode attemptedLockModeToAcquireWith,
+                LockMode currentlyAcquiredLockMode
+        ) {
+            CompletableFuture<Lock> failedFuture = new CompletableFuture<>();
+
+            fireEvent(LOCK_CONFLICT, new 
LockEventParameters(failedToAcquireLockTxId, 
allLockHolderTxs())).whenComplete((v, ex) -> {
+                if (ex != null) {
+                    
failedFuture.completeExceptionally(abandonedLockException(failedToAcquireLockTxId,
 currentLockHolderTxId));
+                } else {
+                    failedFuture.completeExceptionally(new 
PossibleDeadlockOnLockAcquireException(
+                            failedToAcquireLockTxId,
+                            currentLockHolderTxId,
+                            attemptedLockModeToAcquireWith,
+                            currentlyAcquiredLockMode
+                    ));
+                }
+            });
+
             // TODO: https://issues.apache.org/jira/browse/IGNITE-21153
-            return failedFuture(coarseLockException(txId, conflictedHolderId, 
res.isCompletedExceptionally()));
+            return failedFuture;
         }
 
         /**
@@ -868,15 +864,20 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
 
             for (Entry<UUID, WaiterImpl> entry : 
waiters.tailMap(waiter.txId(), false).entrySet()) {
                 WaiterImpl tmp = entry.getValue();
-                LockMode mode = tmp.lockMode;
+                LockMode currentlyAcquiredLockMode = tmp.lockMode;
 
-                if (mode != null && !mode.isCompatible(intendedLockMode)) {
+                if (currentlyAcquiredLockMode != null && 
!currentlyAcquiredLockMode.isCompatible(intendedLockMode)) {
                     if (conflictFound(waiter.txId())) {
                         waiter.fail(abandonedLockException(waiter.txId, 
tmp.txId));
 
                         return true;
                     } else if (!deadlockPreventionPolicy.usePriority() && 
deadlockPreventionPolicy.waitTimeout() == 0) {
-                        waiter.fail(lockException(waiter.txId, tmp.txId));
+                        waiter.fail(new PossibleDeadlockOnLockAcquireException(
+                                waiter.txId,
+                                tmp.txId,
+                                intendedLockMode,
+                                currentlyAcquiredLockMode
+                        ));
 
                         return true;
                     }
@@ -887,9 +888,9 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
 
             for (Entry<UUID, WaiterImpl> entry : 
waiters.headMap(waiter.txId()).entrySet()) {
                 WaiterImpl tmp = entry.getValue();
-                LockMode mode = tmp.lockMode;
+                LockMode currentlyAcquiredLockMode = tmp.lockMode;
 
-                if (mode != null && !mode.isCompatible(intendedLockMode)) {
+                if (currentlyAcquiredLockMode != null && 
!currentlyAcquiredLockMode.isCompatible(intendedLockMode)) {
                     if (skipFail) {
                         return false;
                     } else if (conflictFound(waiter.txId())) {
@@ -897,7 +898,11 @@ public class HeapLockManager extends 
AbstractEventProducer<LockEvent, LockEventP
 
                         return true;
                     } else if (deadlockPreventionPolicy.waitTimeout() == 0) {
-                        waiter.fail(lockException(waiter.txId, tmp.txId));
+                        waiter.fail(new PossibleDeadlockOnLockAcquireException(
+                                waiter.txId,
+                                tmp.txId, intendedLockMode,
+                                currentlyAcquiredLockMode
+                        ));
 
                         return true;
                     } else {


Reply via email to