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 85b995d410 IGNITE-18051 Extend test coverage for lock model (#1398)
85b995d410 is described below
commit 85b995d410f6a17202b80a2416e11df93a337a22
Author: Vladislav Pyatkov <[email protected]>
AuthorDate: Thu Dec 1 16:02:16 2022 +0300
IGNITE-18051 Extend test coverage for lock model (#1398)
---
.../ignite/internal/table/ItTableScanTest.java | 352 ++++++++++++++++++++-
.../replicator/PartitionReplicaListener.java | 4 +-
.../ignite/internal/tx/impl/HeapLockManager.java | 4 +-
.../internal/tx/AbstractLockManagerTest.java | 244 ++++++++++++++
4 files changed, 599 insertions(+), 5 deletions(-)
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 2ffbdc25db..dcdff459fb 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
@@ -17,9 +17,13 @@
package org.apache.ignite.internal.table;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -30,19 +34,26 @@ import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.Flow.Subscription;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
import java.util.stream.IntStream;
import org.apache.ignite.internal.app.IgniteImpl;
import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.ByteBufferRow;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
import org.apache.ignite.internal.schema.configuration.TablesConfiguration;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.schema.row.RowAssembler;
import org.apache.ignite.internal.sql.engine.AbstractBasicIntegrationTest;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.internal.tx.InternalTransaction;
import org.apache.ignite.lang.IgniteStringFormatter;
import org.apache.ignite.table.Tuple;
import org.apache.ignite.tx.IgniteTransactions;
-import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
@@ -58,6 +69,15 @@ public class ItTableScanTest extends
AbstractBasicIntegrationTest {
/** Ids to insert. */
private static final List<Integer> ROW_IDS = List.of(1, 2, 5, 6, 7, 10,
53);
+ private static final SchemaDescriptor SCHEMA = new SchemaDescriptor(
+ 1,
+ new Column[]{new Column("key", NativeTypes.INT32, false)},
+ new Column[]{
+ new Column("valInt", NativeTypes.INT32, false),
+ new Column("valInt", NativeTypes.STRING, false)
+ }
+ );
+
@BeforeEach
public void beforeTest() {
TableImpl table = getOrCreateTable();
@@ -147,12 +167,294 @@ public class ItTableScanTest extends
AbstractBasicIntegrationTest {
//assertEquals(ROW_IDS.size() + 1, scannedRows.size());
}
+ @Test
+ public void testUpsertDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.upsert(createKeyValueRow(3), tx)
+ .thenApply(unused -> 1);
+ });
+ }
+
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testUpsertAllDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.upsertAll(List.of(createKeyValueRow(3),
createKeyValueRow(60)), tx)
+ .thenApply(unused -> 2);
+ });
+ }
+
+ @Test
+ public void testGetAndUpsertDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.getAndUpsert(createKeyValueRow(3), tx)
+ .thenApply(previous -> {
+ assertNull(previous);
+
+ return 1;
+ });
+ });
+ }
+
+ @Test
+ public void testInsertDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.insert(createKeyValueRow(3), tx)
+ .thenApply(inserted -> {
+ assertTrue(inserted);
+
+ return 1;
+ });
+ });
+ }
+
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testInsertAllDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.insertAll(List.of(createKeyValueRow(3),
createKeyValueRow(60)), tx)
+ .thenApply(notInsertedRows -> {
+ assertTrue(notInsertedRows.isEmpty());
+
+ return 2;
+ });
+ });
+ }
+
+ @Test
+ public void testReplaceDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.replace(createKeyValueRow(6), tx)
+ .thenApply(inserted -> {
+ assertTrue(inserted);
+
+ return 0;
+ });
+ });
+ }
+
+ @Test
+ @Disabled("IGNITE-18299 Value comparison in table operations")
+ public void testReplaceOldDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.replace(createOldKeyValueRow(6),
createKeyValueRow(6), tx)
+ .thenApply(inserted -> {
+ assertTrue(inserted);
+
+ return 0;
+ });
+ });
+ }
+
+ @Test
+ public void testGetAndReplaceDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.getAndReplace(createKeyValueRow(6), tx)
+ .thenApply(previous -> {
+ assertNotNull(previous);
+
+ return 0;
+ });
+ });
+ }
+
+ @Test
+ public void testDeleteDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.delete(createKeyRow(6), tx)
+ .thenApply(deleted -> {
+ assertTrue(deleted);
+
+ return -1;
+ });
+ });
+ }
+
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testDeleteAllDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.deleteAll(List.of(createKeyRow(6),
createKeyRow(10)), tx)
+ .thenApply(deletedRows -> {
+ assertEquals(2, deletedRows.size());
+
+ return -2;
+ });
+ });
+ }
+
+ @Test
+ @Disabled("IGNITE-18299 Value comparison in table operations")
+ public void testDeleteExactDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.deleteExact(createOldKeyValueRow(6), tx)
+ .thenApply(deleted -> {
+ assertTrue(deleted);
+
+ return -1;
+ });
+ });
+ }
+
+ @Test
+ @Disabled("IGNITE-18299 Value comparison in table operations IGNITE-18294
Multiple lock intentions support")
+ public void testDeleteAllExactDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return
internalTable.deleteAllExact(List.of(createOldKeyValueRow(6),
createOldKeyValueRow(10)), tx)
+ .thenApply(deletedRows -> {
+ assertEquals(2, deletedRows.size());
+
+ return -2;
+ });
+ });
+ }
+
+ @Test
+ public void testGetAndDeleteDuringPureTableScan() throws Exception {
+ pureTableScan(tx -> {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ return internalTable.getAndDelete(createKeyRow(6), tx)
+ .thenApply(deleted -> {
+ assertNotNull(deleted);
+
+ return -1;
+ });
+ });
+ }
+
+ /**
+ * The method executes an operation, encapsulated in closure, during a
pure table scan.
+ *
+ * @param txOperationAction An closure to apply during the scan operation.
+ * @throws Exception If failed.
+ */
+ public void pureTableScan(Function<InternalTransaction,
CompletableFuture<Integer>> txOperationAction) throws Exception {
+ TableImpl table = getOrCreateTable();
+
+ InternalTable internalTable = table.internalTable();
+
+ InternalTransaction tx = (InternalTransaction)
CLUSTER_NODES.get(0).transactions().begin();
+
+ log.info("Old transaction [id=" + tx.id());
+
+ ArrayList<ByteBuffer> scannedRows = new ArrayList<>();
+
+ Publisher<BinaryRow> publisher = internalTable.scan(0, null, null,
null, null, 0, null);
+
+ CompletableFuture<Void> scanned = new CompletableFuture<>();
+
+ Subscription subscription = subscribeToPublisher(scannedRows,
publisher, scanned);
+
+ subscription.request(1);
+
+ IgniteTestUtils.waitForCondition(() -> !scannedRows.isEmpty(), 10_000);
+
+ assertEquals(1, scannedRows.size());
+ assertFalse(scanned.isDone());
+
+ var txOpFut = txOperationAction.apply(tx);
+
+ assertFalse(txOpFut.isDone());
+
+ subscription.request(2);
+
+ IgniteTestUtils.waitForCondition(() -> scannedRows.size() == 3,
10_000);
+
+ assertEquals(3, scannedRows.size());
+ assertFalse(scanned.isDone());
+ assertFalse(txOpFut.isDone());
+
+ subscription.request(1_000); // Request so much entries here to close
the publisher.
+
+ IgniteTestUtils.await(scanned);
+
+ assertThat(txOpFut, willCompleteSuccessfully());
+
+ tx.commit();
+
+ assertEquals(ROW_IDS.size(), scannedRows.size());
+
+ var pub = internalTable.scan(0, null, null, null, null, 0, null);
+
+ assertEquals(ROW_IDS.size() + txOpFut.get(), scanAllRows(pub).size());
+ }
+
+ /**
+ * Scans all rows form given publisher.
+ *
+ * @param publisher Publisher.
+ * @return List of scanned rows.
+ * @throws Exception If failed.
+ */
+ private ArrayList<ByteBuffer> scanAllRows(Publisher<BinaryRow> publisher)
throws Exception {
+ ArrayList<ByteBuffer> scannedRows = new ArrayList<>();
+ CompletableFuture<Void> scanned = new CompletableFuture<>();
+
+ Subscription subscription = subscribeToPublisher(scannedRows,
publisher, scanned);
+
+ subscription.request(1_000); // Request so much entries here to close
the publisher.
+
+ IgniteTestUtils.waitForCondition(() -> scanned.isDone(), 10_000);
+
+ return scannedRows;
+ }
+
/**
* Loads data.
*
* @param table Ignite table.
*/
- @NotNull
private static void loadData(TableImpl table) {
ROW_IDS.forEach(id -> insertRow(id));
@@ -250,4 +552,50 @@ public class ItTableScanTest extends
AbstractBasicIntegrationTest {
sql(IgniteStringFormatter.format("INSERT INTO {} (key, valInt, valStr)
VALUES ({}, {}, '{}');",
TABLE_NAME, rowId, rowId, "Str_" + rowId));
}
+
+ /**
+ * Creates an entire row with new value.
+ *
+ * @param id Primary key.
+ * @return Entire row.
+ */
+ private static Row createKeyValueRow(int id) {
+ RowAssembler rowBuilder = new RowAssembler(SCHEMA, 0, 0);
+
+ rowBuilder.appendInt(id);
+ rowBuilder.appendInt(id);
+ rowBuilder.appendString("StrNew_" + id);
+
+ return new Row(SCHEMA, new ByteBufferRow(rowBuilder.toBytes()));
+ }
+
+ /**
+ * Creates an entire row with old value.
+ *
+ * @param id Primary key.
+ * @return Entire row.
+ */
+ private static Row createOldKeyValueRow(int id) {
+ RowAssembler rowBuilder = new RowAssembler(SCHEMA, 0, 0);
+
+ rowBuilder.appendInt(id);
+ rowBuilder.appendInt(id);
+ rowBuilder.appendString("Str_" + id);
+
+ return new Row(SCHEMA, new ByteBufferRow(rowBuilder.toBytes()));
+ }
+
+ /**
+ * Creates a key row from primary key.
+ *
+ * @param id Primary key.
+ * @return Key row.
+ */
+ private static Row createKeyRow(int id) {
+ RowAssembler rowBuilder = new RowAssembler(SCHEMA, 0, 0);
+
+ rowBuilder.appendInt(id);
+
+ return new Row(SCHEMA, new ByteBufferRow(rowBuilder.toBytes()));
+ }
}
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 cee18b3a9e..f719ac1900 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
@@ -1388,7 +1388,7 @@ public class PartitionReplicaListener implements
ReplicaListener {
return completedFuture(false);
}
- return takeLocksForDelete(searchRow, rowId, txId)
+ return takeLocksForDelete(row, rowId, txId)
.thenCompose(ignored ->
applyCmdWithExceptionHandling(
updateCommand(commitPartitionId,
rowId.uuid(), null, txId)))
.thenApply(ignored -> true);
@@ -1400,7 +1400,7 @@ public class PartitionReplicaListener implements
ReplicaListener {
return completedFuture(null);
}
- return takeLocksForDelete(searchRow, rowId, txId)
+ return takeLocksForDelete(row, rowId, txId)
.thenCompose(ignored ->
applyCmdWithExceptionHandling(
updateCommand(commitPartitionId,
rowId.uuid(), null, txId)))
.thenApply(ignored -> row);
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 e1ea18a87b..0eb6217873 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
@@ -351,6 +351,8 @@ public class HeapLockManager implements LockManager {
WaiterImpl tmp = entry.getValue();
if (!tmp.locked() && isWaiterReadyToNotify(tmp, true)) {
+ assert tmp.locked() : "This waiter in not locked for
notification [waiter=" + tmp + ']';
+
toNotify.add(tmp);
}
}
@@ -359,7 +361,7 @@ public class HeapLockManager implements LockManager {
WaiterImpl tmp = entry.getValue();
if (!tmp.locked() && isWaiterReadyToNotify(tmp, false)) {
- assert !tmp.locked();
+ assert !tmp.locked() : "Only failed waiter can be notified
here [waiter=" + tmp + ']';
toNotify.add(tmp);
toFail.add(tmp.txId());
diff --git
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/AbstractLockManagerTest.java
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/AbstractLockManagerTest.java
index 514443791e..10f8a16695 100644
---
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/AbstractLockManagerTest.java
+++
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/AbstractLockManagerTest.java
@@ -17,11 +17,13 @@
package org.apache.ignite.internal.tx;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willSucceedFast;
import static org.apache.ignite.internal.tx.LockMode.IS;
import static org.apache.ignite.internal.tx.LockMode.IX;
import static org.apache.ignite.internal.tx.LockMode.S;
import static org.apache.ignite.internal.tx.LockMode.SIX;
import static org.apache.ignite.internal.tx.LockMode.X;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -45,6 +47,7 @@ import
org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.lang.IgniteException;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
@@ -834,6 +837,247 @@ public abstract class AbstractLockManagerTest extends
IgniteAbstractTest {
assertFalse(tx2SharedLock.isDone());
}
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testLockingOverloadAndUpgrade() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+
+ var tx1Lock = lockManager.acquire(tx2, key, X);
+
+ assertTrue(tx1Lock.isDone());
+
+ var tx2sLock = lockManager.acquire(tx1, key, S);
+
+ assertFalse(tx2sLock.isDone());
+
+ var tx2xLock = lockManager.acquire(tx1, key, X);
+
+ assertFalse(tx2xLock.isDone());
+
+ lockManager.release(tx1Lock.join());
+
+ assertThat(tx2sLock, willSucceedFast());
+ assertThat(tx2xLock, willSucceedFast());
+ }
+
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testLockingOverload() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+
+ var tx1Lock = lockManager.acquire(tx2, key, X);
+
+ assertTrue(tx1Lock.isDone());
+
+ var tx2xLock = lockManager.acquire(tx1, key, X);
+
+ assertFalse(tx2xLock.isDone());
+
+ var tx2s1Lock = lockManager.acquire(tx1, key, S);
+ var tx2s2Lock = lockManager.acquire(tx1, key, S);
+
+ assertFalse(tx2s1Lock.isDone());
+ assertFalse(tx2s2Lock.isDone());
+
+ lockManager.release(tx1Lock.join());
+
+ assertThat(tx2xLock, willSucceedFast());
+ assertThat(tx2s1Lock, willSucceedFast());
+ assertThat(tx2s2Lock, willSucceedFast());
+ }
+
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testFailUpgrade() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+ UUID tx3 = Timestamp.nextVersion().toUuid();
+
+ var tx1Lock = lockManager.acquire(tx1, key, S);
+ var tx2Lock = lockManager.acquire(tx2, key, S);
+ var tx3Lock = lockManager.acquire(tx3, key, S);
+
+ assertTrue(tx1Lock.isDone());
+ assertTrue(tx2Lock.isDone());
+ assertTrue(tx3Lock.isDone());
+
+ var tx1xLock = lockManager.acquire(tx1, key, X);
+ var tx2xLock = lockManager.acquire(tx2, key, X);
+
+ assertFalse(tx1xLock.isDone());
+ assertFalse(tx2xLock.isDone());
+
+ lockManager.release(tx3Lock.join());
+
+ assertTrue(tx1xLock.isDone());
+ assertFalse(tx2xLock.isDone());
+
+ lockManager.release(tx1xLock.join());
+
+ assertThat(tx2xLock, willSucceedFast());
+ }
+
+ @Test
+ @Disabled("IGNITE-18294 Multiple lock intentions support")
+ public void testDowngradeTargetLock() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+
+ var tx1Lock = lockManager.acquire(tx1, key, S);
+ var tx2Lock = lockManager.acquire(tx2, key, S);
+
+ assertThat(tx1Lock, willSucceedFast());
+ assertThat(tx2Lock, willSucceedFast());
+
+ var tx1IxLock = lockManager.acquire(tx1, key, IX);
+
+ assertFalse(tx1IxLock.isDone());
+
+ assertEquals(SIX, lockManager.locks(tx1).next().lockMode());
+
+ lockManager.release(tx1, key, S);
+
+ assertFalse(tx1IxLock.isDone());
+ assertEquals(IX, lockManager.locks(tx1).next().lockMode());
+
+ lockManager.release(tx2, key, S);
+
+ assertThat(tx1IxLock, willSucceedFast());
+ }
+
+ @Test
+ public void testFailWait() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+ UUID tx3 = Timestamp.nextVersion().toUuid();
+
+ var tx3Lock = lockManager.acquire(tx3, key, S);
+
+ assertThat(tx3Lock, willSucceedFast());
+
+ var tx2Lock = lockManager.acquire(tx2, key, X);
+
+ assertFalse(tx2Lock.isDone());
+
+ var tx1Lock = lockManager.acquire(tx1, key, X);
+
+ assertFalse(tx1Lock.isDone());
+
+ lockManager.release(tx3, key, S);
+
+ expectConflict(tx2Lock);
+
+ assertThat(tx1Lock, willSucceedFast());
+ }
+
+ @Test
+ public void testWaitInOrder() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+ UUID tx3 = Timestamp.nextVersion().toUuid();
+
+ var tx3IxLock = lockManager.acquire(tx3, key, IX);
+ var tx3Lock = lockManager.acquire(tx3, key, S);
+
+ assertThat(tx3IxLock, willSucceedFast());
+ assertThat(tx3Lock, willSucceedFast());
+
+ var tx2Lock = lockManager.acquire(tx2, key, IX);
+
+ assertFalse(tx2Lock.isDone());
+
+ var tx1Lock = lockManager.acquire(tx1, key, X);
+
+ assertFalse(tx1Lock.isDone());
+
+ lockManager.release(tx3, key, S);
+
+ assertThat(tx2Lock, willSucceedFast());
+
+ lockManager.release(tx3, key, IX);
+ lockManager.release(tx2, key, IX);
+
+ assertThat(tx3Lock, willSucceedFast());
+ }
+
+ @Test
+ public void testWaitNotInOrder() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+ UUID tx3 = Timestamp.nextVersion().toUuid();
+
+ var tx3IxLock = lockManager.acquire(tx3, key, IX);
+ var tx3Lock = lockManager.acquire(tx3, key, S);
+
+ assertThat(tx3IxLock, willSucceedFast());
+ assertThat(tx3Lock, willSucceedFast());
+
+ var tx2Lock = lockManager.acquire(tx2, key, X);
+
+ assertFalse(tx2Lock.isDone());
+
+ var tx1Lock = lockManager.acquire(tx1, key, IX);
+
+ assertFalse(tx1Lock.isDone());
+
+ lockManager.release(tx3, key, S);
+
+ assertThat(tx1Lock, willSucceedFast());
+
+ lockManager.release(tx1, key, IX);
+ lockManager.release(tx3, key, IX);
+
+ assertThat(tx2Lock, willSucceedFast());
+ }
+
+ @Test
+ public void testWaitFailNotInOrder() {
+ LockKey key = new LockKey("test");
+
+ UUID tx1 = Timestamp.nextVersion().toUuid();
+ UUID tx2 = Timestamp.nextVersion().toUuid();
+ UUID tx3 = Timestamp.nextVersion().toUuid();
+
+ var tx3IxLock = lockManager.acquire(tx3, key, IX);
+ var tx3Lock = lockManager.acquire(tx3, key, S);
+
+ assertThat(tx3IxLock, willSucceedFast());
+ assertThat(tx3Lock, willSucceedFast());
+
+ var tx2Lock = lockManager.acquire(tx2, key, X);
+
+ assertFalse(tx2Lock.isDone());
+
+ var tx1Lock = lockManager.acquire(tx1, key, IX);
+
+ assertFalse(tx1Lock.isDone());
+
+ lockManager.release(tx3, key, S);
+
+ assertThat(tx1Lock, willSucceedFast());
+
+ lockManager.release(tx3, key, IX);
+ lockManager.release(tx1, key, IX);
+
+ expectConflict(tx2Lock);
+ }
+
/**
* Do test single key multithreaded.
*