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 {