diff --git a/docs/_docs/monitoring-metrics/system-views.adoc b/docs/_docs/monitoring-metrics/system-views.adoc index d7426347a0f6b..5e8efa8942424 100644 --- a/docs/_docs/monitoring-metrics/system-views.adoc +++ b/docs/_docs/monitoring-metrics/system-views.adoc @@ -275,6 +275,7 @@ Each row in this view represents a transaction object on the node where the view |ORIGINATING_NODE_ID | UUID | ID of the node that initiated the current transaction object. For a transaction object mapped to a primary partition, this is the transaction initiator node; for a transaction object mapped to a backup partition, this is the node that owns the primary partition |STATE | string | Current transaction state |XID | UUID | Unique transaction identifier +|ORIGINATING_XID | UUID | Ttransaction ID on originating node |LABEL | string | Transaction label |START_TIME | long | Start time of the transaction on this node |ISOLATION | string | Transaction isolation level @@ -299,6 +300,39 @@ Each row in this view represents a transaction object on the node where the view |TOP_VER | string | Topology version assigned to the transaction. If not assigned explicitly, it is a topology version of the latest partition exchange. |=== +== CACHE_EXPLICIT_LOCKS + +This view exposes information about explicit locks acquired on the current node. +Explicit locks are acquired using the `Cache.lock(key)` API. + +[{table_opts}] +|=== +|NAME | TYPE | DESCRIPTION +|CACHE_ID | int | Cache ID +|KEY | string | Locked key +|THREAD_ID | long | ID of the thread that acquired the lock +|XID | UUID | Lock ID (unique identifier) +|=== + +== CACHE_LOCKS + +This view exposes information about all locks (both explicit and transactional) requested for keys hosted on the current node. +This includes locks from remote nodes trying to access primary keys on this node. + +[{table_opts}] +|=== +|NAME | TYPE | DESCRIPTION +|CACHE_ID | int | Cache ID +|KEY | string | Locked key +|NODE_ID | UUID | ID of the node where the lock is hosted +|ORIGINATING_NODE_ID | UUID | ID of the node that initiated the lock request +|IS_OWNER | boolean | `True` if the lock is currently owned +|IS_TX | boolean | `True` if the lock is part of a transaction +|FLAGS | short | All flags for the lock. This field represents a bit mask with possible values: LOCAL(0x01), OWNER(0x02), READY(0x04), REENTRY(0x08), USED(0x10), TX(0x40), SINGLE_IMPLICIT(0x80), DHT_LOCAL(0x100), NEAR_LOCAL(0x200), REMOVED(0x400), READ(0x800); +|XID | UUID | Current transaction ID (or explicit lock ID) +|ORIGINATING_XID | UUID | Originating transaction ID (or explicit lock ID) +|=== + == NODES diff --git a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java index fcbd74fe0c550..14a97520c8175 100644 --- a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java +++ b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java @@ -3291,7 +3291,7 @@ public void clearAllListener() { * @param ver Cache version. * @return Version represented as {@code IgniteUuid}. */ - public static IgniteUuid asIgniteUuid(GridCacheVersion ver) { - return new IgniteUuid(new UUID(ver.topologyVersion(), ver.nodeOrderAndDrIdRaw()), ver.order()); + public static @Nullable IgniteUuid asIgniteUuid(@Nullable GridCacheVersion ver) { + return ver == null ? null : new IgniteUuid(new UUID(ver.topologyVersion(), ver.nodeOrderAndDrIdRaw()), ver.order()); } } diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java index 3f989f2e6ae93..0604f506b8c4c 100755 --- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java @@ -369,7 +369,9 @@ public void testGetAllView() throws Exception { "STATISTICS_LOCAL_DATA", "STATISTICS_GLOBAL_DATA", "SQL_PLANS_HISTORY", - "IGNITE_PLUGINS" + "IGNITE_PLUGINS", + "CACHE_EXPLICIT_LOCKS", + "CACHE_LOCKS" )); Set actViews = new TreeSet<>(); diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java index b1bd8246ff915..cf7d9137f95d3 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java @@ -955,6 +955,7 @@ public void testGetAllColumns() throws Exception { "SYS.TRANSACTIONS.LOCAL_NODE_ID.null", "SYS.TRANSACTIONS.STATE.null", "SYS.TRANSACTIONS.XID.null", + "SYS.TRANSACTIONS.ORIGINATING_XID.null", "SYS.TRANSACTIONS.LABEL.null", "SYS.TRANSACTIONS.START_TIME.null", "SYS.TRANSACTIONS.ISOLATION.null", @@ -1178,8 +1179,21 @@ public void testGetAllColumns() throws Exception { "SYS.IGNITE_PLUGINS.NAME.null", "SYS.IGNITE_PLUGINS.INFO.null", "SYS.IGNITE_PLUGINS.VERSION.null", - "SYS.IGNITE_PLUGINS.CLASS_NAME.null" - )); + "SYS.IGNITE_PLUGINS.CLASS_NAME.null", + "SYS.CACHE_EXPLICIT_LOCKS.CACHE_ID.null", + "SYS.CACHE_EXPLICIT_LOCKS.KEY.null", + "SYS.CACHE_EXPLICIT_LOCKS.THREAD_ID.null", + "SYS.CACHE_EXPLICIT_LOCKS.XID.null", + "SYS.CACHE_LOCKS.CACHE_ID.null", + "SYS.CACHE_LOCKS.KEY.null", + "SYS.CACHE_LOCKS.NODE_ID.null", + "SYS.CACHE_LOCKS.ORIGINATING_NODE_ID.null", + "SYS.CACHE_LOCKS.IS_OWNER.null", + "SYS.CACHE_LOCKS.IS_TX.null", + "SYS.CACHE_LOCKS.FLAGS.null", + "SYS.CACHE_LOCKS.XID.null", + "SYS.CACHE_LOCKS.ORIGINATING_XID.null" + )); Assert.assertEquals(expectedCols, actualSysCols); } diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java index d927b361a758e..42bbfa10683c0 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java @@ -502,7 +502,9 @@ public void testViews() { "DS_QUEUES", "PAGES_TIMESTAMP_HISTOGRAM", "SQL_PLANS_HISTORY", - "IGNITE_PLUGINS" + "IGNITE_PLUGINS", + "CACHE_EXPLICIT_LOCKS", + "CACHE_LOCKS" )); Set viewNames = new TreeSet<>(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java index e0c6d9b42406a..53c8ad6f5774d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal.processors.cache; +import java.util.ArrayList; +import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; @@ -216,6 +218,20 @@ public boolean isEmpty() { } } + /** + * @return Lock candidates. + */ + public Collection candidates() { + lock(); + + try { + return new ArrayList<>(F.flatCollections(cands.values())); + } + finally { + unlock(); + } + } + /** * Marks all candidates added for given key as owned. * diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java index 9ca503678031b..b7fd59015f261 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java @@ -20,6 +20,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; @@ -49,6 +50,8 @@ import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx; import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; +import org.apache.ignite.internal.systemview.CacheExplicitLockViewWalker; +import org.apache.ignite.internal.systemview.CacheLockViewWalker; import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashSet; import org.apache.ignite.internal.util.GridConcurrentFactory; import org.apache.ignite.internal.util.GridConcurrentHashSet; @@ -67,6 +70,8 @@ import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.lang.IgniteUuid; +import org.apache.ignite.spi.systemview.view.CacheExplicitLockView; +import org.apache.ignite.spi.systemview.view.CacheLockView; import org.apache.ignite.util.deque.FastSizeDeque; import org.jetbrains.annotations.Nullable; @@ -94,6 +99,18 @@ public class GridCacheMvccManager extends GridCacheSharedManagerAdapter { private static final int MAX_NESTED_LSNR_CALLS = getInteger(IGNITE_MAX_NESTED_LISTENER_CALLS, DFLT_MAX_NESTED_LISTENER_CALLS); + /** System view name for cache explicit locks. */ + public static final String CACHE_EXPLICIT_LOCKS_VIEW = "cacheExplicitLocks"; + + /** System view description for cache explicit locks. */ + public static final String CACHE_EXPLICIT_LOCKS_VIEW_DESC = "Explicit locks on cache keys"; + + /** System view name for cache locks. */ + public static final String CACHE_LOCKS_VIEW = "cacheLocks"; + + /** System view description for cache keys locks. */ + public static final String CACHE_LOCKS_VIEW_DESC = "Held and queued locks on cache keys"; + /** Pending locks per thread. */ private final ThreadLocal> pending = new ThreadLocal<>(); @@ -282,6 +299,33 @@ else if (log.isDebugEnabled()) pendingExplicit = GridConcurrentFactory.newMap(); cctx.gridEvents().addLocalEventListener(discoLsnr, EVT_NODE_FAILED, EVT_NODE_LEFT); + + cctx.kernalContext().systemView().registerInnerCollectionView( + CACHE_EXPLICIT_LOCKS_VIEW, + CACHE_EXPLICIT_LOCKS_VIEW_DESC, + new CacheExplicitLockViewWalker(), + pendingExplicit.values(), + GridCacheExplicitLockSpan::candidates, + (threadId, lock) -> new CacheExplicitLockView(lock) + ); + + cctx.kernalContext().systemView().registerInnerCollectionView( + CACHE_LOCKS_VIEW, + CACHE_LOCKS_VIEW_DESC, + new CacheLockViewWalker(), + locked.values(), + entry -> { + entry.lockEntry(); + try { + GridCacheMvcc mvcc = entry.mvccExtras(); + return mvcc != null ? mvcc.localCandidates() : Collections.emptyList(); + } + finally { + entry.unlockEntry(); + } + }, + (entry, cand) -> new CacheLockView(cand) + ); } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheExplicitLockView.java b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheExplicitLockView.java new file mode 100644 index 0000000000000..8d2a36d6703ba --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheExplicitLockView.java @@ -0,0 +1,73 @@ +/* + * 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.spi.systemview.view; + +import org.apache.ignite.internal.binary.BinaryUtils; +import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate; +import org.apache.ignite.internal.systemview.Order; +import org.apache.ignite.internal.systemview.SystemViewDescriptor; +import org.apache.ignite.lang.IgniteUuid; + +/** + * Cache explicit lock representation for a {@link SystemView}. + * Shows locks acquired on current node (not hosted by current node). + */ +@SystemViewDescriptor +public class CacheExplicitLockView { + /** */ + private final GridCacheMvccCandidate cand; + + /** + * @param cand Lock candidate + */ + public CacheExplicitLockView(GridCacheMvccCandidate cand) { + this.cand = cand; + } + + /** + * @return Cache ID. + */ + @Order + public int cacheId() { + return cand.key().cacheId(); + } + + /** + * @return Key. + */ + @Order(1) + public String key() { + return cand.key().key().toString(); + } + + /** + * @return Thread ID. + */ + @Order(2) + public long threadId() { + return cand.threadId(); + } + + /** + * @return Version. + */ + @Order(3) + public IgniteUuid xid() { + return BinaryUtils.asIgniteUuid(cand.version()); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheLockView.java b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheLockView.java new file mode 100644 index 0000000000000..dbee3e2e34a9c --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheLockView.java @@ -0,0 +1,115 @@ +/* + * 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.spi.systemview.view; + +import java.util.UUID; +import org.apache.ignite.internal.binary.BinaryUtils; +import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate; +import org.apache.ignite.internal.systemview.Order; +import org.apache.ignite.internal.systemview.SystemViewDescriptor; +import org.apache.ignite.lang.IgniteUuid; + +/** + * Cache key lock representation for a {@link SystemView}. + * Shows held cache key locks and queued cache key locks hosted on current node, + * including explicit locks and transactional locks. + */ +@SystemViewDescriptor +public class CacheLockView { + /** Lock candidate. */ + private final GridCacheMvccCandidate cand; + + /** + * @param cand Lock candidate + */ + public CacheLockView(GridCacheMvccCandidate cand) { + this.cand = cand; + } + + /** + * @return Cache ID. + */ + @Order + public int cacheId() { + return cand.key().cacheId(); + } + + /** + * @return Key. + */ + @Order(1) + public String key() { + return cand.key().key().toString(); + } + + /** + * @return Node ID. + */ + @Order(2) + public UUID nodeId() { + return cand.nodeId(); + } + + /** + * @return Originating node ID. + */ + @Order(3) + public UUID originatingNodeId() { + return cand.otherNodeId(); + } + + /** + * @return "OWNER" flag. + */ + @Order(4) + public boolean isOwner() { + return cand.owner(); + } + + /** + * @return "TX" flag. + */ + @Order(5) + public boolean isTx() { + return cand.tx(); + } + + /** + * @return All enabled flags. + */ + @Order(6) + public short flags() { + return cand.flags(); + } + + /** + * @return Transaction ID. + */ + @Order(7) + public IgniteUuid xid() { + return BinaryUtils.asIgniteUuid(cand.version()); + } + + /** + * @return Originating transaction ID. + */ + @Order(8) + public IgniteUuid originatingXid() { + return BinaryUtils.asIgniteUuid(cand.otherVersion()); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java index bc09a478c99b7..547715fee2cf7 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Objects; import java.util.UUID; +import org.apache.ignite.internal.binary.BinaryUtils; import org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx; import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry; import org.apache.ignite.internal.processors.cache.transactions.IgniteTxState; @@ -125,6 +126,14 @@ public IgniteUuid xid() { return tx.xid(); } + /** + * @return Near transaction ID. + * @see IgniteInternalTx#nearXidVersion() () + */ + public IgniteUuid originatingXid() { + return BinaryUtils.asIgniteUuid(tx.nearXidVersion()); + } + /** * @return {@code True} if transaction is started for system cache. * @see IgniteInternalTx#system() @@ -284,6 +293,5 @@ public String cacheIds() { catch (Throwable e) { return null; } - } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewLocksTest.java b/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewLocksTest.java new file mode 100644 index 0000000000000..8d5f34dcdda7f --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewLocksTest.java @@ -0,0 +1,387 @@ +/* + * 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.metric; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.Lock; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.spi.systemview.view.CacheExplicitLockView; +import org.apache.ignite.spi.systemview.view.CacheLockView; +import org.apache.ignite.spi.systemview.view.SystemView; +import org.apache.ignite.spi.systemview.view.TransactionView; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.transactions.Transaction; +import org.junit.Test; + +import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; +import static org.apache.ignite.cache.CacheMode.PARTITIONED; +import static org.apache.ignite.internal.processors.cache.GridCacheMvccManager.CACHE_EXPLICIT_LOCKS_VIEW; +import static org.apache.ignite.internal.processors.cache.GridCacheMvccManager.CACHE_LOCKS_VIEW; +import static org.apache.ignite.internal.processors.cache.transactions.IgniteTxManager.TXS_MON_LIST; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; +import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; +import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; + +/** Tests for {@link SystemView} for locks. */ +public class SystemViewLocksTest extends SystemViewAbstractTest { + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + IgniteEx ignite = startGrids(3); + + ignite.getOrCreateCache( + new CacheConfiguration(DEFAULT_CACHE_NAME) + .setAtomicityMode(TRANSACTIONAL) + .setCacheMode(PARTITIONED) + .setBackups(1) + ); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + stopAllGrids(); + + super.afterTest(); + } + + /** */ + @Test + public void testExplicitLocks() throws Exception { + IgniteEx ignite0 = grid(0); + IgniteEx ignite1 = grid(1); + IgniteEx ignite2 = grid(2); + + CountDownLatch finishLatch = new CountDownLatch(1); + + try { + Integer key0 = primaryKey(ignite0.cache(DEFAULT_CACHE_NAME)); + Integer key1 = primaryKey(ignite1.cache(DEFAULT_CACHE_NAME)); + Integer key2 = primaryKey(ignite2.cache(DEFAULT_CACHE_NAME)); + + CountDownLatch firstNodeLatch = new CountDownLatch(1); + + // Block key0 and key1 from node0. + Runnable task0 = explicitLockTask(ignite0, () -> { + firstNodeLatch.countDown(); + U.awaitQuiet(finishLatch); + }, key0, key1); + + // Block key2 and wait for key0 from node1. + Runnable task1 = explicitLockTask(ignite1, () -> {}, key2, key0); + + // Wait for key0 from node2. + Runnable task2a = explicitLockTask(ignite2, () -> {}, key0); + + // Wait for key1 from node2. + Runnable task2b = explicitLockTask(ignite2, () -> {}, key1); + + IgniteInternalFuture fut0 = GridTestUtils.runAsync(task0); + firstNodeLatch.await(); + IgniteInternalFuture fut1 = GridTestUtils.runAsync(task1); + IgniteInternalFuture fut2a = GridTestUtils.runAsync(task2a); + IgniteInternalFuture fut2b = GridTestUtils.runAsync(task2b); + + List explicitLocks0 = viewContent(ignite0, CACHE_EXPLICIT_LOCKS_VIEW, 2); + List explicitLocks1 = viewContent(ignite1, CACHE_EXPLICIT_LOCKS_VIEW, 2); + List explicitLocks2 = viewContent(ignite2, CACHE_EXPLICIT_LOCKS_VIEW, 2); + + List keyLocks0 = viewContent(ignite0, CACHE_LOCKS_VIEW, 3); + List keyLocks1 = viewContent(ignite1, CACHE_LOCKS_VIEW, 2); + List keyLocks2 = viewContent(ignite2, CACHE_LOCKS_VIEW, 1); + + // Check threads. + assertEquals(explicitLocks0.get(0).threadId(), explicitLocks0.get(1).threadId()); + assertEquals(explicitLocks1.get(0).threadId(), explicitLocks1.get(1).threadId()); + assertNotSame(explicitLocks2.get(0).threadId(), explicitLocks2.get(1).threadId()); + + // Check lock owners. + CacheLockView owner0 = F.find(keyLocks0, null, CacheLockView::isOwner); + CacheLockView owner1 = F.find(keyLocks1, null, CacheLockView::isOwner); + CacheLockView owner2 = F.find(keyLocks2, null, CacheLockView::isOwner); + + assertNotNull(owner0); + assertNotNull(owner1); + assertNotNull(owner2); + + assertEquals(owner0.originatingNodeId(), ignite0.localNode().id()); + assertEquals(1, F.size(explicitLocks0, l -> l.xid().equals(owner0.originatingXid()))); + + assertEquals(owner1.originatingNodeId(), ignite0.localNode().id()); + assertEquals(1, F.size(explicitLocks0, l -> l.xid().equals(owner1.originatingXid()))); + + assertEquals(owner2.originatingNodeId(), ignite1.localNode().id()); + assertEquals(1, F.size(explicitLocks1, l -> l.xid().equals(owner2.originatingXid()))); + + // Check waiting locks. + assertEquals(1, F.size(keyLocks0, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite1.localNode().id()))); + assertEquals(1, F.size(keyLocks0, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite2.localNode().id()))); + assertEquals(1, F.size(keyLocks1, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite2.localNode().id()))); + + finishLatch.countDown(); + + fut0.get(); + fut1.get(); + fut2a.get(); + fut2b.get(); + } + finally { + finishLatch.countDown(); + } + } + + /** */ + @Test + public void testTxLocks() throws Exception { + IgniteEx ignite0 = grid(0); + IgniteEx ignite1 = grid(1); + IgniteEx ignite2 = grid(2); + + CountDownLatch finishLatch = new CountDownLatch(1); + + try { + Integer key0 = primaryKey(ignite0.cache(DEFAULT_CACHE_NAME)); + Integer key1 = primaryKey(ignite1.cache(DEFAULT_CACHE_NAME)); + Integer key2 = primaryKey(ignite2.cache(DEFAULT_CACHE_NAME)); + + CountDownLatch firstNodeLatch = new CountDownLatch(1); + + // Block key0 and key1 from node0. + Runnable task0 = txLockTask(ignite0, () -> { + firstNodeLatch.countDown(); + U.awaitQuiet(finishLatch); + }, key0, key1); + + // Block key2 and wait for key0 from node1. + Runnable task1 = txLockTask(ignite1, () -> {}, key2, key0); + + // Wait for key0 from node2. + Runnable task2a = txLockTask(ignite2, () -> {}, key0); + + // Wait for key1 from node2. + Runnable task2b = txLockTask(ignite2, () -> {}, key1); + + IgniteInternalFuture fut0 = GridTestUtils.runAsync(task0); + firstNodeLatch.await(); + IgniteInternalFuture fut1 = GridTestUtils.runAsync(task1); + IgniteInternalFuture fut2a = GridTestUtils.runAsync(task2a); + IgniteInternalFuture fut2b = GridTestUtils.runAsync(task2b); + + List txs0 = viewContent(ignite0, TXS_MON_LIST, 3); // 1 near + 2 dht. + List txs1 = viewContent(ignite1, TXS_MON_LIST, 3); // 1 near + 2 dht + List txs2 = viewContent(ignite2, TXS_MON_LIST, 3); // 2 near + 1 dht + + List keyLocks0 = viewContent(ignite0, CACHE_LOCKS_VIEW, 3); + List keyLocks1 = viewContent(ignite1, CACHE_LOCKS_VIEW, 2); + List keyLocks2 = viewContent(ignite2, CACHE_LOCKS_VIEW, 1); + + // Check lock owners. + CacheLockView owner0 = F.find(keyLocks0, null, CacheLockView::isOwner); + CacheLockView owner1 = F.find(keyLocks1, null, CacheLockView::isOwner); + CacheLockView owner2 = F.find(keyLocks2, null, CacheLockView::isOwner); + + assertNotNull(owner0); + assertNotNull(owner1); + assertNotNull(owner2); + + assertEquals(owner0.originatingNodeId(), ignite0.localNode().id()); + assertEquals(1, F.size(txs0, l -> l.xid().equals(owner0.originatingXid()) && l.xid().equals(owner0.xid()))); + + assertEquals(owner1.originatingNodeId(), ignite0.localNode().id()); + assertEquals(1, F.size(txs0, l -> l.xid().equals(owner1.originatingXid()))); // Near. + assertEquals(1, F.size(txs1, l -> l.xid().equals(owner1.xid()))); // Dht. + + assertEquals(owner2.originatingNodeId(), ignite1.localNode().id()); + assertEquals(1, F.size(txs1, l -> l.xid().equals(owner2.originatingXid()))); // Near. + assertEquals(1, F.size(txs2, l -> l.xid().equals(owner2.xid()))); // Dht. + + // Check waiting locks. + CacheLockView waiting1on0 = F.find(keyLocks0, null, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite1.localNode().id())); + CacheLockView waiting2on0 = F.find(keyLocks0, null, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite2.localNode().id())); + CacheLockView waiting2on1 = F.find(keyLocks1, null, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite2.localNode().id())); + + assertNotNull(waiting1on0); + assertNotNull(waiting2on0); + assertNotNull(waiting2on1); + + assertEquals(1, F.size(txs1, l -> l.xid().equals(waiting1on0.originatingXid()))); // Near. + assertEquals(1, F.size(txs0, l -> l.xid().equals(waiting1on0.xid()))); // Dht. + + assertEquals(1, F.size(txs2, l -> l.xid().equals(waiting2on0.originatingXid()))); // Near. + assertEquals(1, F.size(txs0, l -> l.xid().equals(waiting2on0.xid()))); // Dht. + + assertEquals(1, F.size(txs2, l -> l.xid().equals(waiting2on1.originatingXid()))); // Near. + assertEquals(1, F.size(txs1, l -> l.xid().equals(waiting2on1.xid()))); // Dht. + + finishLatch.countDown(); + + fut0.get(); + fut1.get(); + fut2a.get(); + fut2b.get(); + } + finally { + finishLatch.countDown(); + } + } + + /** */ + @Test + public void testMixedExplicitTxLocks() throws Exception { + IgniteEx ignite0 = grid(0); + IgniteEx ignite1 = grid(1); + IgniteEx ignite2 = grid(2); + + CountDownLatch finishLatch = new CountDownLatch(1); + + try { + Integer key1 = primaryKey(ignite1.cache(DEFAULT_CACHE_NAME)); + Integer key2 = primaryKey(ignite2.cache(DEFAULT_CACHE_NAME)); + + CountDownLatch firstNodeLatch = new CountDownLatch(1); + CountDownLatch secondNodeLatch = new CountDownLatch(1); + + // Block key1 from node0. + Runnable task0 = explicitLockTask(ignite0, () -> { + firstNodeLatch.countDown(); + U.awaitQuiet(finishLatch); + }, key1); + + // Block key2 from node1. + Runnable task1 = txLockTask(ignite1, () -> { + secondNodeLatch.countDown(); + U.awaitQuiet(finishLatch); + }, key2); + + // Wait for key2 from node2. + Runnable task2a = explicitLockTask(ignite2, () -> {}, key2); + + // Wait for key1 from node2. + Runnable task2b = txLockTask(ignite2, () -> {}, key1); + + IgniteInternalFuture fut0 = GridTestUtils.runAsync(task0); + firstNodeLatch.await(); + IgniteInternalFuture fut1 = GridTestUtils.runAsync(task1); + secondNodeLatch.await(); + IgniteInternalFuture fut2a = GridTestUtils.runAsync(task2a); + IgniteInternalFuture fut2b = GridTestUtils.runAsync(task2b); + + List explicitLocks0 = viewContent(ignite0, CACHE_EXPLICIT_LOCKS_VIEW, 1); + List explicitLocks2 = viewContent(ignite2, CACHE_EXPLICIT_LOCKS_VIEW, 1); + List txs1 = viewContent(ignite1, TXS_MON_LIST, 2); // 1 near + 1 dht + List txs2 = viewContent(ignite2, TXS_MON_LIST, 2); // 1 near + 1 dht + + List keyLocks1 = viewContent(ignite1, CACHE_LOCKS_VIEW, 2); + List keyLocks2 = viewContent(ignite2, CACHE_LOCKS_VIEW, 2); + + // Check lock owners. + CacheLockView owner1 = F.find(keyLocks1, null, CacheLockView::isOwner); + CacheLockView owner2 = F.find(keyLocks2, null, CacheLockView::isOwner); + + assertNotNull(owner1); + assertNotNull(owner2); + + assertEquals(owner1.originatingNodeId(), ignite0.localNode().id()); + assertEquals(1, F.size(explicitLocks0, l -> l.xid().equals(owner1.originatingXid()))); + + assertEquals(owner2.originatingNodeId(), ignite1.localNode().id()); + assertEquals(1, F.size(txs1, l -> l.xid().equals(owner2.originatingXid()))); // Near. + assertEquals(1, F.size(txs2, l -> l.xid().equals(owner2.xid()))); // Dht. + + // Check waiting locks. + CacheLockView waiting2on1 = F.find(keyLocks1, null, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite2.localNode().id())); + CacheLockView waiting2on2 = F.find(keyLocks2, null, + l -> !l.isOwner() && l.originatingNodeId().equals(ignite2.localNode().id())); + + assertNotNull(waiting2on1); + assertNotNull(waiting2on2); + + assertEquals(1, F.size(txs2, l -> l.xid().equals(waiting2on1.originatingXid()))); // Near. + assertEquals(1, F.size(txs1, l -> l.xid().equals(waiting2on1.xid()))); // Dht. + + assertEquals(1, F.size(explicitLocks2, l -> l.xid().equals(waiting2on2.originatingXid()))); + + finishLatch.countDown(); + + fut0.get(); + fut1.get(); + fut2a.get(); + fut2b.get(); + } + finally { + finishLatch.countDown(); + } + } + + /** */ + private static Runnable explicitLockTask(IgniteEx ignite, Runnable body, int... keysToLock) { + return () -> { + Lock[] locks = new Lock[keysToLock.length]; + + for (int i = 0; i < keysToLock.length; i++) { + locks[i] = ignite.cache(DEFAULT_CACHE_NAME).lock(keysToLock[i]); + locks[i].lock(); + } + + try { + body.run(); + } + finally { + for (int i = keysToLock.length - 1; i >= 0; i--) + locks[i].unlock(); + } + }; + } + + /** */ + private static Runnable txLockTask(IgniteEx ignite, Runnable body, int... keysToLock) { + return () -> { + try (Transaction tx = ignite.transactions().txStart(PESSIMISTIC, REPEATABLE_READ)) { + for (int i = 0; i < keysToLock.length; i++) + ignite.cache(DEFAULT_CACHE_NAME).put(keysToLock[i], 0); + + body.run(); + + tx.commit(); + } + }; + } + + /** */ + private static List viewContent(IgniteEx ignite, String viewName, int expSize) throws Exception { + SystemView view = ignite.context().systemView().view(viewName); + + assertTrue("Failed to wait for view size [ignite=" + ignite.name() + ", viewName=" + viewName + + ", expSize=" + expSize + ", actSize=" + F.size(view.iterator()), + waitForCondition(() -> F.size(view.iterator()) == expSize, 1_000L)); + + return U.arrayList(view.iterator(), expSize); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java index 2f4d1787be3f9..4426afc205343 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java @@ -45,6 +45,7 @@ import org.apache.ignite.internal.metric.SystemViewConfigurationTest; import org.apache.ignite.internal.metric.SystemViewDSTest; import org.apache.ignite.internal.metric.SystemViewExecutorsTest; +import org.apache.ignite.internal.metric.SystemViewLocksTest; import org.apache.ignite.internal.metric.SystemViewMetastorageTest; import org.apache.ignite.internal.metric.SystemViewNodesTest; import org.apache.ignite.internal.metric.SystemViewPageListsTest; @@ -122,6 +123,7 @@ public static List> suite(Collection ignoredTests) { GridTestUtils.addTestIfNeeded(suite, SystemViewConfigurationTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, SystemViewPluginTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, SystemViewQueriesTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, SystemViewLocksTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, CacheMetricsAddRemoveTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, CacheMetricsConflictResolverTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, JmxExporterSpiTest.class, ignoredTests); diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java index 15a512dbaab88..e98770d4a5bb1 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java @@ -465,7 +465,9 @@ public void testViews() { "DS_QUEUES", "PAGES_TIMESTAMP_HISTOGRAM", "SQL_PLANS_HISTORY", - "IGNITE_PLUGINS" + "IGNITE_PLUGINS", + "CACHE_LOCKS", + "CACHE_EXPLICIT_LOCKS" )); Set actViews = new TreeSet<>(); diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java index 5c74bdf53aa5a..87f000f273822 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java @@ -32,6 +32,7 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; import java.util.function.BiConsumer; import java.util.stream.Collectors; import java.util.stream.LongStream; @@ -81,6 +82,7 @@ import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.X; +import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgnitePredicate; @@ -92,6 +94,7 @@ import org.apache.ignite.spi.systemview.view.SystemView; import org.apache.ignite.spi.systemview.view.sql.SqlTableView; import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.transactions.Transaction; import org.junit.Assert; import org.junit.Test; @@ -102,6 +105,8 @@ import static org.apache.ignite.internal.processors.query.running.RunningQueryManager.SQL_QRY_VIEW; import static org.apache.ignite.internal.util.IgniteUtils.MB; import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; +import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; +import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; import static org.junit.Assert.assertNotEquals; /** @@ -1896,6 +1901,56 @@ public void testPluginView() throws Exception { } } + /** */ + @Test + public void testLocksView() throws Exception { + try (IgniteEx ignite = startGrid()) { + IgniteCache cache = ignite.getOrCreateCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) + .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL)); + + Lock lock1 = cache.lock(1); + Lock lock2 = cache.lock(2); + + lock1.lock(); + lock2.lock(); + try (Transaction ignored = ignite.transactions().txStart(PESSIMISTIC, REPEATABLE_READ)) { + cache.put(3, 3); + + // Join explicit locks with key locks. + List> res = execSql("SELECT el.cache_id, el.thread_id, kl.is_owner, kl.is_tx, kl.originating_node_id " + + "FROM SYS.CACHE_EXPLICIT_LOCKS el JOIN SYS.CACHE_LOCKS kl ON el.xid = kl.xid"); + + assertEquals(2, res.size()); + + for (List lock : res) { + assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), lock.get(0)); + assertEquals(Thread.currentThread().getId(), lock.get(1)); + assertEquals(true, lock.get(2)); + assertEquals(false, lock.get(3)); + assertEquals(ignite.localNode().id(), lock.get(4)); + } + + // Join transactions with key locks. + res = execSql("SELECT kl.cache_id, tx.thread_id, kl.is_owner, kl.is_tx, kl.originating_node_id " + + "FROM SYS.TRANSACTIONS tx JOIN SYS.CACHE_LOCKS kl ON tx.xid = kl.xid"); + + assertEquals(1, res.size()); + + for (List lock : res) { + assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), lock.get(0)); + assertEquals(Thread.currentThread().getId(), lock.get(1)); + assertEquals(true, lock.get(2)); + assertEquals(true, lock.get(3)); + assertEquals(ignite.localNode().id(), lock.get(4)); + } + } + finally { + lock2.unlock(); + lock1.unlock(); + } + } + } + /** * Mock for {@link ClusterMetricsImpl} that always returns big (more than 24h) duration for all duration metrics. */