You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2019/06/05 11:08:32 UTC
[ignite] 29/31: GG-19221 [GG-18667]-[IGNITE-11750] Implement locked
pages info dump for long-running B+Tree operations
This is an automated email from the ASF dual-hosted git repository.
amashenkov pushed a commit to branch gg-19225
in repository https://gitbox.apache.org/repos/asf/ignite.git
commit 76289ae047f2ab53c7f3f8ec5a4702d0f948eab2
Author: Dmitriy Govorukhin <dm...@gmail.com>
AuthorDate: Wed Jun 5 13:27:00 2019 +0300
GG-19221 [GG-18667]-[IGNITE-11750] Implement locked pages info dump for long-running B+Tree operations
---
.../JmhPageLockTrackerBenchmark.java | 143 ++++
.../stack/LockTrackerNoBarrier.java | 65 ++
.../benchmarks/jmh/tree/BPlusTreeBenchmark.java | 15 +-
.../org/apache/ignite/IgniteSystemProperties.java | 33 +
.../ignite/internal/commandline/CommandList.java | 10 +-
.../commandline/diagnostic/DiagnosticCommand.java | 114 ++++
.../diagnostic/DiagnosticSubCommand.java | 73 ++
.../commandline/diagnostic/PageLocksCommand.java | 210 ++++++
.../processors/cache/CacheDiagnosticManager.java | 134 ++++
.../processors/cache/GridCacheProcessor.java | 11 +-
.../processors/cache/GridCacheSharedContext.java | 25 +-
.../cache/IgniteCacheOffheapManager.java | 5 -
.../cache/IgniteCacheOffheapManagerImpl.java | 37 +-
.../processors/cache/mvcc/txlog/TxLog.java | 37 +-
.../processors/cache/mvcc/txlog/TxLogTree.java | 30 +-
.../cache/persistence/DataStructure.java | 113 ++--
.../cache/persistence/GridCacheOffheapManager.java | 93 ++-
.../IgniteCacheDatabaseSharedManager.java | 50 +-
.../cache/persistence/IndexStorageImpl.java | 38 +-
.../diagnostic/pagelocktracker/DumpProcessor.java | 40 ++
.../diagnostic/pagelocktracker/DumpSupported.java | 42 ++
.../diagnostic/pagelocktracker/InvalidContext.java | 38 ++
.../pagelocktracker/LockTrackerFactory.java | 114 ++++
.../diagnostic/pagelocktracker/PageLockDump.java | 32 +
.../PageLockListenerIndexAdapter.java | 69 ++
.../pagelocktracker/PageLockTracker.java | 365 ++++++++++
.../pagelocktracker/PageLockTrackerMXBean.java | 58 ++
.../pagelocktracker/PageLockTrackerMXBeanImpl.java | 59 ++
.../pagelocktracker/PageLockTrackerManager.java | 270 ++++++++
.../pagelocktracker/PageMetaInfoStore.java | 89 +++
.../pagelocktracker/SharedPageLockTracker.java | 444 ++++++++++++
.../pagelocktracker/ThreadPageLocksDumpLock.java | 91 +++
.../dumpprocessors/ToFileDumpProcessor.java | 83 +++
.../dumpprocessors/ToStringDumpProcessor.java | 335 +++++++++
.../diagnostic/pagelocktracker/log/LockLog.java | 137 ++++
.../pagelocktracker/log/PageLockLogSnapshot.java | 138 ++++
.../pagelocktracker/stack/LockStack.java | 188 ++++++
.../stack/PageLockStackSnapshot.java | 79 +++
.../store/HeapPageMetaInfoStore.java | 162 +++++
.../store/OffHeapPageMetaInfoStore.java | 200 ++++++
.../persistence/freelist/AbstractFreeList.java | 13 +-
.../{CacheFreeListImpl.java => CacheFreeList.java} | 33 +-
.../cache/persistence/freelist/PagesList.java | 12 +-
.../cache/persistence/metastorage/MetaStorage.java | 51 +-
.../persistence/metastorage/MetastorageTree.java | 24 +-
.../UpgradePendingTreeToPerPartitionTask.java | 3 +-
.../cache/persistence/tree/BPlusTree.java | 22 +-
.../persistence/tree/reuse/ReuseListImpl.java | 24 +-
.../wal/reader/IgniteWalIteratorFactory.java | 2 +-
.../processors/cache/tree/CacheDataTree.java | 11 +-
.../processors/cache/tree/PendingEntriesTree.java | 13 +-
.../processors/diagnostic/DiagnosticProcessor.java | 8 +
.../visor/diagnostic/VisorPageLocksResult.java | 82 +++
.../visor/diagnostic/VisorPageLocksTask.java | 141 ++++
.../diagnostic/VisorPageLocksTrackerArgs.java | 135 ++++
.../main/resources/META-INF/classnames.properties | 4 +
.../db/wal/IgniteWalIteratorSwitchSegmentTest.java | 2 +
.../db/wal/WalRecoveryTxLogicalRecordsTest.java | 4 +-
.../pagelocktracker/AbstractPageLockTest.java | 56 ++
.../PageLockTrackerMXBeanImplTest.java | 80 +++
.../PageLockTrackerManagerTest.java | 218 ++++++
.../pagelocktracker/PageLockTrackerTestSuit.java | 43 ++
.../pagelocktracker/SharedPageLockTrackerTest.java | 541 +++++++++++++++
.../dumpprocessors/ToFileDumpProcessorTest.java | 108 +++
.../pagelocktracker/log/HeapArrayLockLogTest.java | 29 +
.../pagelocktracker/log/OffHeapLockLogTest.java | 29 +
.../pagelocktracker/log/PageLockLogTest.java | 641 ++++++++++++++++++
.../stack/HeapArrayLockStackTest.java | 29 +
.../stack/OffHeapLockStackTest.java | 29 +
.../pagelocktracker/stack/PageLockStackTest.java | 751 +++++++++++++++++++++
.../pagemem/BPlusTreePageMemoryImplTest.java | 4 +-
.../BPlusTreeReuseListPageMemoryImplTest.java | 1 +
.../pagemem/IndexStoragePageMemoryImplTest.java | 4 +-
.../pagemem/PageMemoryImplNoLoadTest.java | 1 +
.../persistence/pagemem/PageMemoryImplTest.java | 1 +
.../database/BPlusTreeReuseSelfTest.java | 62 +-
.../processors/database/BPlusTreeSelfTest.java | 277 ++++----
...mplSelfTest.java => CacheFreeListSelfTest.java} | 22 +-
.../processors/database/IndexStorageSelfTest.java | 1 +
.../loadtests/hashmap/GridCacheTestContext.java | 10 +-
.../ignite/testsuites/IgniteBasicTestSuite.java | 4 +-
.../ignite/testsuites/IgnitePdsTestSuite4.java | 16 +
.../apache/ignite/util/GridCommandHandlerTest.java | 52 +-
.../processors/query/h2/IgniteH2Indexing.java | 9 +-
.../processors/query/h2/database/H2Tree.java | 12 +-
85 files changed, 7522 insertions(+), 361 deletions(-)
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/JmhPageLockTrackerBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/JmhPageLockTrackerBenchmark.java
new file mode 100644
index 0000000..41dd51d
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/JmhPageLockTrackerBenchmark.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.benchmarks.jmh.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.benchmarks.jmh.diagnostic.pagelocktracker.stack.LockTrackerNoBarrier;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+
+/**
+ * Benchmark PageLockTracker (factory LockTrackerFactory)
+ */
+public class JmhPageLockTrackerBenchmark {
+ /**
+ * @param args Params.
+ */
+ public static void main(String[] args) throws Exception {
+ Options opt = new OptionsBuilder()
+ .include(JmhPageLockTrackerBenchmark.class.getSimpleName())
+ .build();
+
+ new Runner(opt).run();
+ }
+
+ /** */
+ @State(Scope.Thread)
+ public static class ThreadLocalState {
+ PageLockListener pl;
+
+ @Param({"2", "4", "8", "16"})
+ int stackSize;
+
+ @Param({
+ "HeapArrayLockStack",
+ "HeapArrayLockLog",
+ "OffHeapLockStack",
+ "OffHeapLockLog"
+ })
+ String type;
+
+ @Param({"true", "false"})
+ boolean barrier;
+
+ int StructureId = 123;
+
+ @Setup
+ public void doSetup() {
+ pl = create(Thread.currentThread().getName(), type, barrier);
+ }
+ }
+
+ /**
+ * Mesure cost for (beforelock -> lock -> unlock) operation.
+ */
+ @Benchmark
+ @BenchmarkMode(Mode.Throughput)
+ @Fork(1)
+ @Warmup(iterations = 10)
+ @Measurement(iterations = 10)
+ //@OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public void lockUnlock(ThreadLocalState localState) {
+ PageLockListener pl = localState.pl;
+
+ for (int i = 0; i < localState.stackSize; i++) {
+ int pageId = i + 1;
+
+ pl.onBeforeReadLock(localState.StructureId, pageId, pageId);
+
+ pl.onReadLock(localState.StructureId, pageId, pageId, pageId);
+ }
+
+ for (int i = localState.stackSize; i > 0; i--) {
+ int pageId = i;
+
+ pl.onReadUnlock(localState.StructureId, pageId, pageId, pageId);
+ }
+ }
+
+ /**
+ * Factory method.
+ *
+ * @param name Lock tracer name.
+ * @param type Lock tracer type.
+ * @param barrier If {@code True} use real implementation,
+ * if {@code False} use implementation with safety dump barrier.
+ * @return Page lock tracker as PageLockListener.
+ */
+ private static PageLockListener create(String name, String type, boolean barrier) {
+ PageLockTracker tracker;
+
+ switch (type) {
+ case "HeapArrayLockStack":
+ tracker = LockTrackerFactory.create(HEAP_STACK, name);
+ break;
+ case "HeapArrayLockLog":
+ tracker = LockTrackerFactory.create(HEAP_LOG, name);
+ break;
+ case "OffHeapLockStack":
+ tracker = LockTrackerFactory.create(OFF_HEAP_STACK, name);
+ break;
+
+ case "OffHeapLockLog":
+ tracker = LockTrackerFactory.create(OFF_HEAP_LOG, name);
+ break;
+ default:
+ throw new IllegalArgumentException("type:" + type);
+ }
+
+ return barrier ? tracker : new LockTrackerNoBarrier(tracker);
+ }
+}
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/stack/LockTrackerNoBarrier.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/stack/LockTrackerNoBarrier.java
new file mode 100644
index 0000000..dc3b3ff
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/stack/LockTrackerNoBarrier.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.benchmarks.jmh.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+
+/**
+ * Local without barrier syncronization on operation.
+ */
+public class LockTrackerNoBarrier implements PageLockListener {
+ /** */
+ private final PageLockTracker delegate;
+
+ /** */
+ public LockTrackerNoBarrier(
+ PageLockTracker delegate
+ ) {
+ this.delegate = delegate;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+ delegate.onBeforeWriteLock0(cacheId, pageId, page);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onWriteLock0(cacheId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onWriteUnlock0(cacheId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+ delegate.onBeforeReadLock0(cacheId, pageId, page);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onReadLock0(cacheId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onReadUnlock(cacheId, pageId, page, pageAddr);
+ }
+}
\ No newline at end of file
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
index 6513c13..586b85c 100644
--- a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
@@ -174,8 +174,19 @@ public class BPlusTreeBenchmark extends JmhAbstractBenchmark {
*/
TestTree(ReuseList reuseList, int cacheId, PageMemory pageMem, long metaPageId)
throws IgniteCheckedException {
- super("test", cacheId, pageMem, null, new AtomicLong(), metaPageId, reuseList,
- new IOVersions<>(new LongInnerIO()), new IOVersions<>(new LongLeafIO()), null);
+ super(
+ "test",
+ cacheId,
+ pageMem,
+ null,
+ new AtomicLong(),
+ metaPageId,
+ reuseList,
+ new IOVersions<>(new LongInnerIO()),
+ new IOVersions<>(new LongLeafIO()),
+ null,
+ null
+ );
PageIO.registerTest(latestInnerIO(), latestLeafIO());
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index 4e7b312..ee9eac9 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -1120,6 +1120,39 @@ public final class IgniteSystemProperties {
public static final String IGNITE_TX_OWNER_DUMP_REQUESTS_ALLOWED = "IGNITE_TX_OWNER_DUMP_REQUESTS_ALLOWED";
/**
+ * Page lock tracker type.
+ * -1 - Disable lock tracking.
+ * 1 - HEAP_STACK
+ * 2 - HEAP_LOG
+ * 3 - OFF_HEAP_STACK
+ * 4 - OFF_HEAP_LOG
+ *
+ * Default is 2 - HEAP_LOG.
+ */
+ public static final String IGNITE_PAGE_LOCK_TRACKER_TYPE = "IGNITE_PAGE_LOCK_TRACKER_TYPE";
+
+ /**
+ * Capacity in pages for storing in page lock tracker strucuture.
+ *
+ * Default is 512 pages.
+ */
+ public static final String IGNITE_PAGE_LOCK_TRACKER_CAPACITY = "IGNITE_PAGE_LOCK_TRACKER_CAPACITY";
+
+ /**
+ * Page lock tracker thread for checking hangs threads interval.
+ *
+ * Default is 60_000 ms.
+ */
+ public static final String IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL = "IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL";
+
+ /**
+ * Enables threads locks dumping on critical node failure.
+ *
+ * Default is {@code true}.
+ */
+ public static final String IGNITE_DUMP_PAGE_LOCK_ON_FAILURE = "IGNITE_DUMP_PAGE_LOCK_ON_FAILURE";
+
+ /**
* Enforces singleton.
*/
private IgniteSystemProperties() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
index d595e05..232b0f5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.commandline;
import org.apache.ignite.internal.commandline.cache.CacheCommands;
+import org.apache.ignite.internal.commandline.diagnostic.DiagnosticCommand;
/**
* High-level commands.
@@ -41,7 +42,10 @@ public enum CommandList {
CACHE("--cache", new CacheCommands()),
/** */
- WAL("--wal", new WalCommands());
+ WAL("--wal", new WalCommands()),
+
+ /** */
+ DIAGNOSTIC("--diagnostic", new DiagnosticCommand());
/** Private values copy so there's no need in cloning it every time. */
private static final CommandList[] VALUES = CommandList.values();
@@ -89,8 +93,8 @@ public enum CommandList {
}
/** {@inheritDoc} */
- @Override public String toString() {
- return text;
+ @Override public String toString() {
+ return text;
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticCommand.java
new file mode 100644
index 0000000..97e0218
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticCommand.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.commandline.diagnostic;
+
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.CommandLogger;
+
+import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static org.apache.ignite.internal.commandline.CommandList.DIAGNOSTIC;
+import static org.apache.ignite.internal.commandline.CommandLogger.join;
+import static org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand.HELP;
+import static org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand.PAGE_LOCKS;
+
+/**
+ *
+ */
+public class DiagnosticCommand implements Command<DiagnosticSubCommand> {
+ /**
+ *
+ */
+ private DiagnosticSubCommand subcommand;
+
+ /**
+ *
+ */
+ private CommandLogger logger;
+
+ /** {@inheritDoc} */
+ @Override public Object execute(GridClientConfiguration clientCfg, CommandLogger logger) throws Exception {
+ this.logger = logger;
+
+ if (subcommand == HELP) {
+ printDiagnosticHelp();
+
+ return null;
+ }
+
+ Command command = subcommand.subcommand();
+
+ if (command == null)
+ throw new IllegalStateException("Unknown command " + subcommand);
+
+ return command.execute(clientCfg, logger);
+ }
+
+ /** {@inheritDoc} */
+ @Override public DiagnosticSubCommand arg() {
+ return subcommand;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void parseArguments(CommandArgIterator argIter) {
+ if (!argIter.hasNextSubArg()) {
+ subcommand = HELP;
+
+ return;
+ }
+
+ String str = argIter.nextArg("").toLowerCase();
+
+ DiagnosticSubCommand cmd = DiagnosticSubCommand.of(str);
+
+ if (cmd == null)
+ cmd = HELP;
+
+ switch (cmd) {
+ case HELP:
+ break;
+
+ case PAGE_LOCKS:
+ cmd.subcommand().parseArguments(argIter);
+
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown diagnostic subcommand " + cmd);
+ }
+
+ if (argIter.hasNextSubArg())
+ throw new IllegalArgumentException("Unexpected argument of diagnostic subcommand: " + argIter.peekNextArg());
+
+ subcommand = cmd;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void printUsage(CommandLogger logger) {
+ logger.logWithIndent("View diagnostic information in a cluster. For more details type:");
+ logger.logWithIndent(join(" ", UTILITY_NAME, DIAGNOSTIC, HELP), 2);
+ logger.nl();
+ }
+
+ /**
+ *
+ */
+ private void printDiagnosticHelp() {
+ logger.log(join(" ", UTILITY_NAME, DIAGNOSTIC, PAGE_LOCKS + " - dump page locks info."));
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticSubCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticSubCommand.java
new file mode 100644
index 0000000..3fd1c03
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticSubCommand.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.commandline.diagnostic;
+
+import org.apache.ignite.internal.commandline.Command;
+
+/**
+ *
+ */
+public enum DiagnosticSubCommand {
+ /** */
+ HELP("help", null),
+
+ /** */
+ PAGE_LOCKS("pageLocks", new PageLocksCommand());
+
+ /** Diagnostic command name. */
+ private final String name;
+
+ /** Command instance for certain type. */
+ private final Command command;
+
+ /**
+ * @param name Command name.
+ * @param command Command handler.
+ */
+ DiagnosticSubCommand(
+ String name,
+ Command command
+ ) {
+ this.name = name;
+ this.command = command;
+ }
+
+ /**
+ * @return Subcommand realization.
+ */
+ public Command subcommand() {
+ return command;
+ }
+
+ /**
+ * @param text Command text.
+ * @return Command for the text.
+ */
+ public static DiagnosticSubCommand of(String text) {
+ for (DiagnosticSubCommand cmd : DiagnosticSubCommand.values()) {
+ if (cmd.name.equalsIgnoreCase(text))
+ return cmd;
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return name;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/PageLocksCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/PageLocksCommand.java
new file mode 100644
index 0000000..c110c98
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/PageLocksCommand.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.commandline.diagnostic;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.CommandLogger;
+import org.apache.ignite.internal.commandline.TaskExecutor;
+import org.apache.ignite.internal.visor.diagnostic.VisorPageLocksResult;
+import org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTask;
+import org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTrackerArgs;
+
+import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static org.apache.ignite.internal.commandline.CommandList.DIAGNOSTIC;
+import static org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand.PAGE_LOCKS;
+
+/**
+ *
+ */
+public class PageLocksCommand implements Command<PageLocksCommand.Args> {
+ /**
+ *
+ */
+ public static final String DUMP = "dump";
+ /**
+ *
+ */
+ public static final String DUMP_LOG = "dump_log";
+
+ /**
+ *
+ */
+ private Args args;
+
+ /**
+ *
+ */
+ private CommandLogger logger;
+
+ /**
+ *
+ */
+ private boolean help;
+
+ /** {@inheritDoc} */
+ @Override public Object execute(GridClientConfiguration clientCfg, CommandLogger logger) throws Exception {
+ this.logger = logger;
+
+ if (help) {
+ help = false;
+
+ printUsage(logger);
+
+ return null;
+ }
+
+ Set<String> nodeIds = args.nodeIds;
+
+ try (GridClient client = Command.startClient(clientCfg)) {
+ if (args.allNodes) {
+ client.compute().nodes().forEach(n -> {
+ nodeIds.add(String.valueOf(n.consistentId()));
+ nodeIds.add(n.nodeId().toString());
+ });
+ }
+ }
+
+ VisorPageLocksTrackerArgs taskArg = new VisorPageLocksTrackerArgs(args.op, args.type, args.filePath, nodeIds);
+
+ Map<ClusterNode, VisorPageLocksResult> res;
+
+ try (GridClient client = Command.startClient(clientCfg)) {
+ res = TaskExecutor.executeTask(
+ client,
+ VisorPageLocksTask.class,
+ taskArg,
+ clientCfg
+ );
+ }
+
+ printResult(res);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Args arg() {
+ return args;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void parseArguments(CommandArgIterator argIter) {
+ if (argIter.hasNextSubArg()) {
+ String cmd = argIter.nextArg("").toLowerCase();
+
+ if (DUMP.equals(cmd) || DUMP_LOG.equals(cmd)) {
+ boolean allNodes = false;
+ String filePath = null;
+
+ Set<String> nodeIds = new TreeSet<>();
+
+ while (argIter.hasNextArg()){
+ String nextArg = argIter.nextArg("").toLowerCase();
+
+ if ("--all".equals(nextArg))
+ allNodes = true;
+ else if ("--nodes".equals(nextArg)) {
+ while (argIter.hasNextArg()){
+ nextArg = argIter.nextArg("").toLowerCase();
+
+ nodeIds.add(nextArg);
+ }
+ }
+ else {
+ if (new File(nextArg).isDirectory())
+ filePath = nextArg;
+ }
+ }
+
+ args = new Args(DUMP, cmd, filePath, allNodes, nodeIds);
+ }
+ else
+ help = true;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void printUsage(CommandLogger logger) {
+ logger.log("View pages locks state information on the node or nodes.");
+ logger.log(CommandLogger.join(" ",
+ UTILITY_NAME, DIAGNOSTIC, PAGE_LOCKS, DUMP,
+ "[--path path_to_directory] [--all|--nodes nodeId1,nodeId2,..|--nodes consistentId1,consistentId2,..]",
+ "// Save page locks dump to file generated in IGNITE_HOME/work directory."));
+ logger.log(CommandLogger.join(" ",
+ UTILITY_NAME, DIAGNOSTIC, PAGE_LOCKS, DUMP_LOG,
+ "[--all|--nodes nodeId1,nodeId2,..|--nodes consistentId1,consistentId2,..]",
+ "// Pring page locks dump to console on the node or nodes."));
+ logger.nl();
+ }
+
+ /**
+ * @param res Result.
+ */
+ private void printResult(Map<ClusterNode, VisorPageLocksResult> res) {
+ res.forEach((n, res0) -> {
+ logger.log(n.id() + " (" + n.consistentId() + ") " + res0.result());
+ });
+ }
+
+ /**
+ *
+ */
+ public static class Args {
+ /**
+ *
+ */
+ private final String op;
+ /**
+ *
+ */
+ private final String type;
+ /**
+ *
+ */
+ private final String filePath;
+ /**
+ *
+ */
+ private final boolean allNodes;
+ /**
+ *
+ */
+ private final Set<String> nodeIds;
+
+ /**
+ * @param op Operation.
+ * @param type Type.
+ * @param filePath File path.
+ * @param nodeIds Node ids.
+ */
+ public Args(String op, String type, String filePath, boolean allNodes, Set<String> nodeIds) {
+ this.op = op;
+ this.type = type;
+ this.filePath = filePath;
+ this.allNodes = allNodes;
+ this.nodeIds = nodeIds;
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheDiagnosticManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheDiagnosticManager.java
new file mode 100644
index 0000000..ec271fb
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheDiagnosticManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache;
+
+import javax.management.InstanceNotFoundException;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerMXBean;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Component for manage all cache diagnostic functionality.
+ */
+public class CacheDiagnosticManager extends GridCacheSharedManagerAdapter {
+ /** Diagnostic mxbeans group name. */
+ public static final String MBEAN_GROUP = "Diagnostic";
+
+ /** Page lock tracker manager */
+ private PageLockTrackerManager pageLockTrackerManager;
+
+ /** {@inheritDoc} */
+ @Override protected void start0() throws IgniteCheckedException {
+ super.start0();
+
+ String name = cctx.kernalContext().pdsFolderResolver().resolveFolders().consistentId().toString();
+
+ pageLockTrackerManager = new PageLockTrackerManager(log, name);
+
+ pageLockTrackerManager.start();
+
+ registerMetricsMBean(
+ cctx.gridConfig(),
+ MBEAN_GROUP,
+ PageLockTrackerMXBean.MBEAN_NAME,
+ pageLockTrackerManager.mxBean(),
+ PageLockTrackerMXBean.class
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void stop0(boolean cancel) {
+ super.stop0(cancel);
+
+ unregisterMetricsMBean(cctx.gridConfig(), MBEAN_GROUP, PageLockTrackerMXBean.MBEAN_NAME);
+
+ pageLockTrackerManager.stop();
+ }
+
+ /**
+ * Getter.
+ *
+ * @return Page lock tracker mananger.
+ */
+ public PageLockTrackerManager pageLockTracker() {
+ return pageLockTrackerManager;
+ }
+
+ /**
+ * @param cfg Ignite configuration.
+ * @param groupName Name of group.
+ * @param mbeanName Metrics MBean name.
+ * @param impl Metrics implementation.
+ * @param clazz Metrics class type.
+ */
+ protected <T> void registerMetricsMBean(
+ IgniteConfiguration cfg,
+ String groupName,
+ String mbeanName,
+ T impl,
+ Class<T> clazz
+ ) {
+ if (U.IGNITE_MBEANS_DISABLED)
+ return;
+
+ try {
+ U.registerMBean(
+ cfg.getMBeanServer(),
+ cfg.getIgniteInstanceName(),
+ groupName,
+ mbeanName,
+ impl,
+ clazz);
+ }
+ catch (Throwable e) {
+ U.error(log, "Failed to register MBean with name: " + mbeanName, e);
+ }
+ }
+
+ /**
+ * @param cfg Ignite configuration.
+ * @param groupName Name of group.
+ * @param name Name of MBean.
+ */
+ protected void unregisterMetricsMBean(
+ IgniteConfiguration cfg,
+ String groupName,
+ String name
+ ) {
+ if (U.IGNITE_MBEANS_DISABLED)
+ return;
+
+ assert cfg != null;
+
+ try {
+ cfg.getMBeanServer().unregisterMBean(
+ U.makeMBeanName(
+ cfg.getIgniteInstanceName(),
+ groupName,
+ name
+ ));
+ }
+ catch (InstanceNotFoundException ignored) {
+ // We tried to unregister a non-existing MBean, not a big deal.
+ }
+ catch (Throwable e) {
+ U.error(log, "Failed to unregister MBean for memory metrics: " + name, e);
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
index 167cdcc..8c3c871 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
@@ -3340,8 +3340,10 @@ public class GridCacheProcessor extends GridProcessorAdapter {
* @throws IgniteCheckedException If failed.
*/
@SuppressWarnings("unchecked")
- private GridCacheSharedContext createSharedContext(GridKernalContext kernalCtx,
- Collection<CacheStoreSessionListener> storeSesLsnrs) throws IgniteCheckedException {
+ private GridCacheSharedContext createSharedContext(
+ GridKernalContext kernalCtx,
+ Collection<CacheStoreSessionListener> storeSesLsnrs
+ ) throws IgniteCheckedException {
IgniteTxManager tm = new IgniteTxManager();
GridCacheMvccManager mvccMgr = new GridCacheMvccManager();
GridCacheVersionManager verMgr = new GridCacheVersionManager();
@@ -3392,6 +3394,8 @@ public class GridCacheProcessor extends GridProcessorAdapter {
DeadlockDetectionManager deadlockDetectionMgr = new DeadlockDetectionManager();
+ CacheDiagnosticManager diagnosticMgr = new CacheDiagnosticManager();
+
return new GridCacheSharedContext(
kernalCtx,
tm,
@@ -3411,7 +3415,8 @@ public class GridCacheProcessor extends GridProcessorAdapter {
jta,
storeSesLsnrs,
mvccCachingMgr,
- deadlockDetectionMgr
+ deadlockDetectionMgr,
+ diagnosticMgr
);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
index d130fad..ba9dc9a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
@@ -142,6 +142,9 @@ public class GridCacheSharedContext<K, V> {
/** Tx metrics. */
private final TransactionMetricsAdapter txMetrics;
+ /** Cache diagnostic manager. */
+ private CacheDiagnosticManager diagnosticMgr;
+
/** Store session listeners. */
private Collection<CacheStoreSessionListener> storeSesLsnrs;
@@ -221,7 +224,8 @@ public class GridCacheSharedContext<K, V> {
CacheJtaManagerAdapter jtaMgr,
Collection<CacheStoreSessionListener> storeSesLsnrs,
MvccCachingManager mvccCachingMgr,
- DeadlockDetectionManager deadlockDetectionMgr
+ DeadlockDetectionManager deadlockDetectionMgr,
+ CacheDiagnosticManager diagnosticMgr
) {
this.kernalCtx = kernalCtx;
@@ -243,7 +247,8 @@ public class GridCacheSharedContext<K, V> {
ttlMgr,
evictMgr,
mvccCachingMgr,
- deadlockDetectionMgr
+ deadlockDetectionMgr,
+ diagnosticMgr
);
this.storeSesLsnrs = storeSesLsnrs;
@@ -411,7 +416,9 @@ public class GridCacheSharedContext<K, V> {
ttlMgr,
evictMgr,
mvccCachingMgr,
- deadlockDetectionMgr);
+ deadlockDetectionMgr,
+ diagnosticMgr
+ );
this.mgrs = mgrs;
@@ -458,7 +465,10 @@ public class GridCacheSharedContext<K, V> {
GridCacheSharedTtlCleanupManager ttlMgr,
PartitionsEvictManager evictMgr,
MvccCachingManager mvccCachingMgr,
- DeadlockDetectionManager deadlockDetectionMgr) {
+ DeadlockDetectionManager deadlockDetectionMgr,
+ CacheDiagnosticManager diagnosticMgr
+ ) {
+ this.diagnosticMgr = add(mgrs, diagnosticMgr);
this.mvccMgr = add(mgrs, mvccMgr);
this.verMgr = add(mgrs, verMgr);
this.txMgr = add(mgrs, txMgr);
@@ -829,6 +839,13 @@ public class GridCacheSharedContext<K, V> {
}
/**
+ * @return Diagnostic manager.
+ */
+ public CacheDiagnosticManager diagnostic(){
+ return diagnosticMgr;
+ }
+
+ /**
* @return Deadlock detection manager.
*/
public DeadlockDetectionManager deadlockDetectionMgr() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
index f8fe509..90a9370 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
@@ -593,11 +593,6 @@ public interface IgniteCacheOffheapManager {
int partId();
/**
- * @return Store name.
- */
- String name();
-
- /**
* @param size Size to init.
* @param updCntr Update counter to init.
* @param cacheSizes Cache sizes if store belongs to group containing multiple caches.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
index 9592c1e..201fdc4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
@@ -67,6 +67,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageI
import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
import org.apache.ignite.internal.processors.cache.tree.CacheDataRowStore;
import org.apache.ignite.internal.processors.cache.tree.CacheDataTree;
@@ -205,17 +206,21 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
assert !cctx.group().persistenceEnabled();
if (cctx.affinityNode() && cctx.ttl().eagerTtlEnabled() && pendingEntries == null) {
- String name = "PendingEntries";
+ String pendingEntriesTreeName = cctx.name() + "##PendingEntries";
long rootPage = allocateForTree();
+ PageLockListener lsnr = ctx.diagnostic().pageLockTracker().createPageLockTracker(pendingEntriesTreeName);
+
pendingEntries = new PendingEntriesTree(
grp,
- name,
+ pendingEntriesTreeName,
grp.dataRegion().pageMemory(),
rootPage,
grp.reuseList(),
- true);
+ true,
+ lsnr
+ );
}
}
@@ -1210,23 +1215,26 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
* @return Cache data store.
* @throws IgniteCheckedException If failed.
*/
- protected CacheDataStore createCacheDataStore0(int p)
- throws IgniteCheckedException {
+ protected CacheDataStore createCacheDataStore0(int p) throws IgniteCheckedException {
final long rootPage = allocateForTree();
CacheDataRowStore rowStore = new CacheDataRowStore(grp, grp.freeList(), p);
- String idxName = treeName(p);
+ String dataTreeName = grp.cacheOrGroupName() + "-" + treeName(p);
+
+ PageLockListener lsnr = ctx.diagnostic().pageLockTracker().createPageLockTracker(dataTreeName);
CacheDataTree dataTree = new CacheDataTree(
grp,
- idxName,
+ dataTreeName,
grp.reuseList(),
rowStore,
rootPage,
- true);
+ true,
+ lsnr
+ );
- return new CacheDataStoreImpl(p, idxName, rowStore, dataTree);
+ return new CacheDataStoreImpl(p, rowStore, dataTree);
}
/** {@inheritDoc} */
@@ -1369,9 +1377,6 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
/** */
private final int partId;
- /** Tree name. */
- private String name;
-
/** */
private final CacheDataRowStore rowStore;
@@ -1398,18 +1403,15 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
/**
* @param partId Partition number.
- * @param name Name.
* @param rowStore Row store.
* @param dataTree Data tree.
*/
public CacheDataStoreImpl(
int partId,
- String name,
CacheDataRowStore rowStore,
CacheDataTree dataTree
) {
this.partId = partId;
- this.name = name;
this.rowStore = rowStore;
this.dataTree = dataTree;
}
@@ -1538,11 +1540,6 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
return pCntr.finalizeUpdateCounters();
}
- /** {@inheritDoc} */
- @Override public String name() {
- return name;
- }
-
/**
* @param cctx Cache context.
* @param oldRow Old row.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
index 238f7d0..041edfe 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
@@ -29,6 +29,7 @@ import org.apache.ignite.internal.pagemem.PageIdUtils;
import org.apache.ignite.internal.pagemem.PageMemory;
import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
import org.apache.ignite.internal.processors.cache.persistence.DbCheckpointListener;
import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
@@ -41,6 +42,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageMetaI
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseListImpl;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.util.IgniteTree;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.U;
@@ -91,7 +93,16 @@ public class TxLog implements DbCheckpointListener {
* @throws IgniteCheckedException If failed.
*/
private void init(GridKernalContext ctx) throws IgniteCheckedException {
+ String txLogName = TX_LOG_CACHE_NAME + "##Tree";
+
+ CacheDiagnosticManager diagnosticMgr = ctx.cache().context().diagnostic();
+
+ PageLockListener txLogLockLsnr = diagnosticMgr.pageLockTracker().createPageLockTracker(txLogName);
+
if (CU.isPersistenceEnabled(ctx.config())) {
+ String txLogReuseListName = TX_LOG_CACHE_NAME + "##ReuseList";
+ PageLockListener txLogReuseListLockLsnr = diagnosticMgr.pageLockTracker().createPageLockTracker(txLogReuseListName);
+
mgr.checkpointReadLock();
try {
@@ -164,9 +175,20 @@ public class TxLog implements DbCheckpointListener {
pageMemory,
wal,
reuseListRoot,
- isNew);
+ isNew,
+ txLogReuseListLockLsnr
+ );
- tree = new TxLogTree(pageMemory, wal, treeRoot, reuseList, ctx.failure(), isNew);
+ tree = new TxLogTree(
+ TX_LOG_CACHE_NAME,
+ pageMemory,
+ wal,
+ treeRoot,
+ reuseList,
+ ctx.failure(),
+ isNew,
+ txLogLockLsnr
+ );
((GridCacheDatabaseSharedManager)mgr).addCheckpointListener(this);
}
@@ -183,7 +205,16 @@ public class TxLog implements DbCheckpointListener {
if ((treeRoot = reuseList1.takeRecycledPage()) == 0L)
treeRoot = pageMemory.allocatePage(TX_LOG_CACHE_ID, INDEX_PARTITION, FLAG_IDX);
- tree = new TxLogTree(pageMemory, null, treeRoot, reuseList1, ctx.failure(), true);
+ tree = new TxLogTree(
+ txLogName,
+ pageMemory,
+ null,
+ treeRoot,
+ reuseList1,
+ ctx.failure(),
+ true,
+ txLogLockLsnr
+ );
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java
index 6c939d3..c3025fd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java
@@ -23,6 +23,7 @@ import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
/**
@@ -38,12 +39,29 @@ public class TxLogTree extends BPlusTree<TxKey, TxRow> {
* @param initNew {@code True} if new tree should be created.
* @throws IgniteCheckedException If fails.
*/
- public TxLogTree(PageMemory pageMem,
- IgniteWriteAheadLogManager wal, long metaPageId,
- ReuseList reuseList, FailureProcessor failureProcessor,
- boolean initNew) throws IgniteCheckedException {
- super(TxLog.TX_LOG_CACHE_NAME, TxLog.TX_LOG_CACHE_ID, pageMem, wal, new AtomicLong(), metaPageId,
- reuseList, TxLogInnerIO.VERSIONS, TxLogLeafIO.VERSIONS, failureProcessor);
+ public TxLogTree(
+ String name,
+ PageMemory pageMem,
+ IgniteWriteAheadLogManager wal,
+ long metaPageId,
+ ReuseList reuseList,
+ FailureProcessor failureProcessor,
+ boolean initNew,
+ PageLockListener lockLsnr
+ ) throws IgniteCheckedException {
+ super(
+ TxLog.TX_LOG_CACHE_NAME,
+ TxLog.TX_LOG_CACHE_ID,
+ pageMem,
+ wal,
+ new AtomicLong(),
+ metaPageId,
+ reuseList,
+ TxLogInnerIO.VERSIONS,
+ TxLogLeafIO.VERSIONS,
+ failureProcessor,
+ lockLsnr
+ );
initTree(initNew);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
index 56842b3..944c27f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -41,7 +41,7 @@ import static org.apache.ignite.internal.pagemem.PageIdUtils.MAX_ITEMID_NUM;
/**
* Base class for all the data structures based on {@link PageMemory}.
*/
-public abstract class DataStructure implements PageLockListener {
+public abstract class DataStructure {
/** For tests. */
public static Random rnd;
@@ -55,6 +55,9 @@ public abstract class DataStructure implements PageLockListener {
protected final IgniteWriteAheadLogManager wal;
/** */
+ protected final PageLockListener lockLsnr;
+
+ /** */
protected ReuseList reuseList;
/**
@@ -65,13 +68,15 @@ public abstract class DataStructure implements PageLockListener {
public DataStructure(
int cacheId,
PageMemory pageMem,
- IgniteWriteAheadLogManager wal
+ IgniteWriteAheadLogManager wal,
+ PageLockListener lockLsnr
) {
assert pageMem != null;
this.grpId = cacheId;
this.pageMem = pageMem;
this.wal = wal;
+ this.lockLsnr = lockLsnr == null ? NOOP_LSNR : lockLsnr;
}
/**
@@ -145,7 +150,7 @@ public abstract class DataStructure implements PageLockListener {
/**
* @param pageId Page ID.
- * @param page Page pointer.
+ * @param page Page pointer.
*/
protected final void releasePage(long pageId, long page) {
pageMem.releasePage(grpId, pageId, page);
@@ -157,7 +162,7 @@ public abstract class DataStructure implements PageLockListener {
* @return Page address or {@code 0} if failed to lock due to recycling.
*/
protected final long tryWriteLock(long pageId, long page) {
- return PageHandler.writeLock(pageMem, grpId, pageId, page, this, true);
+ return PageHandler.writeLock(pageMem, grpId, pageId, page, lockLsnr, true);
}
/**
@@ -166,13 +171,12 @@ public abstract class DataStructure implements PageLockListener {
* @return Page address.
*/
protected final long writeLock(long pageId, long page) {
- return PageHandler.writeLock(pageMem, grpId, pageId, page, this, false);
+ return PageHandler.writeLock(pageMem, grpId, pageId, page, lockLsnr, false);
}
/**
- * <p>
- * Note: Default WAL record policy will be used.
- * </p>
+ * <p> Note: Default WAL record policy will be used. </p>
+ *
* @param pageId Page ID
* @param page Page pointer.
* @param pageAddr Page address.
@@ -188,27 +192,27 @@ public abstract class DataStructure implements PageLockListener {
* @return Page address.
*/
protected final long readLock(long pageId, long page) {
- return PageHandler.readLock(pageMem, grpId, pageId, page, this);
+ return PageHandler.readLock(pageMem, grpId, pageId, page, lockLsnr);
}
/**
* @param pageId Page ID
* @param page Page pointer.
- * @param pageAddr Page address.
+ * @param pageAddr Page address.
*/
protected final void readUnlock(long pageId, long page, long pageAddr) {
- PageHandler.readUnlock(pageMem, grpId, pageId, page, pageAddr, this);
+ PageHandler.readUnlock(pageMem, grpId, pageId, page, pageAddr, lockLsnr);
}
/**
* @param pageId Page ID
* @param page Page pointer.
- * @param pageAddr Page address.
+ * @param pageAddr Page address.
* @param walPlc Full page WAL record policy.
* @param dirty Dirty flag.
*/
protected final void writeUnlock(long pageId, long page, long pageAddr, Boolean walPlc, boolean dirty) {
- PageHandler.writeUnlock(pageMem, grpId, pageId, page, pageAddr, this, walPlc, dirty);
+ PageHandler.writeUnlock(pageMem, grpId, pageId, page, pageAddr, lockLsnr, walPlc, dirty);
}
/**
@@ -233,8 +237,10 @@ public abstract class DataStructure implements PageLockListener {
long pageId,
PageHandler<?, R> h,
int intArg,
- R lockFailed) throws IgniteCheckedException {
- return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, null, intArg, lockFailed);
+ R lockFailed
+ ) throws IgniteCheckedException {
+ return PageHandler.writePage(pageMem, grpId, pageId, lockLsnr, h,
+ null, null, null, null, intArg, lockFailed);
}
/**
@@ -251,8 +257,10 @@ public abstract class DataStructure implements PageLockListener {
PageHandler<X, R> h,
X arg,
int intArg,
- R lockFailed) throws IgniteCheckedException {
- return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, arg, intArg, lockFailed);
+ R lockFailed
+ ) throws IgniteCheckedException {
+ return PageHandler.writePage(pageMem, grpId, pageId, lockLsnr, h,
+ null, null, null, arg, intArg, lockFailed);
}
/**
@@ -271,8 +279,10 @@ public abstract class DataStructure implements PageLockListener {
PageHandler<X, R> h,
X arg,
int intArg,
- R lockFailed) throws IgniteCheckedException {
- return PageHandler.writePage(pageMem, grpId, pageId, page, this, h, null, null, null, arg, intArg, lockFailed);
+ R lockFailed
+ ) throws IgniteCheckedException {
+ return PageHandler.writePage(pageMem, grpId, pageId, page, lockLsnr, h,
+ null, null, null, arg, intArg, lockFailed);
}
/**
@@ -291,8 +301,10 @@ public abstract class DataStructure implements PageLockListener {
PageIO init,
X arg,
int intArg,
- R lockFailed) throws IgniteCheckedException {
- return PageHandler.writePage(pageMem, grpId, pageId, this, h, init, wal, null, arg, intArg, lockFailed);
+ R lockFailed
+ ) throws IgniteCheckedException {
+ return PageHandler.writePage(pageMem, grpId, pageId, lockLsnr, h,
+ init, wal, null, arg, intArg, lockFailed);
}
/**
@@ -309,8 +321,10 @@ public abstract class DataStructure implements PageLockListener {
PageHandler<X, R> h,
X arg,
int intArg,
- R lockFailed) throws IgniteCheckedException {
- return PageHandler.readPage(pageMem, grpId, pageId, this, h, arg, intArg, lockFailed);
+ R lockFailed
+ ) throws IgniteCheckedException {
+ return PageHandler.readPage(pageMem, grpId, pageId, lockLsnr,
+ h, arg, intArg, lockFailed);
}
/**
@@ -329,8 +343,10 @@ public abstract class DataStructure implements PageLockListener {
PageHandler<X, R> h,
X arg,
int intArg,
- R lockFailed) throws IgniteCheckedException {
- return PageHandler.readPage(pageMem, grpId, pageId, page, this, h, arg, intArg, lockFailed);
+ R lockFailed
+ ) throws IgniteCheckedException {
+ return PageHandler.readPage(pageMem, grpId, pageId, page, lockLsnr, h,
+ arg, intArg, lockFailed);
}
/**
@@ -339,7 +355,7 @@ public abstract class DataStructure implements PageLockListener {
* @throws IgniteCheckedException if failed.
*/
protected final void init(long pageId, PageIO init) throws IgniteCheckedException {
- PageHandler.initPage(pageMem, grpId, pageId, init, wal, this);
+ PageHandler.initPage(pageMem, grpId, pageId, init, wal, lockLsnr);
}
/**
@@ -392,27 +408,30 @@ public abstract class DataStructure implements PageLockListener {
return pageMem.realPageSize(grpId);
}
- @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
- // No-op.
- }
+ /** No-op page lock listener. */
+ public static final PageLockListener NOOP_LSNR = new PageLockListener() {
+ @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+ // No-op.
+ }
- @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
- // No-op.
- }
+ @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+ // No-op.
+ }
- @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
- // No-op.
- }
+ @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ // No-op.
+ }
- @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
- // No-op.
- }
+ @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+ // No-op.
+ }
- @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
- // No-op.
- }
+ @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+ // No-op.
+ }
- @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
- // No-op.
- }
+ @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ // No-op.
+ }
+ };
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
index 23650ef..4dd24ba 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageUpdatePartitionDataRecord;
import org.apache.ignite.internal.pagemem.wal.record.delta.PartitionDestroyRecord;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
import org.apache.ignite.internal.processors.cache.CacheEntryPredicate;
import org.apache.ignite.internal.processors.cache.CacheGroupContext;
import org.apache.ignite.internal.processors.cache.CacheObject;
@@ -64,7 +65,7 @@ import org.apache.ignite.internal.processors.cache.distributed.dht.topology.Grid
import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
import org.apache.ignite.internal.processors.cache.persistence.migration.UpgradePendingTreeToPerPartitionTask;
import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryEx;
import org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId;
@@ -126,18 +127,27 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
Metas metas = getOrAllocateCacheMetas();
+ CacheDiagnosticManager diagnosticMgr = ctx.diagnostic();
+
+ String reuseListName = grp.cacheOrGroupName() + "##ReuseList";
+ String indexStorageTreeName = grp.cacheOrGroupName() + "##IndexStorageTree";
+
RootPage reuseListRoot = metas.reuseListRoot;
- reuseList = new ReuseListImpl(grp.groupId(),
+ reuseList = new ReuseListImpl(
+ grp.groupId(),
grp.cacheOrGroupName(),
grp.dataRegion().pageMemory(),
ctx.wal(),
reuseListRoot.pageId().pageId(),
- reuseListRoot.isAllocated());
+ reuseListRoot.isAllocated(),
+ diagnosticMgr.pageLockTracker().createPageLockTracker(reuseListName)
+ );
RootPage metastoreRoot = metas.treeRoot;
- indexStorage = new IndexStorageImpl(grp.dataRegion().pageMemory(),
+ indexStorage = new IndexStorageImpl(
+ grp.dataRegion().pageMemory(),
ctx.wal(),
globalRemoveId(),
grp.groupId(),
@@ -147,7 +157,9 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
reuseList,
metastoreRoot.pageId().pageId(),
metastoreRoot.isAllocated(),
- ctx.kernalContext().failure());
+ ctx.kernalContext().failure(),
+ diagnosticMgr.pageLockTracker().createPageLockTracker(indexStorageTreeName)
+ );
((GridCacheDatabaseSharedManager)ctx.database()).addCheckpointListener(this);
}
@@ -264,7 +276,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
RowStore rowStore0 = store.rowStore();
if (rowStore0 != null) {
- ((CacheFreeListImpl)rowStore0.freeList()).saveMetadata();
+ ((CacheFreeList)rowStore0.freeList()).saveMetadata();
long updCntr = store.updateCounter();
long size = store.fullSize();
@@ -1023,7 +1035,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
for (CacheDataStore store : partDataStores.values()) {
assert store instanceof GridCacheDataStore;
- CacheFreeListImpl freeList = ((GridCacheDataStore)store).freeList;
+ CacheFreeList freeList = ((GridCacheDataStore)store).freeList;
if (freeList == null)
continue;
@@ -1045,7 +1057,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
for (CacheDataStore store : partDataStores.values()) {
assert store instanceof GridCacheDataStore;
- CacheFreeListImpl freeList = ((GridCacheDataStore)store).freeList;
+ CacheFreeList freeList = ((GridCacheDataStore)store).freeList;
if (freeList == null)
continue;
@@ -1463,10 +1475,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
private final int partId;
/** */
- private String name;
-
- /** */
- private volatile CacheFreeListImpl freeList;
+ private volatile CacheFreeList freeList;
/** */
private PendingEntriesTree pendingTree;
@@ -1502,8 +1511,34 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
private GridCacheDataStore(int partId, boolean exists) {
this.partId = partId;
this.exists = exists;
+ }
- name = treeName(partId);
+ /**
+ * @return Name of free pages list.
+ */
+ private String freeListName() {
+ return grp.cacheOrGroupName() + "-" + partId;
+ }
+
+ /**
+ * @return Name of partition meta store.
+ */
+ private String partitionMetaStoreName() {
+ return grp.cacheOrGroupName() + "-partstore-" + partId;
+ }
+
+ /**
+ * @return Name of data tree.
+ */
+ private String dataTreeName() {
+ return grp.cacheOrGroupName() + "-" + treeName(partId);
+ }
+
+ /**
+ * @return Name of pending entires tree.
+ */
+ private String pendingEntriesTreeName() {
+ return grp.cacheOrGroupName() + "-" +"PendingEntries-" + partId;
}
/**
@@ -1538,17 +1573,21 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
", metas=" + metas + ']');
}
+ String freeListName = freeListName();
+
RootPage reuseRoot = metas.reuseListRoot;
- freeList = new CacheFreeListImpl(
+ freeList = new CacheFreeList(
grp.groupId(),
- grp.cacheOrGroupName() + "-" + partId,
+ freeListName,
grp.dataRegion().memoryMetrics(),
grp.dataRegion(),
null,
ctx.wal(),
reuseRoot.pageId().pageId(),
- reuseRoot.isAllocated()) {
+ reuseRoot.isAllocated(),
+ ctx.diagnostic().pageLockTracker().createPageLockTracker(freeListName)
+ ) {
/** {@inheritDoc} */
@Override protected long allocatePageNoReuse() throws IgniteCheckedException {
assert grp.shared().database().checkpointLockIsHeldByThread();
@@ -1556,6 +1595,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
return pageMem.allocatePage(grpId, partId, PageIdAllocator.FLAG_DATA);
}
};
+ String dataTreeName = dataTreeName();
CacheDataRowStore rowStore = new CacheDataRowStore(grp, freeList, partId);
@@ -1563,11 +1603,13 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
CacheDataTree dataTree = new CacheDataTree(
grp,
- name,
+ dataTreeName,
freeList,
rowStore,
treeRoot.pageId().pageId(),
- treeRoot.isAllocated()) {
+ treeRoot.isAllocated(),
+ ctx.diagnostic().pageLockTracker().createPageLockTracker(dataTreeName)
+ ) {
/** {@inheritDoc} */
@Override protected long allocatePageNoReuse() throws IgniteCheckedException {
assert grp.shared().database().checkpointLockIsHeldByThread();
@@ -1576,15 +1618,19 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
}
};
+ String pendingEntriesTreeName = pendingEntriesTreeName();
+
RootPage pendingTreeRoot = metas.pendingTreeRoot;
final PendingEntriesTree pendingTree0 = new PendingEntriesTree(
grp,
- "PendingEntries-" + partId,
+ pendingEntriesTreeName,
grp.dataRegion().pageMemory(),
pendingTreeRoot.pageId().pageId(),
freeList,
- pendingTreeRoot.isAllocated()) {
+ pendingTreeRoot.isAllocated(),
+ ctx.diagnostic().pageLockTracker().createPageLockTracker(pendingEntriesTreeName)
+ ) {
/** {@inheritDoc} */
@Override protected long allocatePageNoReuse() throws IgniteCheckedException {
assert grp.shared().database().checkpointLockIsHeldByThread();
@@ -1595,7 +1641,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
PageMemoryEx pageMem = (PageMemoryEx)grp.dataRegion().pageMemory();
- delegate0 = new CacheDataStoreImpl(partId, name, rowStore, dataTree) {
+ delegate0 = new CacheDataStoreImpl(partId, rowStore, dataTree) {
/** {@inheritDoc} */
@Override public PendingEntriesTree pendingTree() {
return pendingTree0;
@@ -1802,11 +1848,6 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
}
/** {@inheritDoc} */
- @Override public String name() {
- return name;
- }
-
- /** {@inheritDoc} */
@Override public RowStore rowStore() {
CacheDataStore delegate0 = delegate;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
index 704255e..1efe385 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
@@ -58,11 +58,12 @@ import org.apache.ignite.internal.processors.cache.persistence.evict.PageEvictio
import org.apache.ignite.internal.processors.cache.persistence.evict.Random2LruPageEvictionTracker;
import org.apache.ignite.internal.processors.cache.persistence.evict.RandomLruPageEvictionTracker;
import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFolderSettings;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetastorageLifecycleListener;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -118,10 +119,10 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
protected DataRegion dfltDataRegion;
/** */
- protected Map<String, CacheFreeListImpl> freeListMap;
+ protected Map<String, CacheFreeList> freeListMap;
/** */
- private CacheFreeListImpl dfltFreeList;
+ private CacheFreeList dfltFreeList;
/** Page size from memory configuration, may be set only for fake(standalone) IgniteCacheDataBaseSharedManager */
private int pageSize;
@@ -244,18 +245,25 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
for (DataRegion memPlc : dataRegionMap.values()) {
DataRegionConfiguration memPlcCfg = memPlc.config();
- DataRegionMetricsImpl memMetrics = (DataRegionMetricsImpl) memMetricsMap.get(memPlcCfg.getName());
+ DataRegionMetricsImpl memMetrics = (DataRegionMetricsImpl)memMetricsMap.get(memPlcCfg.getName());
boolean persistenceEnabled = memPlcCfg.isPersistenceEnabled();
- CacheFreeListImpl freeList = new CacheFreeListImpl(0,
- cctx.igniteInstanceName(),
- memMetrics,
- memPlc,
- null,
- persistenceEnabled ? cctx.wal() : null,
- 0L,
- true);
+ String freeListName = memPlcCfg.getName() + "##FreeList";
+
+ PageLockListener lsnr = cctx.diagnostic().pageLockTracker().createPageLockTracker(freeListName);
+
+ CacheFreeList freeList = new CacheFreeList(
+ 0,
+ freeListName,
+ memMetrics,
+ memPlc,
+ null,
+ persistenceEnabled ? cctx.wal() : null,
+ 0L,
+ true,
+ lsnr
+ );
freeListMap.put(memPlcCfg.getName(), freeList);
}
@@ -382,11 +390,11 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
final String dataRegName = dataRegCfg.getName();
return new IgniteOutClosure<Long>() {
- private CacheFreeListImpl freeList;
+ private CacheFreeList freeList;
@Override public Long apply() {
if (freeList == null) {
- CacheFreeListImpl freeList0 = freeListMap.get(dataRegName);
+ CacheFreeList freeList0 = freeListMap.get(dataRegName);
if (freeList0 == null)
return 0L;
@@ -409,9 +417,9 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
final String dataRegName = dataRegCfg.getName();
return new DataRegionMetricsProvider() {
- private CacheFreeListImpl freeList;
+ private CacheFreeList freeList;
- private CacheFreeListImpl getFreeList() {
+ private CacheFreeList getFreeList() {
if (freeList == null)
freeList = freeListMap.get(dataRegName);
@@ -419,13 +427,13 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
}
@Override public long partiallyFilledPagesFreeSpace() {
- CacheFreeListImpl freeList0 = getFreeList();
+ CacheFreeList freeList0 = getFreeList();
return freeList0 == null ? 0L : freeList0.freeSpace();
}
@Override public long emptyDataPages() {
- CacheFreeListImpl freeList0 = getFreeList();
+ CacheFreeList freeList0 = getFreeList();
return freeList0 == null ? 0L : freeList0.emptyDataPages();
}
@@ -698,7 +706,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
*/
public void dumpStatistics(IgniteLogger log) {
if (freeListMap != null) {
- for (CacheFreeListImpl freeList : freeListMap.values())
+ for (CacheFreeList freeList : freeListMap.values())
freeList.dumpStatistics(log);
}
}
@@ -991,12 +999,12 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
int sysPageSize = pageMem.systemPageSize();
- CacheFreeListImpl freeListImpl = freeListMap.get(plcCfg.getName());
+ CacheFreeList freeList = freeListMap.get(plcCfg.getName());
for (;;) {
long allocatedPagesCnt = pageMem.loadedPages();
- int emptyDataPagesCnt = freeListImpl.emptyDataPages();
+ int emptyDataPagesCnt = freeList.emptyDataPages();
boolean shouldEvict = allocatedPagesCnt > (memorySize / sysPageSize * plcCfg.getEvictionThreshold()) &&
emptyDataPagesCnt < plcCfg.getEmptyPagesPoolSize();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java
index 4a27140..9415a01 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeaf
import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
import org.apache.ignite.internal.util.lang.GridCursor;
import org.apache.ignite.internal.util.typedef.internal.U;
@@ -87,7 +88,8 @@ public class IndexStorageImpl implements IndexStorage {
final ReuseList reuseList,
final long rootPageId,
final boolean initNew,
- final FailureProcessor failureProcessor
+ final FailureProcessor failureProcessor,
+ final PageLockListener lockLsnr
) {
try {
this.pageMem = pageMem;
@@ -97,8 +99,21 @@ public class IndexStorageImpl implements IndexStorage {
this.allocSpace = allocSpace;
this.reuseList = reuseList;
- metaTree = new MetaTree(grpId, allocPartId, allocSpace, pageMem, wal, globalRmvId, rootPageId,
- reuseList, MetaStoreInnerIO.VERSIONS, MetaStoreLeafIO.VERSIONS, initNew, failureProcessor);
+ metaTree = new MetaTree(
+ grpId,
+ allocPartId,
+ allocSpace,
+ pageMem,
+ wal,
+ globalRmvId,
+ rootPageId,
+ reuseList,
+ MetaStoreInnerIO.VERSIONS,
+ MetaStoreLeafIO.VERSIONS,
+ initNew,
+ failureProcessor,
+ lockLsnr
+ );
}
catch (IgniteCheckedException e) {
throw new IgniteException(e);
@@ -237,9 +252,22 @@ public class IndexStorageImpl implements IndexStorage {
final IOVersions<? extends BPlusInnerIO<IndexItem>> innerIos,
final IOVersions<? extends BPlusLeafIO<IndexItem>> leafIos,
final boolean initNew,
- @Nullable FailureProcessor failureProcessor
+ @Nullable FailureProcessor failureProcessor,
+ @Nullable PageLockListener lockLsnr
) throws IgniteCheckedException {
- super(treeName("meta", "Meta"), cacheId, pageMem, wal, globalRmvId, metaPageId, reuseList, innerIos, leafIos, failureProcessor);
+ super(
+ treeName("meta", "Meta"),
+ cacheId,
+ pageMem,
+ wal,
+ globalRmvId,
+ metaPageId,
+ reuseList,
+ innerIos,
+ leafIos,
+ failureProcessor,
+ lockLsnr
+ );
this.allocPartId = allocPartId;
this.allocSpace = allocSpace;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpProcessor.java
new file mode 100644
index 0000000..8600d34
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpProcessor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.PageLockStackSnapshot;
+
+/**
+ * Dump processor.
+ */
+public interface DumpProcessor {
+ /**
+ * @param snapshot Process lock log snapshot.
+ */
+ void processDump(PageLockLogSnapshot snapshot);
+
+ /**
+ * @param snapshot Process lock stack snapshot.
+ */
+ void processDump(PageLockStackSnapshot snapshot);
+
+ /**
+ * @param snapshot Process lock thread dump snapshot.
+ */
+ void processDump(ThreadPageLocksDumpLock snapshot);
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpSupported.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpSupported.java
new file mode 100644
index 0000000..814c2c3
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpSupported.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.lang.IgniteFuture;
+
+/**
+ * Interface for all page lock tracker entries which support dumping.
+ */
+public interface DumpSupported<T extends PageLockDump> {
+ /** */
+ boolean acquireSafePoint();
+
+ /** */
+ boolean releaseSafePoint();
+
+ /**
+ * Create dump.
+ */
+ T dump();
+
+ /**
+ * Create dump async.
+ *
+ * @return Ignite future.
+ */
+ IgniteFuture<T> dumpSync();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/InvalidContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/InvalidContext.java
new file mode 100644
index 0000000..b27ca40
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/InvalidContext.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+/**
+ * Failure context if any invariant is not satisfied.
+ */
+public class InvalidContext<T extends PageLockDump> {
+ /** */
+ public final String msg;
+ /** */
+ public final T dump;
+
+ /** */
+ public InvalidContext(String msg, T dump) {
+ this.msg = msg;
+ this.dump = dump;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "Error: " + msg + "\n" + dump.toString();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/LockTrackerFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/LockTrackerFactory.java
new file mode 100644
index 0000000..ffcc5a4
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/LockTrackerFactory.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.LockLog;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.LockStack;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.store.HeapPageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.store.OffHeapPageMetaInfoStore;
+
+import static java.lang.String.valueOf;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_CAPACITY;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_TYPE;
+import static org.apache.ignite.IgniteSystemProperties.getInteger;
+
+/**
+ * Page lock tracker factory.
+ *
+ * 1 - HEAP_STACK
+ * 2 - HEAP_LOG
+ * 3 - OFF_HEAP_STACK
+ * 4 - OFF_HEAP_LOG
+ */
+public final class LockTrackerFactory {
+ /**
+ *
+ */
+ public static final int HEAP_STACK = 1;
+ /**
+ *
+ */
+ public static final int HEAP_LOG = 2;
+ /**
+ *
+ */
+ public static final int OFF_HEAP_STACK = 3;
+ /**
+ *
+ */
+ public static final int OFF_HEAP_LOG = 4;
+
+ /**
+ *
+ */
+ public static volatile int DEFAULT_CAPACITY = getInteger(IGNITE_PAGE_LOCK_TRACKER_CAPACITY, 512);
+ /**
+ *
+ */
+ public static volatile int DEFAULT_TYPE = getInteger(IGNITE_PAGE_LOCK_TRACKER_TYPE, HEAP_LOG);
+
+ /**
+ * @param name Page lock tracker name.
+ */
+ public static PageLockTracker<? extends PageLockDump> create(String name) {
+ return create(DEFAULT_TYPE, name);
+ }
+
+ /**
+ * @param name Page lock tracker name.
+ * @param type Page lock tracker type.
+ */
+ public static PageLockTracker<? extends PageLockDump> create(int type, String name) {
+ return create(type, name, DEFAULT_CAPACITY);
+ }
+
+ /**
+ * @param name Page lock tracker name.
+ * @param type Page lock tracker type.
+ * @param size Page lock tracker size (capacity).
+ */
+ public static PageLockTracker<? extends PageLockDump> create(int type, String name, int size) {
+ return create(type, size, name, new MemoryCalculator());
+ }
+
+ /**
+ * @param name Page lock tracker name.
+ * @param type Page lock tracker type.
+ * @param size Page lock tracker size (capacity).
+ */
+ public static PageLockTracker<? extends PageLockDump> create(
+ int type,
+ int size,
+ String name,
+ MemoryCalculator memCalc
+ ) {
+ switch (type) {
+ case HEAP_STACK:
+ return new LockStack(name, new HeapPageMetaInfoStore(size, memCalc), memCalc);
+ case HEAP_LOG:
+ return new LockLog(name, new HeapPageMetaInfoStore(size, memCalc), memCalc);
+ case OFF_HEAP_STACK:
+ return new LockStack(name, new OffHeapPageMetaInfoStore(size, memCalc), memCalc);
+ case OFF_HEAP_LOG:
+ return new LockLog(name, new OffHeapPageMetaInfoStore(size, memCalc), memCalc);
+
+ default:
+ throw new IllegalArgumentException(valueOf(type));
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockDump.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockDump.java
new file mode 100644
index 0000000..e5f2856
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockDump.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+/**
+ * Interface for page lock tracker structures dump.
+ */
+public interface PageLockDump {
+ /**
+ * @param dumpProcessort Apply dump processor.
+ */
+ void apply(DumpProcessor dumpProcessort);
+
+ /**
+ * @return Dump creation time.
+ */
+ long time();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockListenerIndexAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockListenerIndexAdapter.java
new file mode 100644
index 0000000..5f0d4d0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockListenerIndexAdapter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+
+/**
+ * Page lock listener adapter with Id.
+ */
+public class PageLockListenerIndexAdapter implements PageLockListener {
+ /** Adapter id. */
+ private final int id;
+
+ /** Real listener. */
+ private final PageLockListener delegate;
+
+ /**
+ * @param id Adapter id.
+ * @param delegate Real listener.
+ */
+ public PageLockListenerIndexAdapter(int id, PageLockListener delegate) {
+ this.id = id;
+ this.delegate = delegate;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+ delegate.onBeforeWriteLock(id, pageId, page);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onWriteLock(id, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onWriteUnlock(id, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+ delegate.onBeforeReadLock(id, pageId, page);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onReadLock(id, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ delegate.onReadUnlock(id, pageId, page, pageAddr);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTracker.java
new file mode 100644
index 0000000..8517340
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTracker.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.lang.IgniteFuture;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.flag;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageIndex;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.partId;
+import static org.apache.ignite.internal.util.IgniteUtils.hexInt;
+import static org.apache.ignite.internal.util.IgniteUtils.hexLong;
+
+/**
+ * Abstract page lock tracker.
+ */
+public abstract class PageLockTracker<T extends PageLockDump> implements PageLockListener, DumpSupported<T> {
+ /** */
+ private static final long OVERHEAD_SIZE = 16 + 8 + 8 + 4 + 4 + 4 + 8 + 8 + 4 + 4 + 8;
+ /** */
+ public static final int OP_OFFSET = 16;
+ /** */
+ public static final int LOCK_IDX_MASK = 0xFFFF0000;
+ /** */
+ public static final int LOCK_OP_MASK = 0x000000000000FF;
+
+ /** Page read lock operation id. */
+ public static final int READ_LOCK = 1;
+ /** Page read unlock operation id. */
+ public static final int READ_UNLOCK = 2;
+ /** Page write lock operation id. */
+ public static final int WRITE_LOCK = 3;
+ /** Page write unlock operation id. */
+ public static final int WRITE_UNLOCK = 4;
+ /** Page read before lock operation id. */
+ public static final int BEFORE_READ_LOCK = 5;
+ /** Page write before lock operation id. */
+ public static final int BEFORE_WRITE_LOCK = 6;
+
+ /** */
+ protected final String name;
+ /** */
+ protected final PageMetaInfoStore pages;
+ /** Counter for track lock/unlock operations. */
+ protected int heldLockCnt;
+ /** */
+ protected int nextOp;
+ /** */
+ protected int nextOpStructureId;
+ /** */
+ protected long nextOpPageId;
+ /** */
+ private long opCntr;
+ /** */
+ private volatile boolean dump;
+ /** */
+ private volatile boolean locked;
+ /** */
+ private volatile InvalidContext<T> invalidCtx;
+
+ /**
+ *
+ */
+ protected PageLockTracker(String name, PageMetaInfoStore pages, MemoryCalculator memCalc) {
+ this.name = name;
+ this.pages = pages;
+
+ memCalc.onHeapAllocated(OVERHEAD_SIZE);
+ }
+
+ /** */
+ public void onBeforeWriteLock0(int structureId, long pageId, long page) {
+ this.nextOp = BEFORE_WRITE_LOCK;
+ this.nextOpStructureId = structureId;
+ this.nextOpPageId = pageId;
+ }
+
+ /** */
+ public void onBeforeReadLock0(int structureId, long pageId, long page) {
+ this.nextOp = BEFORE_READ_LOCK;
+ this.nextOpStructureId = structureId;
+ this.nextOpPageId = pageId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeWriteLock(int structureId, long pageId, long page) {
+ if (isInvalid())
+ return;
+
+ lock();
+
+ try {
+ onBeforeWriteLock0(structureId, pageId, page);
+ }
+ finally {
+ unLock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock(int structureId, long pageId, long page, long pageAddr) {
+ if (checkFailedLock(pageAddr) || isInvalid())
+ return;
+
+ lock();
+
+ try {
+ onWriteLock0(structureId, pageId, page, pageAddr);
+ }
+ finally {
+ unLock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock(int structureId, long pageId, long page, long pageAddr) {
+ if (isInvalid())
+ return;
+
+ lock();
+
+ try {
+ onWriteUnlock0(structureId, pageId, page, pageAddr);
+ }
+ finally {
+ unLock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeReadLock(int structureId, long pageId, long page) {
+ if (isInvalid())
+ return;
+
+ lock();
+
+ try {
+ onBeforeReadLock0(structureId, pageId, page);
+ }
+ finally {
+ unLock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock(int structureId, long pageId, long page, long pageAddr) {
+ if (checkFailedLock(pageAddr) ||isInvalid())
+ return;
+
+ lock();
+
+ try {
+ onReadLock0(structureId, pageId, page, pageAddr);
+ }
+ finally {
+ unLock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock(int structureId, long pageId, long page, long pageAddr) {
+ if (isInvalid())
+ return;
+
+ lock();
+
+ try {
+ onReadUnlock0(structureId, pageId, page, pageAddr);
+ }
+ finally {
+ unLock();
+ }
+ }
+
+ /** */
+ public abstract void onWriteLock0(int structureId, long pageId, long page, long pageAddr);
+
+ /** */
+ public abstract void onWriteUnlock0(int structureId, long pageId, long page, long pageAddr);
+
+ /** */
+ public abstract void onReadLock0(int structureId, long pageId, long page, long pageAddr);
+
+ /** */
+ public abstract void onReadUnlock0(int structureId, long pageId, long page, long pageAddr);
+
+ /** */
+ public boolean isInvalid() {
+ return invalidCtx != null;
+ }
+
+ /** */
+ private boolean checkFailedLock(long pageAddr){
+ if (pageAddr == 0) {
+ this.nextOp = 0;
+ this.nextOpStructureId = 0;
+ this.nextOpPageId = 0;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /** */
+ public InvalidContext<T> invalidContext() {
+ return invalidCtx;
+ }
+
+ /** */
+ protected void free(){
+ pages.free();
+ }
+
+ /** */
+ protected void invalid(String msg) {
+ T dump = snapshot();
+
+ invalidCtx = new InvalidContext<>(msg, dump);
+ }
+
+ /** */
+ private void lock() {
+ while (!lock0()) {
+ // Busy wait.
+ }
+ }
+
+ /** */
+ private boolean lock0() {
+ awaitDump();
+
+ locked = true;
+ if (dump) {
+ locked = false;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /** */
+ private void unLock() {
+ opCntr++;
+
+ locked = false;
+ }
+
+ /** */
+ private void awaitDump() {
+ while (dump) {
+ // Busy wait.
+ }
+ }
+
+ /** */
+ private void awaitLocks() {
+ while (locked) {
+ // Busy wait.
+ }
+ }
+
+ /**
+ * @return Number of locks operations.
+ */
+ public long operationsCounter(){
+ // Read volatile for thread safety.
+ boolean locked = this.locked;
+
+ return opCntr;
+ }
+
+ /**
+ *
+ */
+ public int heldLocksNumber() {
+ // Read volatile for thread safety.
+ boolean locked = this.locked;
+
+ return heldLockCnt;
+ }
+
+ /** */
+ protected boolean validateOperation(int structureId, long pageId, int op) {
+ if (nextOpStructureId == 0 || nextOp == 0 || nextOpPageId == 0)
+ return true;
+
+ if ((op == READ_LOCK && nextOp != BEFORE_READ_LOCK) ||
+ (op == WRITE_LOCK && nextOp != BEFORE_WRITE_LOCK) ||
+ (structureId != nextOpStructureId) ||
+ (pageId != nextOpPageId)) {
+
+ invalid("Unepected operation: " +
+ "exp=" + argsToString(nextOpStructureId, nextOpPageId, nextOp) + "," +
+ "actl=" + argsToString(structureId, pageId, op)
+ );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /** */
+ protected abstract T snapshot();
+
+ /** {@inheritDoc} */
+ @Override public synchronized boolean acquireSafePoint() {
+ return dump ? false : (dump = true);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized boolean releaseSafePoint() {
+ return !dump ? false : !(dump = false);
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized T dump() {
+ boolean needRelease = acquireSafePoint();
+
+ awaitLocks();
+
+ T dump0 = snapshot();
+
+ if (needRelease)
+ releaseSafePoint();
+
+ return dump0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteFuture<T> dumpSync() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** */
+ public static String argsToString(int structureId, long pageId, int flags) {
+ return "[structureId=" + structureId + ", pageId" + pageIdToString(pageId) + "]";
+ }
+
+ /** */
+ public static String pageIdToString(long pageId) {
+ return "pageId=" + pageId
+ + " [pageIdHex=" + hexLong(pageId)
+ + ", partId=" + partId(pageId) + ", pageIdx=" + pageIndex(pageId)
+ + ", flags=" + hexInt(flag(pageId)) + "]";
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBean.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBean.java
new file mode 100644
index 0000000..d9ed7d6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBean.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.mxbean.MXBeanDescription;
+
+/**
+ * This interface defines JMX managment interface for page lock tracking.
+ */
+@MXBeanDescription("MBean that provides access to page lock tracking.")
+public interface PageLockTrackerMXBean {
+ /** */
+ public static final String MBEAN_NAME = "PageLockTracker";
+ /**
+ * Take page locks dump.
+ *
+ * @return String representation of page locks dump.
+ */
+ @MXBeanDescription("Take page locks dump.")
+ String dumpLocks();
+
+ /**
+ * Take page locks dump and print it to console.
+ */
+ @MXBeanDescription("Take page locks dump and print it to console.")
+ void dumpLocksToLog();
+
+ /**
+ * Take page locks dump and save to file.
+ *
+ * @return Absolute file path.
+ */
+ @MXBeanDescription("Take page locks dump and save to file.")
+ String dumpLocksToFile();
+
+ /**
+ * Take page locks dump and save to file for specific path.
+ *
+ * @param path Path to save file.
+ * @return Absolute file path.
+ */
+ @MXBeanDescription("Take page locks dump and save to file for specific path.")
+ String dumpLocksToFile(String path);
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImpl.java
new file mode 100644
index 0000000..db91457
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+
+/**
+ * Implementation of {@link PageLockTrackerMXBean}.
+ */
+public class PageLockTrackerMXBeanImpl implements PageLockTrackerMXBean {
+ /** */
+ private static final long OVERHEAD_SIZE = 16 + 8;
+
+ /** Page lock tracker manager */
+ private final PageLockTrackerManager mgr;
+
+ /**
+ * @param mgr Page lock tracker manager.
+ */
+ public PageLockTrackerMXBeanImpl(PageLockTrackerManager mgr, MemoryCalculator memoryCalculator) {
+ this.mgr = mgr;
+
+ memoryCalculator.onHeapAllocated(OVERHEAD_SIZE);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String dumpLocks() {
+ return mgr.dumpLocks();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void dumpLocksToLog() {
+ mgr.dumpLocksToLog();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String dumpLocksToFile() {
+ return mgr.dumpLocksToFile();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String dumpLocksToFile(String path) {
+ return mgr.dumpLocksToFile(path);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManager.java
new file mode 100644
index 0000000..c48d38c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManager.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.io.File;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.processors.cache.persistence.DataStructure;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.SharedPageLockTracker.State;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lifecycle.LifecycleAware;
+import org.jetbrains.annotations.NotNull;
+
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_TYPE;
+import static org.apache.ignite.IgniteSystemProperties.getInteger;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+
+/**
+ * Page lock manager.
+ */
+public class PageLockTrackerManager implements LifecycleAware {
+ /** */
+ private static final long OVERHEAD_SIZE = 16 + 8 + 8 + 8 + 8;
+
+ /** */
+ private final MemoryCalculator memoryCalculator = new MemoryCalculator();
+
+ /** MXbean */
+ private final PageLockTrackerMXBean mxBean;
+
+ /** */
+ private final SharedPageLockTracker sharedPageLockTracker;
+
+ /** */
+ private final IgniteLogger log;
+
+ /** */
+ private Set<State> threads;
+
+ /** */
+ private final String managerNameId;
+
+ /** */
+ private final boolean trackingEnable;
+
+
+ /**
+ * Default constructor.
+ */
+ public PageLockTrackerManager(IgniteLogger log) {
+ this(log, "mgr_" + UUID.randomUUID().toString());
+ }
+
+ /**
+ * Default constructor.
+ */
+ public PageLockTrackerManager(IgniteLogger log, String managerNameId) {
+ this.trackingEnable = !(getInteger(IGNITE_PAGE_LOCK_TRACKER_TYPE, HEAP_LOG) == -1);
+ this.managerNameId = managerNameId;
+ this.mxBean = new PageLockTrackerMXBeanImpl(this, memoryCalculator);
+ this.sharedPageLockTracker = new SharedPageLockTracker(this::onHangThreads, memoryCalculator);
+ this.log = log;
+
+ memoryCalculator.onHeapAllocated(OVERHEAD_SIZE);
+ }
+
+ /**
+ * @param threads Hang threads.
+ */
+ private void onHangThreads(@NotNull Set<State> threads) {
+ assert threads != null;
+
+ // Processe only one for same list thread state.
+ // Protection of spam.
+ if (!threads.equals(this.threads)) {
+ this.threads = threads;
+
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ StringBuilder sb = new StringBuilder();
+
+ threads.forEach(s -> {
+ Thread th = s.thread;
+ sb.append("(")
+ .append(th.getName())
+ .append("-")
+ .append(th.getId())
+ .append(", ")
+ .append(th.getState())
+ .append(")");
+
+ });
+
+ log.warning("Threads hanged: [" + sb + "]");
+ // If some thread is hangs
+ // Print to log.
+ log.warning(ToStringDumpProcessor.toStringDump(dump));
+
+ try {
+ // Write dump to file.
+ ToFileDumpProcessor.toFileDump(dump, new File(U.defaultWorkDirectory()), managerNameId);
+ }
+ catch (IgniteCheckedException e) {
+ log.warning("Faile to save locks dump file.", e);
+ }
+ }
+ }
+
+ /**
+ * @param name Lock tracker name.
+ * @return Instance of {@link PageLockListener} for tracking lock/unlock operations.
+ */
+ public PageLockListener createPageLockTracker(String name) {
+ if (!trackingEnable)
+ return DataStructure.NOOP_LSNR;
+
+ return sharedPageLockTracker.registrateStructure(name);
+ }
+
+ /**
+ * Take page locks dump.
+ *
+ * @return String representation of page locks dump.
+ */
+ public String dumpLocks() {
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ return ToStringDumpProcessor.toStringDump(dump);
+ }
+
+ /**
+ * Take page locks dump and print it to console.
+ */
+ public void dumpLocksToLog() {
+ log.warning(dumpLocks());
+ }
+
+ /**
+ * Take page locks dump and save to file.
+ *
+ * @return Absolute file path.
+ */
+ public String dumpLocksToFile() {
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ try {
+ return ToFileDumpProcessor.toFileDump(dump, new File(U.defaultWorkDirectory()), managerNameId);
+ }
+ catch (IgniteCheckedException e) {
+ throw U.convertException(e);
+ }
+ }
+
+ /**
+ * Take page locks dump and save to file for specific path.
+ *
+ * @param path Path to save file.
+ * @return Absolute file path.
+ */
+ public String dumpLocksToFile(String path) {
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ try {
+ return ToFileDumpProcessor.toFileDump(dump, new File(path), managerNameId);
+ }
+ catch (IgniteCheckedException e) {
+ throw U.convertException(e);
+ }
+ }
+
+ /**
+ * Getter.
+ *
+ * @return PageLockTrackerMXBean object.
+ */
+ public PageLockTrackerMXBean mxBean() {
+ return mxBean;
+ }
+
+ /**
+ * @return Total heap overhead in bytes.
+ */
+ public long getHeapOverhead() {
+ return memoryCalculator.heapUsed.get();
+ }
+
+ /**
+ * @return Total offheap overhead in bytes.
+ */
+ public long getOffHeapOverhead() {
+ return memoryCalculator.offHeapUsed.get();
+ }
+
+ /**
+ * @return Total overhead in bytes.
+ */
+ public long getTotalOverhead() {
+ return getHeapOverhead() + getOffHeapOverhead();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void start() throws IgniteException {
+ sharedPageLockTracker.start();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void stop() throws IgniteException {
+ sharedPageLockTracker.stop();
+ }
+
+ /**
+ *
+ */
+ public static class MemoryCalculator {
+ /** */
+ private final AtomicLong heapUsed = new AtomicLong();
+ /** */
+ private final AtomicLong offHeapUsed = new AtomicLong();
+
+ /** */
+ MemoryCalculator(){
+ onHeapAllocated(16 + (8 + 16) * 2);
+ }
+
+ /** */
+ public void onHeapAllocated(long bytes) {
+ assert bytes >= 0;
+
+ heapUsed.getAndAdd(bytes);
+ }
+
+ /** */
+ public void onOffHeapAllocated(long bytes) {
+ assert bytes >= 0;
+
+ offHeapUsed.getAndAdd(bytes);
+ }
+
+ /** */
+ public void onHeapFree(long bytes) {
+ heapUsed.getAndAdd(-bytes);
+ }
+
+ /** */
+ public void onOffHeapFree(long bytes) {
+ offHeapUsed.getAndAdd(-bytes);
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageMetaInfoStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageMetaInfoStore.java
new file mode 100644
index 0000000..4272f94
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageMetaInfoStore.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+/**
+ *
+ */
+public interface PageMetaInfoStore {
+ /**
+ * @return Capacity.
+ */
+ int capacity();
+
+ /**
+ * @return True if empty.
+ */
+ boolean isEmpty();
+
+ /**
+ * Add page to store.
+ *
+ * @param itemIdx Index of page in store.
+ * @param op Page operation.
+ * @param structureId Data structure id.
+ * @param pageId Page id.
+ * @param pageAddrHeader Page header addres.
+ * @param pageAddr Page addres.
+ */
+ void add(int itemIdx, int op, int structureId, long pageId, long pageAddrHeader, long pageAddr);
+
+ /**
+ * Remove page from store by index.
+ */
+ void remove(int itemIdx);
+
+ /**
+ * @param itemIdx Index of page in store.
+ * @return Page operation.
+ */
+ int getOperation(int itemIdx);
+
+ /**
+ * @param itemIdx Index of page in store.
+ * @return Data structure id.
+ */
+ int getStructureId(int itemIdx);
+
+ /**
+ * @param itemIdx Index of page in store.
+ * @return Page id.
+ */
+ long getPageId(int itemIdx);
+
+ /**
+ * @param itemIdx Index of page in store.
+ * @return Page header address.
+ */
+ long getPageAddrHeader(int itemIdx);
+
+ /**
+ * @param itemIdx Index of page in store.
+ * @return Page address.
+ */
+ long getPageAddr(int itemIdx);
+
+ /**
+ * @return Copy of current store state.
+ */
+ PageMetaInfoStore copy();
+
+ /**
+ * Free resource.
+ */
+ void free();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTracker.java
new file mode 100644
index 0000000..dfda39ee
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTracker.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteInterruptedException;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteFuture;
+import org.apache.ignite.lifecycle.LifecycleAware;
+
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL;
+import static org.apache.ignite.IgniteSystemProperties.getInteger;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.DEFAULT_CAPACITY;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.DEFAULT_TYPE;
+
+//TODO Fast local get thread local.
+//TODO Dynamic enable/disable tracing.
+//TODO Collect page content to dump. AG
+/**
+ *
+ */
+public class SharedPageLockTracker implements LifecycleAware, PageLockListener, DumpSupported<ThreadPageLocksDumpLock> {
+ /**
+ *
+ */
+ private static final long OVERHEAD_SIZE = 16 + (8 * 8) + (4 * 3);
+
+ /**
+ *
+ */
+ private final MemoryCalculator memCalc;
+
+ /**
+ *
+ */
+ public final int threadLimits;
+ /**
+ *
+ */
+ public final int timeOutWorkerInterval;
+ /**
+ *
+ */
+ private final Map<Long, PageLockTracker<? extends PageLockDump>> threadStacks = new HashMap<>();
+ /**
+ *
+ */
+ private final Map<Long, Thread> threadIdToThreadRef = new HashMap<>();
+ /**
+ *
+ */
+ private final Map<String, Integer> structureNameToId = new HashMap<>();
+ /** Thread for clean terminated threads from map. */
+ private final TimeOutWorker timeOutWorker = new TimeOutWorker();
+ /**
+ *
+ */
+ private Map<Long, SharedPageLockTracker.State> prevThreadsState = new HashMap<>();
+ /**
+ *
+ */
+ private int idGen;
+ /**
+ *
+ */
+ private final Consumer<Set<SharedPageLockTracker.State>> hangThreadsCallBack;
+ /**
+ *
+ */
+ private final ThreadLocal<PageLockTracker> lockTracker = ThreadLocal.withInitial(this::createTracker);
+
+ /**
+ *
+ */
+ public SharedPageLockTracker() {
+ this((ids) -> {
+ }, new MemoryCalculator());
+ }
+
+ /**
+ *
+ */
+ public SharedPageLockTracker(Consumer<Set<State>> hangThreadsCallBack, MemoryCalculator memCalc) {
+ this(
+ 1000,
+ getInteger(IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL, 60_000),
+ hangThreadsCallBack,
+ memCalc
+ );
+ }
+
+ /**
+ *
+ */
+ public SharedPageLockTracker(
+ int threadLimits,
+ int timeOutWorkerInterval,
+ Consumer<Set<SharedPageLockTracker.State>> hangThreadsCallBack,
+ MemoryCalculator memCalc
+ ) {
+ this.threadLimits = threadLimits;
+ this.timeOutWorkerInterval = timeOutWorkerInterval;
+ this.hangThreadsCallBack = hangThreadsCallBack;
+ this.memCalc = memCalc;
+
+ this.memCalc.onHeapAllocated(OVERHEAD_SIZE);
+ }
+
+ /**
+ * Factory method for creating thread local {@link PageLockTracker}.
+ *
+ * @return PageLockTracer instance.
+ */
+ private PageLockTracker createTracker() {
+ Thread thread = Thread.currentThread();
+
+ String name = "name=" + thread.getName();
+ long threadId = thread.getId();
+
+ PageLockTracker<? extends PageLockDump> tracker = LockTrackerFactory.create(
+ DEFAULT_TYPE, DEFAULT_CAPACITY, name, memCalc
+ );
+
+ synchronized (this) {
+ threadStacks.put(threadId, tracker);
+
+ threadIdToThreadRef.put(threadId, thread);
+
+ memCalc.onHeapAllocated(((8 + 16 + 8) + 8) * 2);
+
+ if (threadIdToThreadRef.size() > threadLimits)
+ cleanTerminatedThreads();
+ }
+
+ return tracker;
+ }
+
+ /**
+ *
+ */
+ public synchronized PageLockListener registrateStructure(String structureName) {
+ Integer id = structureNameToId.get(structureName);
+
+ if (id == null) {
+ structureNameToId.put(structureName, id = (++idGen));
+
+ // Size for new (K,V) pair.
+ memCalc.onHeapAllocated((structureName.getBytes().length + 16) + (8 + 16 + 4));
+ }
+
+ // Size for PageLockListenerIndexAdapter object.
+ memCalc.onHeapAllocated(16 + 4 + 8);
+
+ return new PageLockListenerIndexAdapter(id, this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeWriteLock(int structureId, long pageId, long page) {
+ lockTracker.get().onBeforeWriteLock(structureId, pageId, page);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock(int structureId, long pageId, long page, long pageAddr) {
+ lockTracker.get().onWriteLock(structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock(int structureId, long pageId, long page, long pageAddr) {
+ lockTracker.get().onWriteUnlock(structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeReadLock(int structureId, long pageId, long page) {
+ lockTracker.get().onBeforeReadLock(structureId, pageId, page);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock(int structureId, long pageId, long page, long pageAddr) {
+ lockTracker.get().onReadLock(structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock(int structureId, long pageId, long page, long pageAddr) {
+ lockTracker.get().onReadUnlock(structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public synchronized ThreadPageLocksDumpLock dump() {
+ Collection<PageLockTracker<? extends PageLockDump>> trackers = threadStacks.values();
+ List<ThreadPageLocksDumpLock.ThreadState> threadStates = new ArrayList<>(threadStacks.size());
+
+ for (PageLockTracker tracker : trackers) {
+ boolean acquired = tracker.acquireSafePoint();
+
+ //TODO
+ assert acquired;
+ }
+
+ for (Map.Entry<Long, PageLockTracker<? extends PageLockDump>> entry : threadStacks.entrySet()) {
+ Long threadId = entry.getKey();
+ Thread thread = threadIdToThreadRef.get(threadId);
+
+ PageLockTracker<? extends PageLockDump> tracker = entry.getValue();
+
+ try {
+ PageLockDump pageLockDump = tracker.dump();
+
+ threadStates.add(
+ new ThreadPageLocksDumpLock.ThreadState(
+ threadId,
+ thread.getName(),
+ thread.getState(),
+ pageLockDump,
+ tracker.isInvalid() ? tracker.invalidContext() : null
+ )
+ );
+ }
+ finally {
+ tracker.releaseSafePoint();
+ }
+ }
+
+ Map<Integer, String> idToStructureName0 =
+ Collections.unmodifiableMap(
+ structureNameToId.entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getValue,
+ Map.Entry::getKey
+ ))
+ );
+
+ List<ThreadPageLocksDumpLock.ThreadState> threadStates0 =
+ Collections.unmodifiableList(threadStates);
+
+ // Get first thread dump time or current time is threadStates is empty.
+ long time = !threadStates.isEmpty() ? threadStates.get(0).pageLockDump.time() : System.currentTimeMillis();
+
+ return new ThreadPageLocksDumpLock(time, idToStructureName0, threadStates0);
+ }
+
+ /**
+ *
+ */
+ private synchronized void cleanTerminatedThreads() {
+ Iterator<Map.Entry<Long, Thread>> it = threadIdToThreadRef.entrySet().iterator();
+
+ while (it.hasNext()) {
+ Map.Entry<Long, Thread> entry = it.next();
+
+ long threadId = entry.getKey();
+ Thread thread = entry.getValue();
+
+ if (thread.getState() == Thread.State.TERMINATED) {
+ PageLockTracker tracker = threadStacks.remove(threadId);
+
+ if (tracker != null) {
+ memCalc.onHeapFree((8 + 16 + 8) + 8);
+
+ tracker.free();
+ }
+
+ it.remove();
+
+ memCalc.onHeapFree((8 + 16 + 8) + 8);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ private synchronized Map<Long, State> getThreadOperationState() {
+ return threadStacks.entrySet().stream().collect(Collectors.toMap(
+ Map.Entry::getKey,
+ e -> {
+ PageLockTracker<? extends PageLockDump> lt = e.getValue();
+
+ return new State(lt.operationsCounter(), lt.heldLocksNumber(), threadIdToThreadRef.get(e.getKey()));
+ }
+ ));
+ }
+
+ /**
+ *
+ */
+ private synchronized Set<State> hangThreads() {
+ Set<State> hangsThreads = new HashSet<>();
+
+ Map<Long, SharedPageLockTracker.State> currentThreadsOperationState = getThreadOperationState();
+
+ prevThreadsState.forEach((threadId, prevState) -> {
+ State state = currentThreadsOperationState.get(threadId);
+
+ if (state == null)
+ return;
+
+ boolean threadHoldedLocks = state.heldLockCnt != 0;
+
+ // If thread holds a lock and does not change state it may be hanged.
+ if (prevState.equals(state) && threadHoldedLocks)
+ hangsThreads.add(state);
+ });
+
+ prevThreadsState = currentThreadsOperationState;
+
+ return hangsThreads;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void start() throws IgniteException {
+ timeOutWorker.setDaemon(true);
+
+ timeOutWorker.start();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void stop() throws IgniteException {
+ timeOutWorker.interrupt();
+
+ try {
+ timeOutWorker.join();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+
+ throw new IgniteInterruptedException(e);
+ }
+ }
+
+ /**
+ *
+ */
+ private class TimeOutWorker extends Thread {
+ /**
+ *
+ */
+ @Override public void run() {
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ sleep(timeOutWorkerInterval);
+
+ cleanTerminatedThreads();
+
+ if (hangThreadsCallBack != null) {
+ Set<SharedPageLockTracker.State> threadIds = hangThreads();
+
+ if (!F.isEmpty(threadIds))
+ hangThreadsCallBack.accept(threadIds);
+ }
+ }
+ }
+ catch (InterruptedException e) {
+ // No-op.
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ public static class State {
+ /**
+ *
+ */
+ final long threadOpCnt;
+ /**
+ *
+ */
+ final long heldLockCnt;
+ /**
+ *
+ */
+ final Thread thread;
+
+ /**
+ *
+ */
+ private State(long threadOpCnt, long heldLockCnt, Thread thread) {
+ this.threadOpCnt = threadOpCnt;
+ this.heldLockCnt = heldLockCnt;
+ this.thread = thread;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ State state = (State)o;
+ return threadOpCnt == state.threadOpCnt &&
+ heldLockCnt == state.heldLockCnt &&
+ Objects.equals(thread, state.thread);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(threadOpCnt, heldLockCnt, thread);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteFuture<ThreadPageLocksDumpLock> dumpSync() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean acquireSafePoint() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean releaseSafePoint() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/ThreadPageLocksDumpLock.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/ThreadPageLocksDumpLock.java
new file mode 100644
index 0000000..9228c9e
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/ThreadPageLocksDumpLock.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+
+/**
+ *
+ */
+public class ThreadPageLocksDumpLock implements PageLockDump {
+ /** */
+ public final long time;
+
+ /** */
+ public final Map<Integer, String> structureIdToStrcutureName;
+
+ /** */
+ public final List<ThreadState> threadStates;
+
+ /** */
+ public ThreadPageLocksDumpLock(
+ long time,
+ Map<Integer, String> structureIdToStrcutureName,
+ List<ThreadState> threadStates
+ ) {
+ this.time = time;
+ this.structureIdToStrcutureName = structureIdToStrcutureName;
+ this.threadStates = threadStates;
+ }
+
+ /** */
+ public static class ThreadState {
+ /** */
+ public final long threadId;
+ /** */
+ public final String threadName;
+ /** */
+ public final Thread.State state;
+ /** */
+ public final PageLockDump pageLockDump;
+ /** */
+ public final InvalidContext<? extends PageLockDump> invalidContext;
+
+ /** */
+ public ThreadState(
+ long threadId,
+ String threadName,
+ Thread.State state,
+ PageLockDump pageLockDump,
+ InvalidContext<? extends PageLockDump> invalidContext
+ ) {
+ this.threadId = threadId;
+ this.threadName = threadName;
+ this.state = state;
+ this.pageLockDump = pageLockDump;
+ this.invalidContext = invalidContext;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void apply(DumpProcessor dumpProcessor) {
+ dumpProcessor.processDump(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long time() {
+ return time;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return toStringDump(this);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessor.java
new file mode 100644
index 0000000..60bb590
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessor.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+
+import static java.nio.channels.FileChannel.open;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+/**
+ *
+ */
+public class ToFileDumpProcessor {
+ /** Date format. */
+ public static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS");
+
+ /** File name prefix. */
+ public static final String PREFIX_NAME = "page_lock_dump_";
+
+ /**
+ * @param pageLockDump Dump.
+ * @param dir Directory to save.
+ */
+ public static String toFileDump(PageLockDump pageLockDump, File dir, String name) throws IgniteCheckedException {
+ try {
+ if (!dir.exists())
+ dir.mkdirs();
+
+ File file = new File(dir, PREFIX_NAME + name + "_" + DATE_FMT.format(new Date(pageLockDump.time())));
+
+ return saveToFile(ToStringDumpProcessor.toStringDump(pageLockDump), file);
+ }
+ catch (IOException e) {
+ throw new IgniteCheckedException(e);
+ }
+ }
+
+ /**
+ * @param dump Dump.
+ * @param file File to save.
+ */
+ private static String saveToFile(String dump, File file) throws IOException {
+ assert dump != null;
+ assert file != null;
+ assert !dump.isEmpty();
+
+ try (FileChannel ch = open(file.toPath(), CREATE_NEW, WRITE)) {
+ ByteBuffer buf = ByteBuffer.wrap(dump.getBytes());
+
+ assert buf.position() == 0;
+ assert buf.limit() > 0;
+
+ while (buf.position() != buf.limit())
+ ch.write(buf);
+
+ ch.force(true);
+ }
+
+ return file.getAbsolutePath();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToStringDumpProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToStringDumpProcessor.java
new file mode 100644
index 0000000..51a26dc
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToStringDumpProcessor.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.DumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.ThreadPageLocksDumpLock;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.ThreadPageLocksDumpLock.ThreadState;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.PageLockStackSnapshot;
+import org.apache.ignite.internal.util.typedef.internal.SB;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.flag;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageIndex;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.partId;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_WRITE_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_OP_MASK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_UNLOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.WRITE_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.WRITE_UNLOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.pageIdToString;
+import static org.apache.ignite.internal.util.IgniteUtils.hexInt;
+import static org.apache.ignite.internal.util.IgniteUtils.hexLong;
+
+/**
+ * Proccessor for buils string from {@link PageLockDump}.
+ */
+public class ToStringDumpProcessor {
+ /** Date format. */
+ public static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+ /**
+ * @param pageLockDump Dump.
+ * @return String representation of dump.
+ */
+ public static String toStringDump(PageLockDump pageLockDump) {
+ StringBuilder sb = new StringBuilder();
+
+ ToStringHelper proc = new ToStringHelper(sb, strucutreIdMapFunc(pageLockDump));
+
+ pageLockDump.apply(proc);
+
+ return sb.toString();
+ }
+
+ /** */
+ private static Function<Integer, String> strucutreIdMapFunc(PageLockDump pageLockDump) {
+ if (pageLockDump instanceof ThreadPageLocksDumpLock) {
+ ThreadPageLocksDumpLock dump = (ThreadPageLocksDumpLock)pageLockDump;
+
+ return dump.structureIdToStrcutureName::get;
+ }
+ else
+ return String::valueOf;
+ }
+
+ /** */
+ private static class ToStringHelper implements DumpProcessor {
+ /** */
+ private final Function<Integer, String> strucutreIdMapFunc;
+ /** */
+ private final StringBuilder sb;
+
+ /** */
+ private ToStringHelper(StringBuilder sb, Function<Integer, String> strucutreIdMapFunc) {
+ this.sb = sb;
+ this.strucutreIdMapFunc = strucutreIdMapFunc;
+ }
+
+ /** Helper class for track lock/unlock count. */
+ class LockState {
+ int readlock;
+ int writelock;
+ }
+
+ /** */
+ private String operationToString(int op) {
+ switch (op) {
+ case BEFORE_READ_LOCK:
+ return "Try Read lock";
+ case BEFORE_WRITE_LOCK:
+ return "Try Write lock";
+ case READ_LOCK:
+ return "Read lock";
+ case READ_UNLOCK:
+ return "Read unlock";
+ case WRITE_LOCK:
+ return "Write lock";
+ case WRITE_UNLOCK:
+ return "Write unlock";
+ }
+
+ return "N/A";
+ }
+
+ /**
+ * @param entry Log entry.
+ * @return String line.
+ */
+ private String buildPageInfo(PageLockLogSnapshot.LogEntry entry) {
+ int op = entry.operation;
+ long pageId = entry.pageId;
+ int structureId = entry.structureId;
+
+ return operationToString(op) + " pageId=" + pageId
+ + ", structureId=" + strucutreIdMapFunc.apply(structureId)
+ + " [pageIdHex=" + hexLong(pageId)
+ + ", partId=" + partId(pageId) + ", pageIdx=" + pageIndex(pageId)
+ + ", flags=" + hexInt(flag(pageId)) + "]";
+ }
+
+ /**
+ * @param holdedLocks Holded locks map.
+ * @return String line.
+ */
+ private String lockedPagesInfo(Map<Long, LockState> holdedLocks) {
+ SB sb = new SB();
+
+ sb.a("Locked pages = [");
+
+ boolean first = true;
+
+ for (Map.Entry<Long, LockState> entry : holdedLocks.entrySet()) {
+ Long pageId = entry.getKey();
+ LockState lockState = entry.getValue();
+
+ if (!first)
+ sb.a(",");
+ else
+ first = false;
+
+ sb.a(pageId)
+ .a("[" + hexLong(pageId) + "]")
+ .a("(r=" + lockState.readlock + "|w=" + lockState.writelock + ")");
+ }
+
+ sb.a("]");
+
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void processDump(PageLockLogSnapshot snapshot) {
+ Map<Long, LockState> holdetLocks = new LinkedHashMap<>();
+
+ SB logLocksStr = new SB();
+
+ List<PageLockLogSnapshot.LogEntry> locklog = snapshot.locklog;
+ int nextOp = snapshot.nextOp;
+ long nextOpPageId = snapshot.nextOpPageId;
+ int nextOpStructureId = snapshot.nextOpStructureId;
+
+ for (PageLockLogSnapshot.LogEntry entry : locklog) {
+ int op = entry.operation;
+ long pageId = entry.pageId;
+ int locksHolded = entry.holdedLocks;
+
+ if (op == READ_LOCK || op == WRITE_LOCK || op == BEFORE_READ_LOCK || op == BEFORE_WRITE_LOCK) {
+ LockState state = holdetLocks.get(pageId);
+
+ if (state == null)
+ holdetLocks.put(pageId, state = new LockState());
+
+ if (op == READ_LOCK)
+ state.readlock++;
+
+ if (op == WRITE_LOCK)
+ state.writelock++;
+
+ logLocksStr.a("L=" + locksHolded + " -> " + buildPageInfo(entry) + U.nl());
+ }
+
+ if (op == READ_UNLOCK || op == WRITE_UNLOCK) {
+ LockState state = holdetLocks.get(pageId);
+
+ if (op == READ_UNLOCK)
+ state.readlock--;
+
+ if (op == WRITE_UNLOCK)
+ state.writelock--;
+
+ if (state.readlock == 0 && state.writelock == 0)
+ holdetLocks.remove(pageId);
+
+ logLocksStr.a("L=" + locksHolded + " <- " + buildPageInfo(entry) + U.nl());
+ }
+ }
+
+ if (nextOpPageId != 0) {
+ logLocksStr.a("-> " + operationToString(nextOp) + " nextOpPageId=" + nextOpPageId +
+ ", nextOpStructureId=" + strucutreIdMapFunc.apply(nextOpStructureId)
+ + " [pageIdHex=" + hexLong(nextOpPageId)
+ + ", partId=" + partId(nextOpPageId) + ", pageIdx=" + pageIndex(nextOpPageId)
+ + ", flags=" + hexInt(flag(nextOpPageId)) + "]" + U.nl());
+ }
+
+ sb.append(lockedPagesInfo(holdetLocks)).append(U.nl());
+
+ sb.append("Locked pages log: ").append(snapshot.name)
+ .append(" time=(").append(snapshot.time).append(", ")
+ .append(DATE_FMT.format(new java.util.Date(snapshot.time)))
+ .append(")")
+ .append(U.nl());
+
+ sb.append(logLocksStr).append(U.nl());
+ ;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void processDump(PageLockStackSnapshot snapshot) {
+ int headIdx = snapshot.headIdx;
+ PageMetaInfoStore pageIdLocksStack = snapshot.pageIdLocksStack;
+ long nextOpPageId = snapshot.nextOpPageId;
+
+ Map<Long, LockState> holdedLocks = new LinkedHashMap<>();
+
+ SB stackStr = new SB();
+
+ if (nextOpPageId != 0)
+ stackStr.a("\t-> " + operationToString(snapshot.nextOp) +
+ " structureId=" + strucutreIdMapFunc.apply(snapshot.nextOpStructureId) +
+ " " + pageIdToString(nextOpPageId) + U.nl());
+
+ for (int itemIdx = headIdx - 1; itemIdx >= 0; itemIdx--) {
+ long pageId = pageIdLocksStack.getPageId(itemIdx);
+
+ if (pageId == 0 && itemIdx == 0)
+ break;
+
+ int op;
+
+ if (pageId == 0) {
+ stackStr.a("\t -\n");
+
+ continue;
+ }
+ else {
+ op = pageIdLocksStack.getOperation(itemIdx) & LOCK_OP_MASK;
+
+ int structureId = pageIdLocksStack.getStructureId(itemIdx);
+
+ stackStr.a("\t" + operationToString(op) +
+ " structureId=" + strucutreIdMapFunc.apply(structureId) +
+ " " + pageIdToString(pageId) + U.nl());
+ }
+
+ if (op == READ_LOCK || op == WRITE_LOCK || op == BEFORE_READ_LOCK) {
+ LockState state = holdedLocks.get(pageId);
+
+ if (state == null)
+ holdedLocks.put(pageId, state = new LockState());
+
+ if (op == READ_LOCK)
+ state.readlock++;
+
+ if (op == WRITE_LOCK)
+ state.writelock++;
+
+ }
+ }
+
+ sb.append(lockedPagesInfo(holdedLocks)).append(U.nl());
+
+ sb.append("Locked pages stack: ").append(snapshot.name)
+ .append(" time=(").append(snapshot.time).append(", ")
+ .append(DATE_FMT.format(new java.util.Date(snapshot.time)))
+ .append(")")
+ .append(U.nl());
+
+ sb.append(stackStr).append(U.nl());
+ }
+
+ /** {@inheritDoc} */
+ @Override public void processDump(ThreadPageLocksDumpLock snapshot) {
+ sb.append("Page locks dump:").append(U.nl()).append(U.nl());
+
+ List<ThreadState> threadStates = new ArrayList<>(snapshot.threadStates);
+
+ // Sort thread dump by thread names.
+ threadStates.sort(new Comparator<ThreadState>() {
+ /** {@inheritDoc} */
+ @Override public int compare(ThreadState thState1, ThreadState thState2) {
+ return thState1.threadName.compareTo(thState2.threadName);
+ }
+ });
+
+ for (ThreadState ths : threadStates) {
+ sb.append("Thread=[name=").append(ths.threadName)
+ .append(", id=").append(ths.threadId)
+ .append("], state=").append(ths.state)
+ .append(U.nl());
+
+ PageLockDump pageLockDump0;
+
+ if (ths.invalidContext == null)
+ pageLockDump0 = ths.pageLockDump;
+ else {
+ sb.append(ths.invalidContext.msg).append(U.nl());
+
+ pageLockDump0 = ths.invalidContext.dump;
+ }
+
+ pageLockDump0.apply(this);
+
+ sb.append(U.nl());
+ }
+ }
+ }
+}
+
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/LockLog.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/LockLog.java
new file mode 100644
index 0000000..a1b92ba
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/LockLog.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+
+/**
+ * Abstract page lock log class.
+ **/
+public class LockLog extends PageLockTracker<PageLockLogSnapshot> {
+ /** */
+ protected int headIdx;
+
+ /**
+ * Constructor.
+ *
+ * @param name Page lock log name.
+ * @param pageMetaInfoStore Object storing page meta info.
+ */
+ public LockLog(String name, PageMetaInfoStore pageMetaInfoStore, MemoryCalculator memCalc) {
+ super(name, pageMetaInfoStore, memCalc);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock0(int structureId, long pageId, long page, long pageAddr) {
+ log(WRITE_LOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock0(int structureId, long pageId, long page, long pageAddr) {
+ log(WRITE_UNLOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock0(int structureId, long pageId, long page, long pageAddr) {
+ log(READ_LOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock0(int structureId, long pageId, long page, long pageAddr) {
+ log(READ_UNLOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /**
+ * Log lock operation.
+ *
+ * @param structureId Structure id.
+ * @param pageId Page id.
+ * @param op Operation.
+ */
+ private void log(int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+ if (!validateOperation(structureId, pageId, op))
+ return;
+
+ if (headIdx + 1 > pages.capacity()) {
+ invalid("Log overflow, size:" + pages.capacity() +
+ ", headIdx=" + headIdx + " " + argsToString(structureId, pageId, op));
+
+ return;
+ }
+
+ long pageId0 = pages.getPageId(headIdx);
+
+ if (pageId0 != 0L && pageId0 != pageId) {
+ invalid("Head should be empty, headIdx=" + headIdx + " " +
+ argsToString(structureId, pageId, op));
+
+ return;
+ }
+
+ if (READ_LOCK == op || WRITE_LOCK == op)
+ heldLockCnt++;
+
+ if (READ_UNLOCK == op || WRITE_UNLOCK == op)
+ heldLockCnt--;
+
+ int curIdx = heldLockCnt << OP_OFFSET & LOCK_IDX_MASK;
+
+ pages.add(headIdx, curIdx | op, structureId, pageId, pageAddrHeader, pageAddr);
+
+ if (BEFORE_READ_LOCK == op || BEFORE_WRITE_LOCK == op)
+ return;
+
+ headIdx++;
+
+ if (heldLockCnt == 0)
+ reset();
+
+ if (op != BEFORE_READ_LOCK && op != BEFORE_WRITE_LOCK &&
+ nextOpPageId == pageId && nextOpStructureId == structureId) {
+ nextOpStructureId = 0;
+ nextOpPageId = 0;
+ nextOp = 0;
+ }
+ }
+
+ /**
+ * Reset log state.
+ */
+ private void reset() {
+ for (int itemIdx = 0; itemIdx < headIdx; itemIdx++)
+ pages.remove(itemIdx);
+
+ headIdx = 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public PageLockLogSnapshot snapshot() {
+ PageMetaInfoStore log = pages.copy();
+
+ return new PageLockLogSnapshot(
+ name,
+ System.currentTimeMillis(),
+ headIdx,
+ log,
+ nextOp,
+ nextOpStructureId,
+ nextOpPageId
+ );
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogSnapshot.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogSnapshot.java
new file mode 100644
index 0000000..1514df0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogSnapshot.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.DumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_IDX_MASK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_OP_MASK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.OP_OFFSET;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+
+/**
+ * Page lock log snapshot.
+ */
+public class PageLockLogSnapshot implements PageLockDump {
+ /** Page lock log name. */
+ public final String name;
+ /** Creation time. */
+ public final long time;
+ /** Head position. */
+ public final int headIdx;
+ /** List of log entries. */
+ public final List<LogEntry> locklog;
+ /** */
+ public final PageMetaInfoStore log;
+ /** Next operation. */
+ public final int nextOp;
+ /** Next data structure. */
+ public final int nextOpStructureId;
+ /** Next page id. */
+ public final long nextOpPageId;
+
+ /**
+ *
+ */
+ public PageLockLogSnapshot(
+ String name,
+ long time,
+ int headIdx,
+ PageMetaInfoStore log,
+ int nextOp,
+ int nextOpStructureId,
+ long nextOpPageId
+ ) {
+ this.name = name;
+ this.time = time;
+ this.headIdx = headIdx;
+ this.log = log;
+ this.locklog = toList(log);
+ this.nextOp = nextOp;
+ this.nextOpStructureId = nextOpStructureId;
+ this.nextOpPageId = nextOpPageId;
+ }
+
+ /**
+ * Convert log to list {@link PageLockLogSnapshot.LogEntry}.
+ *
+ * @return List of {@link PageLockLogSnapshot.LogEntry}.
+ */
+ private List<PageLockLogSnapshot.LogEntry> toList(PageMetaInfoStore pages) {
+ List<PageLockLogSnapshot.LogEntry> lockLog = new ArrayList<>(pages.capacity());
+
+ for (int itemIdx = 0; itemIdx < headIdx; itemIdx ++) {
+ int metaOnLock = pages.getOperation(itemIdx);
+
+ assert metaOnLock != 0;
+
+ int heldLocks = (int)(metaOnLock & LOCK_IDX_MASK) >> OP_OFFSET;
+
+ assert heldLocks >= 0;
+
+ int op = metaOnLock & LOCK_OP_MASK;
+
+ int structureId = pages.getStructureId(itemIdx);
+
+ long pageId = pages.getPageId(itemIdx);
+
+ lockLog.add(new PageLockLogSnapshot.LogEntry(pageId, structureId, op, heldLocks));
+ }
+
+ return lockLog;
+ }
+
+ /**
+ * Log entry.
+ */
+ public static class LogEntry {
+ /** */
+ public final long pageId;
+ /** */
+ public final int structureId;
+ /** */
+ public final int operation;
+ /** */
+ public final int holdedLocks;
+ /** */
+ public LogEntry(long pageId, int structureId, int operation, int holdedLock) {
+ this.pageId = pageId;
+ this.structureId = structureId;
+ this.operation = operation;
+ this.holdedLocks = holdedLock;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public void apply(DumpProcessor dumpProcessor) {
+ dumpProcessor.processDump(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long time() {
+ return time;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return toStringDump(this);
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/LockStack.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/LockStack.java
new file mode 100644
index 0000000..1606c72
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/LockStack.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+
+/**
+ * Abstract page lock stack.
+ */
+public class LockStack extends PageLockTracker<PageLockStackSnapshot> {
+ /**
+ *
+ */
+ protected int headIdx;
+
+ /**
+ * @param name Page lock stack name.
+ * @param pageMetaInfoStore Capacity.
+ */
+ public LockStack(String name, PageMetaInfoStore pageMetaInfoStore, MemoryCalculator memCalc) {
+ super(name, pageMetaInfoStore, memCalc);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock0(int structureId, long pageId, long page, long pageAddr) {
+ push(WRITE_LOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock0(int structureId, long pageId, long page, long pageAddr) {
+ pop(WRITE_UNLOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock0(int structureId, long pageId, long page, long pageAddr) {
+ push(READ_LOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock0(int structureId, long pageId, long page, long pageAddr) {
+ pop(READ_UNLOCK, structureId, pageId, page, pageAddr);
+ }
+
+ /**
+ * Push operation on top of stack.
+ *
+ * @param structureId Strcuture id.
+ * @param pageId Page id.
+ * @param op Operation type.
+ */
+ private void push(int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+ if (!validateOperation(structureId, pageId, op))
+ return;
+
+ reset();
+
+ if (headIdx + 1 > pages.capacity()) {
+ invalid("Stack overflow, size=" + pages.capacity() +
+ ", headIdx=" + headIdx + " " + argsToString(structureId, pageId, op));
+
+ return;
+ }
+
+ long pageId0 = pages.getPageId(headIdx);
+
+ if (pageId0 != 0L) {
+ invalid("Head element should be empty, headIdx=" + headIdx +
+ ", pageIdOnHead=" + pageId0 + " " + argsToString(structureId, pageId, op));
+
+ return;
+ }
+
+ int curIdx = heldLockCnt << OP_OFFSET & LOCK_IDX_MASK;
+
+ pages.add(headIdx, curIdx | op, structureId, pageId, pageAddrHeader, pageAddr);
+
+ headIdx++;
+ heldLockCnt++;
+ }
+
+ /**
+ * Pop operation from top of stack.
+ *
+ * @param structureId Structure id.
+ * @param pageId Page id.
+ * @param op Operation type.
+ */
+ private void pop(int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+ if (!validateOperation(structureId, pageId, op))
+ return;
+
+ reset();
+
+ if (headIdx > 1) {
+ int lastItemIdx = headIdx - 1;
+
+ long lastPageId = pages.getPageId(lastItemIdx);
+
+ if (lastPageId == pageId) {
+ pages.remove(lastItemIdx);
+
+ //Reset head to the first not empty element.
+ do {
+ headIdx--;
+ heldLockCnt--;
+ }
+ while (headIdx > 0 && pages.getPageId(headIdx - 1) == 0);
+ }
+ else {
+ for (int itemIdx = lastItemIdx - 1; itemIdx >= 0; itemIdx--) {
+ if (pages.getPageId(itemIdx) == pageId) {
+ pages.remove(itemIdx);
+ return;
+ }
+ }
+
+ invalid("Can not find pageId in stack, headIdx=" + headIdx + " "
+ + argsToString(structureId, pageId, op));
+ }
+ }
+ else {
+ if (headIdx < 0) {
+ invalid("HeadIdx can not be less, headIdx="
+ + headIdx + ", " + argsToString(structureId, pageId, op));
+
+ return;
+ }
+
+ long pageId0 = pages.getPageId(0);
+
+ if (pageId0 == 0) {
+ invalid("Stack is empty, can not pop elemnt" + argsToString(structureId, pageId, op));
+
+ return;
+ }
+
+ if (pageId0 == pageId) {
+ pages.remove(0);
+
+ headIdx = 0;
+ heldLockCnt = 0;
+ }
+ else
+ invalid("Can not find pageId in stack, headIdx=" + headIdx + " "
+ + argsToString(structureId, pageId, op));
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected PageLockStackSnapshot snapshot() {
+ PageMetaInfoStore stack = pages.copy();
+
+ return new PageLockStackSnapshot(
+ name,
+ System.currentTimeMillis(),
+ headIdx,
+ stack,
+ nextOp,
+ nextOpStructureId,
+ nextOpPageId
+ );
+ }
+
+ /**
+ * Reset next opeation info.
+ */
+ private void reset() {
+ nextOpPageId = 0;
+ nextOp = 0;
+ nextOpStructureId = 0;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackSnapshot.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackSnapshot.java
new file mode 100644
index 0000000..76e985d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackSnapshot.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.DumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+
+/**
+ * Page lock stack snapshot.
+ */
+public class PageLockStackSnapshot implements PageLockDump {
+ /** */
+ public final String name;
+ /** */
+ public final long time;
+ /** */
+ public final int headIdx;
+ /** */
+ public final PageMetaInfoStore pageIdLocksStack;
+ /** */
+ public final int nextOp;
+ /** */
+ public final int nextOpStructureId;
+ /** */
+ public final long nextOpPageId;
+
+ /**
+ *
+ */
+ public PageLockStackSnapshot(
+ String name,
+ long time,
+ int headIdx,
+ PageMetaInfoStore pageIdLocksStack,
+ int nextOp,
+ int nextOpStructureId,
+ long nextOpPageId
+ ) {
+ this.name = name;
+ this.time = time;
+ this.headIdx = headIdx;
+ this.pageIdLocksStack = pageIdLocksStack;
+ this.nextOp = nextOp;
+ this.nextOpStructureId = nextOpStructureId;
+ this.nextOpPageId = nextOpPageId;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void apply(DumpProcessor dumpProcessor) {
+ dumpProcessor.processDump(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long time() {
+ return time;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return toStringDump(this);
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/HeapPageMetaInfoStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/HeapPageMetaInfoStore.java
new file mode 100644
index 0000000..051f1e1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/HeapPageMetaInfoStore.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.store;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class HeapPageMetaInfoStore implements PageMetaInfoStore {
+ /**
+ *
+ */
+ private static final int OVERHEAD_SIZE = 8 + 16 + 8 + 8;
+ /**
+ *
+ */
+ private static final int PAGE_ID_OFFSET = 0;
+ /**
+ *
+ */
+ private static final int PAGE_HEADER_ADDRESS_OFFSET = 1;
+ /**
+ *
+ */
+ private static final int PAGE_ADDRESS_OFFSET = 2;
+ /**
+ *
+ */
+ private static final int PAGE_META_OFFSET = 3;
+ /**
+ *
+ */
+ private static final int ITEM_SIZE = 4;
+ /**
+ *
+ */
+ private long[] arr;
+ /**
+ *
+ */
+ private final MemoryCalculator memoryCalc;
+
+ /**
+ *
+ */
+ public HeapPageMetaInfoStore(int capacity, @Nullable MemoryCalculator memoryCalc) {
+ this.arr = new long[capacity * ITEM_SIZE];
+ this.memoryCalc = memoryCalc;
+
+ if (memoryCalc != null)
+ memoryCalc.onHeapAllocated(arr.length * 8 + OVERHEAD_SIZE);
+ }
+
+ HeapPageMetaInfoStore(long[] arr) {
+ this.arr = arr;
+ memoryCalc = null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int capacity() {
+ return arr.length / ITEM_SIZE;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isEmpty() {
+ for (int i = 0; i < arr.length; i++) {
+ if (arr[i] != 0)
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void add(int itemIdx, int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+ arr[ITEM_SIZE * itemIdx + PAGE_ID_OFFSET] = pageId;
+ arr[ITEM_SIZE * itemIdx + PAGE_HEADER_ADDRESS_OFFSET] = pageAddrHeader;
+ arr[ITEM_SIZE * itemIdx + PAGE_ADDRESS_OFFSET] = pageAddr;
+ arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET] = meta(structureId, op);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void remove(int itemIdx) {
+ arr[ITEM_SIZE * itemIdx + PAGE_ID_OFFSET] = 0;
+ arr[ITEM_SIZE * itemIdx + PAGE_HEADER_ADDRESS_OFFSET] = 0;
+ arr[ITEM_SIZE * itemIdx + PAGE_ADDRESS_OFFSET] = 0;
+ arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET] = 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getOperation(int itemIdx) {
+ long structureIdAndOp = arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET];
+
+ return (int)((structureIdAndOp >> 32));
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getStructureId(int itemIdx) {
+ long structureIdAndOp = arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET];
+
+ return (int)(structureIdAndOp);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long getPageId(int itemIdx) {
+ return arr[ITEM_SIZE * itemIdx];
+ }
+
+ /** {@inheritDoc} */
+ @Override public long getPageAddrHeader(int itemIdx) {
+ return arr[ITEM_SIZE * itemIdx + PAGE_HEADER_ADDRESS_OFFSET];
+ }
+
+ /** {@inheritDoc} */
+ @Override public long getPageAddr(int itemIdx) {
+ return arr[ITEM_SIZE * itemIdx + PAGE_ADDRESS_OFFSET];
+ }
+
+ /** {@inheritDoc} */
+ @Override public PageMetaInfoStore copy() {
+ return new HeapPageMetaInfoStore(arr.clone());
+ }
+
+ /** {@inheritDoc} */
+ @Override public void free() {
+ if (memoryCalc != null)
+ memoryCalc.onHeapFree(arr.length * 8 + OVERHEAD_SIZE);
+
+ arr = null;
+ }
+
+ /**
+ * Build long from two int.
+ *
+ * @param structureId Structure id.
+ * @param op Operation.
+ */
+ private long meta(int structureId, int op) {
+ long major = ((long)op) << 32;
+
+ long minor = structureId & 0xFFFFFFFFL;
+
+ return major | minor;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/OffHeapPageMetaInfoStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/OffHeapPageMetaInfoStore.java
new file mode 100644
index 0000000..8cdd858
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/OffHeapPageMetaInfoStore.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.store;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.util.GridUnsafe;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_OP_MASK;
+
+/**
+ *
+ */
+public class OffHeapPageMetaInfoStore implements PageMetaInfoStore {
+ /**
+ *
+ */
+ private static final long OVERHEAD_SIZE = 16 + 4 + 4 + 8 + 8;
+ /**
+ *
+ */
+ private static final int PAGE_ID_OFFSET = 0;
+ /**
+ *
+ */
+ private static final int PAGE_HEADER_ADDRESS_OFFSET = 8;
+ /**
+ *
+ */
+ private static final int PAGE_ADDRESS_OFFSET = 16;
+ /**
+ *
+ */
+ private static final int PAGE_META_OFFSET = 24;
+ /**
+ *
+ */
+ private static final int ITEM_SIZE = 4;
+ /**
+ *
+ */
+ private final int size;
+ /**
+ *
+ */
+ private final int capacity;
+ /**
+ *
+ */
+ private final long ptr;
+ /**
+ *
+ */
+ private final MemoryCalculator memCalc;
+
+ /**
+ *
+ */
+ public OffHeapPageMetaInfoStore(int capacity, @Nullable MemoryCalculator memCalc) {
+ this.capacity = capacity;
+ this.size = this.capacity * (8 * ITEM_SIZE);
+ this.ptr = allocate(size);
+ this.memCalc = memCalc;
+
+ if (memCalc != null) {
+ memCalc.onHeapAllocated(OVERHEAD_SIZE);
+ memCalc.onOffHeapAllocated(size);
+ }
+ }
+
+ /**
+ *
+ */
+ private long allocate(int size) {
+ long ptr = GridUnsafe.allocateMemory(size);
+
+ GridUnsafe.setMemory(ptr, size, (byte)0);
+
+ return ptr;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int capacity() {
+ return capacity;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isEmpty() {
+ for (int i = 0; i < size; i++) {
+ if (GridUnsafe.getByte(ptr + i) != 0)
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void add(int itemIdx, int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_ID_OFFSET, pageId);
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_HEADER_ADDRESS_OFFSET, pageAddrHeader);
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_ADDRESS_OFFSET, pageAddr);
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_META_OFFSET, join(structureId, op));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void remove(int itemIdx) {
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_ID_OFFSET, 0);
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_HEADER_ADDRESS_OFFSET, 0);
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_ADDRESS_OFFSET, 0);
+ GridUnsafe.putLong(offset(itemIdx) + PAGE_META_OFFSET, 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getOperation(int itemIdx) {
+ long structureIdAndOp = GridUnsafe.getLong(offset(itemIdx) + PAGE_META_OFFSET);
+
+ return (int)((structureIdAndOp >> 32) & LOCK_OP_MASK);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int getStructureId(int itemIdx) {
+ long structureIdAndOp = GridUnsafe.getLong(offset(itemIdx) + PAGE_META_OFFSET);
+
+ return (int)(structureIdAndOp);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long getPageId(int itemIdx) {
+ return GridUnsafe.getLong(offset(itemIdx) + PAGE_ID_OFFSET);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long getPageAddrHeader(int itemIdx) {
+ return GridUnsafe.getLong(offset(itemIdx) + PAGE_HEADER_ADDRESS_OFFSET);
+ }
+
+ /** {@inheritDoc} */
+ @Override public long getPageAddr(int itemIdx) {
+ return GridUnsafe.getLong(offset(itemIdx) + PAGE_ADDRESS_OFFSET);
+ }
+
+ /**
+ *
+ */
+ private long offset(long itemIdx) {
+ long offset = ptr + itemIdx * 8 * ITEM_SIZE;
+
+ assert offset >= ptr && offset <= ((ptr + size) - 8 * ITEM_SIZE) :"offset=" + (offset - ptr) + ", size=" + size;
+
+ return offset;
+ }
+
+ /** {@inheritDoc} */
+ @Override public PageMetaInfoStore copy() {
+ long[] arr = new long[capacity * 4];
+
+ GridUnsafe.copyMemory(null, ptr, arr, GridUnsafe.LONG_ARR_OFF, size);
+
+ return new HeapPageMetaInfoStore(arr);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void free() {
+ GridUnsafe.freeMemory(ptr);
+
+ if (memCalc != null) {
+ memCalc.onHeapFree(OVERHEAD_SIZE);
+ memCalc.onOffHeapFree(size);
+ }
+ }
+
+ /**
+ * Build long from two int.
+ *
+ * @param structureId Structure id.
+ * @param op Operation.
+ */
+ private long join(int structureId, int op) {
+ long major = ((long)op) << 32;
+
+ long minor = structureId & 0xFFFFFFFFL;
+
+ return major | minor;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
index 63e25ac..0d120d7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -39,6 +39,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.LongLi
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.util.typedef.internal.U;
/**
@@ -339,8 +340,10 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
ReuseList reuseList,
IgniteWriteAheadLogManager wal,
long metaPageId,
- boolean initNew) throws IgniteCheckedException {
- super(cacheId, name, memPlc.pageMemory(), BUCKETS, wal, metaPageId);
+ boolean initNew,
+ PageLockListener lockLsnr
+ ) throws IgniteCheckedException {
+ super(cacheId, name, memPlc.pageMemory(), BUCKETS, wal, metaPageId, lockLsnr);
rmvRow = new RemoveRowHandler(cacheId == 0);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
similarity index 78%
rename from modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
rename to modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
index 2fd3edb..454a600 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -26,12 +26,13 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.AbstractD
import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.util.typedef.internal.U;
/**
* FreeList implementation for cache.
*/
-public class CacheFreeListImpl extends AbstractFreeList<CacheDataRow> {
+public class CacheFreeList extends AbstractFreeList<CacheDataRow> {
/**
* @param cacheId Cache id.
* @param name Name.
@@ -42,10 +43,28 @@ public class CacheFreeListImpl extends AbstractFreeList<CacheDataRow> {
* @param metaPageId Meta page id.
* @param initNew Initialize new.
*/
- public CacheFreeListImpl(int cacheId, String name, DataRegionMetricsImpl regionMetrics, DataRegion dataRegion,
+ public CacheFreeList(
+ int cacheId,
+ String name,
+ DataRegionMetricsImpl regionMetrics,
+ DataRegion dataRegion,
ReuseList reuseList,
- IgniteWriteAheadLogManager wal, long metaPageId, boolean initNew) throws IgniteCheckedException {
- super(cacheId, name, regionMetrics, dataRegion, reuseList, wal, metaPageId, initNew);
+ IgniteWriteAheadLogManager wal,
+ long metaPageId,
+ boolean initNew,
+ PageLockListener lockLsnr
+ ) throws IgniteCheckedException {
+ super(
+ cacheId,
+ name,
+ regionMetrics,
+ dataRegion,
+ reuseList,
+ wal,
+ metaPageId,
+ initNew,
+ lockLsnr
+ );
}
/** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
index b1ea92bf9..1fa9062 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersion
import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.util.GridArrays;
import org.apache.ignite.internal.util.GridLongList;
import org.apache.ignite.internal.util.typedef.F;
@@ -136,9 +137,10 @@ public abstract class PagesList extends DataStructure {
PageMemory pageMem,
int buckets,
IgniteWriteAheadLogManager wal,
- long metaPageId
+ long metaPageId,
+ PageLockListener lockLsnr
) {
- super(cacheId, pageMem, wal);
+ super(cacheId, pageMem, wal, lockLsnr);
this.name = name;
this.buckets = buckets;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
index ea7a997..e36ad17 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
@@ -45,6 +45,7 @@ import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
import org.apache.ignite.internal.pagemem.wal.WALPointer;
import org.apache.ignite.internal.pagemem.wal.record.MetastoreDataRecord;
import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
import org.apache.ignite.internal.processors.cache.CacheGroupContext;
import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
@@ -66,6 +67,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageParti
import org.apache.ignite.internal.processors.cache.persistence.tree.io.SimpleDataPageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
import org.apache.ignite.internal.util.lang.GridCursor;
import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -245,14 +247,39 @@ public class MetaStorage implements DbCheckpointListener, ReadOnlyMetastorage, R
getOrAllocateMetas(partId = PageIdAllocator.METASTORE_PARTITION);
if (!empty) {
- freeList = new FreeListImpl(METASTORAGE_CACHE_ID, "metastorage",
- regionMetrics, dataRegion, null, wal, reuseListRoot.pageId().pageId(),
- reuseListRoot.isAllocated());
+ CacheDiagnosticManager diagnosticMgr = cctx.diagnostic();
+
+ String freeListName = METASTORAGE_CACHE_NAME + "##FreeList";
+ String treeName = METASTORAGE_CACHE_NAME + "##Tree";
+
+ freeList = new FreeListImpl(
+ METASTORAGE_CACHE_ID,
+ freeListName,
+ regionMetrics,
+ dataRegion,
+ null,
+ wal,
+ reuseListRoot.pageId().pageId(),
+ reuseListRoot.isAllocated(),
+ diagnosticMgr.pageLockTracker().createPageLockTracker(freeListName)
+ );
MetastorageRowStore rowStore = new MetastorageRowStore(freeList, db);
- tree = new MetastorageTree(METASTORAGE_CACHE_ID, dataRegion.pageMemory(), wal, rmvId,
- freeList, rowStore, treeRoot.pageId().pageId(), treeRoot.isAllocated(), failureProcessor, partId);
+ tree = new MetastorageTree(
+ METASTORAGE_CACHE_ID,
+ treeName,
+ dataRegion.pageMemory(),
+ wal,
+ rmvId,
+ freeList,
+ rowStore,
+ treeRoot.pageId().pageId(),
+ treeRoot.isAllocated(),
+ failureProcessor,
+ partId,
+ diagnosticMgr.pageLockTracker().createPageLockTracker(treeName)
+ );
if (!readOnly)
((GridCacheDatabaseSharedManager)db).addCheckpointListener(this);
@@ -636,10 +663,18 @@ public class MetaStorage implements DbCheckpointListener, ReadOnlyMetastorage, R
/** */
public class FreeListImpl extends AbstractFreeList<MetastorageDataRow> {
/** {@inheritDoc} */
- FreeListImpl(int cacheId, String name, DataRegionMetricsImpl regionMetrics, DataRegion dataRegion,
+ FreeListImpl(
+ int cacheId,
+ String name,
+ DataRegionMetricsImpl regionMetrics,
+ DataRegion dataRegion,
ReuseList reuseList,
- IgniteWriteAheadLogManager wal, long metaPageId, boolean initNew) throws IgniteCheckedException {
- super(cacheId, name, regionMetrics, dataRegion, reuseList, wal, metaPageId, initNew);
+ IgniteWriteAheadLogManager wal,
+ long metaPageId,
+ boolean initNew,
+ PageLockListener lsnr
+ ) throws IgniteCheckedException {
+ super(cacheId, name, regionMetrics, dataRegion, reuseList, wal, metaPageId, initNew, lsnr);
}
/** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java
index 936c5af..8c3d282 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusInne
import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeafIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
import org.jetbrains.annotations.Nullable;
@@ -57,7 +58,9 @@ public class MetastorageTree extends BPlusTree<MetastorageSearchRow, Metastorage
* @param failureProcessor To call if the tree is corrupted.
* @throws IgniteCheckedException If failed to initialize.
*/
- public MetastorageTree(int cacheId,
+ public MetastorageTree(
+ int cacheId,
+ String name,
PageMemory pageMem,
IgniteWriteAheadLogManager wal,
AtomicLong globalRmvId,
@@ -66,9 +69,22 @@ public class MetastorageTree extends BPlusTree<MetastorageSearchRow, Metastorage
long metaPageId,
boolean initNew,
@Nullable FailureProcessor failureProcessor,
- int partId) throws IgniteCheckedException {
- super("Metastorage", cacheId, pageMem, wal,
- globalRmvId, metaPageId, reuseList, MetastorageInnerIO.VERSIONS, MetastoreLeafIO.VERSIONS, failureProcessor);
+ int partId,
+ @Nullable PageLockListener lockLsnr
+ ) throws IgniteCheckedException {
+ super(
+ name,
+ cacheId,
+ pageMem,
+ wal,
+ globalRmvId,
+ metaPageId,
+ reuseList,
+ MetastorageInnerIO.VERSIONS,
+ MetastoreLeafIO.VERSIONS,
+ failureProcessor,
+ lockLsnr
+ );
this.rowStore = rowStore;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java
index 059ced4..dca08ce 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java
@@ -148,7 +148,8 @@ public class UpgradePendingTreeToPerPartitionTask implements IgniteCallable<Bool
grp.dataRegion().pageMemory(),
pendingRootPage.pageId().pageId(),
((GridCacheOffheapManager)grp.offheap()).reuseListForIndex(null),
- false
+ false,
+ null
);
}
finally {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
index 03524e8..422d082 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
@@ -58,6 +58,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseB
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandlerWrapper;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.failure.FailureProcessor;
import org.apache.ignite.internal.util.GridArrays;
import org.apache.ignite.internal.util.GridLongList;
@@ -746,9 +747,21 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
ReuseList reuseList,
IOVersions<? extends BPlusInnerIO<L>> innerIos,
IOVersions<? extends BPlusLeafIO<L>> leafIos,
- @Nullable FailureProcessor failureProcessor
+ @Nullable FailureProcessor failureProcessor,
+ @Nullable PageLockListener lockLsnr
) throws IgniteCheckedException {
- this(name, cacheId, pageMem, wal, globalRmvId, metaPageId, reuseList, failureProcessor);
+ this(
+ name,
+ cacheId,
+ pageMem,
+ wal,
+ globalRmvId,
+ metaPageId,
+ reuseList,
+ failureProcessor,
+ lockLsnr
+ );
+
setIos(innerIos, leafIos);
}
@@ -771,9 +784,10 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
AtomicLong globalRmvId,
long metaPageId,
ReuseList reuseList,
- @Nullable FailureProcessor failureProcessor
+ @Nullable FailureProcessor failureProcessor,
+ @Nullable PageLockListener lsnr
) throws IgniteCheckedException {
- super(cacheId, pageMem, wal);
+ super(cacheId, pageMem, wal, lsnr);
assert !F.isEmpty(name);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
index aaf07d9..027db9b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.pagemem.PageMemory;
import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
/**
* Reuse list.
@@ -42,13 +43,24 @@ public class ReuseListImpl extends PagesList implements ReuseList {
* @param initNew {@code True} if new metadata should be initialized.
* @throws IgniteCheckedException If failed.
*/
- public ReuseListImpl(int cacheId,
+ public ReuseListImpl(
+ int cacheId,
String name,
PageMemory pageMem,
IgniteWriteAheadLogManager wal,
long metaPageId,
- boolean initNew) throws IgniteCheckedException {
- super(cacheId, name, pageMem, 1, wal, metaPageId);
+ boolean initNew,
+ PageLockListener lockLsnr
+ ) throws IgniteCheckedException {
+ super(
+ cacheId,
+ name,
+ pageMem,
+ 1,
+ wal,
+ metaPageId,
+ lockLsnr
+ );
reuseList = this;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
index 6dd565e..3c3d388 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
@@ -378,7 +378,7 @@ public class IgniteWalIteratorFactory {
kernalCtx, null, null, null,
null, null, null, dbMgr, null,
null, null, null, null, null,
- null,null, null, null, null
+ null, null, null, null, null, null
);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
index 0ab46e6..320fd01 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
@@ -30,6 +30,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageI
import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPagePayload;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataInnerIO;
import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataLeafIO;
import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataInnerIO;
@@ -67,9 +68,11 @@ public class CacheDataTree extends BPlusTree<CacheSearchRow, CacheDataRow> {
ReuseList reuseList,
CacheDataRowStore rowStore,
long metaPageId,
- boolean initNew
+ boolean initNew,
+ PageLockListener lockLsnr
) throws IgniteCheckedException {
- super(name,
+ super(
+ name,
grp.groupId(),
grp.dataRegion().pageMemory(),
grp.dataRegion().config().isPersistenceEnabled() ? grp.shared().wal() : null,
@@ -78,7 +81,9 @@ public class CacheDataTree extends BPlusTree<CacheSearchRow, CacheDataRow> {
reuseList,
innerIO(grp),
leafIO(grp),
- grp.shared().kernalContext().failure());
+ grp.shared().kernalContext().failure(),
+ lockLsnr
+ );
assert rowStore != null;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java
index 9e5d65d..407e630 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java
@@ -22,6 +22,7 @@ import org.apache.ignite.internal.processors.cache.CacheGroupContext;
import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.util.typedef.internal.CU;
/**
@@ -49,9 +50,11 @@ public class PendingEntriesTree extends BPlusTree<PendingRow, PendingRow> {
PageMemory pageMem,
long metaPageId,
ReuseList reuseList,
- boolean initNew)
- throws IgniteCheckedException {
- super(name,
+ boolean initNew,
+ PageLockListener lockLsnr
+ ) throws IgniteCheckedException {
+ super(
+ name,
grp.groupId(),
pageMem,
grp.dataRegion().config().isPersistenceEnabled() ? grp.shared().wal() : null,
@@ -60,7 +63,9 @@ public class PendingEntriesTree extends BPlusTree<PendingRow, PendingRow> {
reuseList,
grp.sharedGroup() ? CacheIdAwarePendingEntryInnerIO.VERSIONS : PendingEntryInnerIO.VERSIONS,
grp.sharedGroup() ? CacheIdAwarePendingEntryLeafIO.VERSIONS : PendingEntryLeafIO.VERSIONS,
- grp.shared().kernalContext().failure());
+ grp.shared().kernalContext().failure(),
+ lockLsnr
+ );
this.grp = grp;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java
index 36cb248..a063dc6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java
@@ -22,6 +22,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.failure.FailureContext;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.processors.GridProcessorAdapter;
@@ -41,6 +42,9 @@ import static org.apache.ignite.internal.util.IgniteStopwatch.logTime;
* Processor which contained helper methods for different diagnostic cases.
*/
public class DiagnosticProcessor extends GridProcessorAdapter {
+ /** Value of the system property that enables page locks dumping on failure. */
+ private static final boolean IGNITE_DUMP_PAGE_LOCK_ON_FAILURE =
+ IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_DUMP_PAGE_LOCK_ON_FAILURE, true);
/** Time formatter for dump file name. */
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss_SSS");
/** Folder name for store diagnostic info. **/
@@ -93,6 +97,10 @@ public class DiagnosticProcessor extends GridProcessorAdapter {
* @param failureCtx Failure context.
*/
public void onFailure(Ignite ignite, FailureContext failureCtx) {
+ // Dump data structures page locks.
+ if (IGNITE_DUMP_PAGE_LOCK_ON_FAILURE)
+ ctx.cache().context().diagnostic().pageLockTracker().dumpLocksToLog();
+
// If we have some corruption in data structure,
// we should scan WAL and print to log and save to file all pages related to corruption for
// future investigation.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksResult.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksResult.java
new file mode 100644
index 0000000..6c6cdde
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.visor.diagnostic;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+
+/**
+ *
+ */
+public class VisorPageLocksResult extends IgniteDataTransferObject {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ private String payload;
+
+ /**
+ *
+ */
+ public VisorPageLocksResult() {
+ //No-op.
+ }
+
+ /**
+ * @param payload Result payload as string.
+ */
+ public VisorPageLocksResult(String payload) {
+ this.payload = payload;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+ if (payload == null) {
+ out.writeInt(0);
+ }
+ else {
+ byte[] bytes = payload.getBytes();
+ int length = bytes.length;
+
+ out.writeInt(length);
+ out.write(bytes);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void readExternalData(byte protoVer, ObjectInput in)
+ throws IOException, ClassNotFoundException {
+ int length = in.readInt();
+
+ if (length != 0) {
+ byte[] bytes = new byte[length];
+
+ in.read(bytes);
+
+ payload = new String(bytes);
+ }
+ }
+
+ /**
+ * @return String result represetnation.
+ */
+ public String result() {
+ return payload;
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTask.java
new file mode 100644
index 0000000..cbe6c47
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTask.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.visor.diagnostic;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.UUID;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorMultiNodeTask;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.jetbrains.annotations.Nullable;
+
+@GridInternal
+public class VisorPageLocksTask
+ extends VisorMultiNodeTask<VisorPageLocksTrackerArgs, Map<ClusterNode, VisorPageLocksResult>, VisorPageLocksResult> {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 0L;
+
+ /** {@inheritDoc} */
+ @Override protected VisorJob<VisorPageLocksTrackerArgs, VisorPageLocksResult> job(VisorPageLocksTrackerArgs arg) {
+ return new VisorPageLocksTrackerJob(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected Collection<UUID> jobNodes(VisorTaskArgument<VisorPageLocksTrackerArgs> arg) {
+ Set<UUID> nodeIds = new HashSet<>();
+
+ Set<String> nodeIds0 = arg.getArgument().nodeIds();
+
+ for (ClusterNode node : ignite.cluster().nodes()) {
+ if (nodeIds0.contains(String.valueOf(node.consistentId())) || nodeIds0.contains(node.id().toString()))
+ nodeIds.add(node.id());
+ }
+
+ if (F.isEmpty(nodeIds))
+ nodeIds.add(ignite.localNode().id());
+
+ return nodeIds;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable @Override protected Map<ClusterNode, VisorPageLocksResult> reduce0(
+ List<ComputeJobResult> results
+ ) throws IgniteException {
+ Map<ClusterNode, VisorPageLocksResult> mapRes = new TreeMap<>();
+
+ results.forEach(j -> {
+ if (j.getException() == null)
+ mapRes.put(j.getNode(), j.getData());
+ else if (j.getException() != null) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ j.getException().printStackTrace(pw);
+
+ mapRes.put(j.getNode(), new VisorPageLocksResult(sw.toString()));
+ }
+ });
+
+ return mapRes;
+ }
+
+ /**
+ *
+ */
+ private static class VisorPageLocksTrackerJob extends VisorJob<VisorPageLocksTrackerArgs, VisorPageLocksResult> {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * @param arg Formal job argument.
+ * @param debug Debug flag.
+ */
+ private VisorPageLocksTrackerJob(VisorPageLocksTrackerArgs arg, boolean debug) {
+ super(arg, debug);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected VisorPageLocksResult run(VisorPageLocksTrackerArgs arg) {
+ PageLockTrackerManager lockTrackerMgr = ignite.context().cache().context().diagnostic().pageLockTracker();
+
+ String op = arg.type();
+
+ String result;
+
+ if ("dump".equals(op)) {
+ String filePath = arg.filePath() != null ?
+ lockTrackerMgr.dumpLocksToFile(arg.filePath()) :
+ lockTrackerMgr.dumpLocksToFile();
+
+ result = "Page locks dump was writtern to file " + filePath;
+ }
+ else if ("dump_log".equals(op)) {
+ lockTrackerMgr.dumpLocksToLog();
+
+ result = "Page locks dump was printed to console " +
+ ToStringDumpProcessor.DATE_FMT.format(new Date(System.currentTimeMillis()));
+ }
+ else
+ result = "Unsupported operation: " + op;
+
+ return new VisorPageLocksResult(result);
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(VisorPageLocksTrackerJob.class, this);
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTrackerArgs.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTrackerArgs.java
new file mode 100644
index 0000000..2911ee5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTrackerArgs.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.visor.diagnostic;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collections;
+import java.util.Set;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class VisorPageLocksTrackerArgs extends IgniteDataTransferObject {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ private String op;
+ /** */
+ private String type;
+ /** */
+ private String filePath;
+ /** */
+ @Nullable private Set<String> consistentIds;
+
+ public VisorPageLocksTrackerArgs() {
+
+ }
+
+ public VisorPageLocksTrackerArgs(String op, String type, String filePath, Set<String> consistentIds) {
+ this.op = op;
+ this.type = type;
+ this.filePath = filePath;
+ this.consistentIds = consistentIds;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+ if (op == null)
+ out.writeInt(0);
+ else {
+ byte[] bytes = op.getBytes();
+ out.writeInt(bytes.length);
+ out.write(bytes);
+ }
+
+ if (type == null)
+ out.writeInt(0);
+ else {
+ byte[] bytes = type.getBytes();
+ out.writeInt(bytes.length);
+ out.write(bytes);
+ }
+
+ if (filePath == null)
+ out.writeInt(0);
+ else {
+ byte[] bytes = filePath.getBytes();
+ out.writeInt(bytes.length);
+ out.write(bytes);
+ }
+
+ if (consistentIds != null)
+ U.writeCollection(out, consistentIds);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+ int opLenght = in.readInt();
+
+ if (opLenght != 0) {
+ byte[] bytes = new byte[opLenght];
+
+ in.read(bytes);
+
+ op = new String(bytes);
+ }
+
+ int typeLenght = in.readInt();
+
+ if (typeLenght != 0) {
+ byte[] bytes = new byte[typeLenght];
+
+ in.read(bytes);
+
+ type = new String(bytes);
+ }
+
+ int filePathLenght = in.readInt();
+
+ if (filePathLenght != 0) {
+ byte[] bytes = new byte[filePathLenght];
+
+ in.read(bytes);
+
+ filePath = new String(bytes);
+ }
+
+ consistentIds = U.readSet(in);
+ }
+
+ public String operation() {
+ return op;
+ }
+
+ public String type() {
+ return type;
+ }
+
+ public String filePath() {
+ return filePath;
+ }
+
+ public Set<String> nodeIds(){
+ return Collections.unmodifiableSet(consistentIds);
+ }
+}
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index e33dcb3..36529a0 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -2345,6 +2345,10 @@ org.apache.ignite.internal.visor.tx.VisorTxTask$TxKillClosure
org.apache.ignite.internal.visor.tx.VisorTxTask$VisorTxJob
org.apache.ignite.internal.visor.tx.VisorTxTaskArg
org.apache.ignite.internal.visor.tx.VisorTxTaskResult
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTrackerArgs
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksResult
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTask
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTask$VisorPageLocksTrackerJob
org.apache.ignite.internal.visor.util.VisorClusterGroupEmptyException
org.apache.ignite.internal.visor.util.VisorEventMapper
org.apache.ignite.internal.visor.util.VisorExceptionWrapper
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java
index 00515d2..47cd341 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java
@@ -152,6 +152,7 @@ public class IgniteWalIteratorSwitchSegmentTest extends GridCommonAbstractTest {
null,
null,
null,
+ null,
null)
).createSerializer(serVer);
@@ -478,6 +479,7 @@ public class IgniteWalIteratorSwitchSegmentTest extends GridCommonAbstractTest {
null,
null,
null,
+ null,
null
);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java
index e172e01..e2caf16 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java
@@ -54,7 +54,7 @@ import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
import org.apache.ignite.internal.processors.cache.persistence.freelist.AbstractFreeList;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseListImpl;
import org.apache.ignite.internal.util.typedef.F;
@@ -885,7 +885,7 @@ public class WalRecoveryTxLogicalRecordsTest extends GridCommonAbstractTest {
boolean foundTails = false;
for (GridDhtLocalPartition part : parts) {
- CacheFreeListImpl freeList = GridTestUtils.getFieldValue(part.dataStore(), "freeList");
+ CacheFreeList freeList = GridTestUtils.getFieldValue(part.dataStore(), "freeList");
if (freeList == null)
// Lazy store.
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/AbstractPageLockTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/AbstractPageLockTest.java
new file mode 100644
index 0000000..f5172c5
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/AbstractPageLockTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.IgniteInterruptedCheckedException;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import static org.junit.Assert.assertEquals;
+
+/** */
+public abstract class AbstractPageLockTest {
+ /** */
+ protected void doRunnable(int deep, Runnable r) {
+ for (int i = 0; i < deep; i++)
+ r.run();
+ }
+
+ /** */
+ protected void awaitRandom(int bound) {
+ try {
+ U.sleep(nextRandomWaitTimeout(bound));
+ }
+ catch (IgniteInterruptedCheckedException e) {
+ throw new IgniteException(e);
+ }
+ }
+
+ protected int nextRandomWaitTimeout(int bound) {
+ ThreadLocalRandom rnd = ThreadLocalRandom.current();
+
+ return rnd.nextInt(bound);
+ }
+
+ protected void checkNextOp(PageLockLogSnapshot lockLog, long nextOpPageId, long nextOp, int nextOpStructureId) {
+ assertEquals(nextOpStructureId, lockLog.nextOpStructureId);
+ assertEquals(nextOp, lockLog.nextOp);
+ assertEquals(nextOpPageId, lockLog.nextOpPageId);
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImplTest.java
new file mode 100644
index 0000000..6772eb3
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImplTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.lang.management.ManagementFactory;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static javax.management.MBeanServerInvocationHandler.newProxyInstance;
+
+/**
+ * {@link PageLockTrackerMXBean} test.
+ */
+public class PageLockTrackerMXBeanImplTest extends GridCommonAbstractTest {
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ super.beforeTest();
+
+ stopAllGrids();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ super.afterTest();
+
+ stopAllGrids();
+ }
+
+ /**
+ * Simple chekc that mxbean is registrated on node start.
+ *
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testSimple() throws Exception {
+ Ignite ig = startGrid();
+
+ PageLockTrackerMXBean pageLockTrackerMXBean = getPageLockTrackerMXBean(ig);
+
+ Assert.assertNotNull(pageLockTrackerMXBean);
+ }
+
+ /**
+ * @param ignite Ignite.
+ */
+ private PageLockTrackerMXBean getPageLockTrackerMXBean(Ignite ignite) throws Exception {
+ ObjectName mBeanName = U.makeMBeanName(
+ ignite.name(),
+ CacheDiagnosticManager.MBEAN_GROUP,
+ PageLockTrackerMXBean.MBEAN_NAME
+ );
+
+ MBeanServer mBeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+ if (!mBeanSrv.isRegistered(mBeanName))
+ fail("MBean is not registered: " + mBeanName.getCanonicalName());
+
+ return newProxyInstance(mBeanSrv, mBeanName, PageLockTrackerMXBean.class, true);
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManagerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManagerTest.java
new file mode 100644
index 0000000..fdc8379
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManagerTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.apache.ignite.internal.processors.cache.persistence.DataStructure;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.ListeningTestLogger;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+
+/**
+ *
+ */
+public class PageLockTrackerManagerTest {
+ /**
+ *
+ */
+ @Test
+ public void testDisableTracking(){
+ System.setProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE", String.valueOf(-1));
+
+ try {
+ PageLockTrackerManager mgr = new PageLockTrackerManager(new ListeningTestLogger());
+
+ PageLockListener pll = mgr.createPageLockTracker("test");
+
+ Assert.assertNotNull(pll);
+ Assert.assertSame(pll, DataStructure.NOOP_LSNR);
+
+ }finally {
+ System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE");
+ }
+
+ System.setProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE", String.valueOf(HEAP_LOG));
+
+ try {
+ PageLockTrackerManager mgr = new PageLockTrackerManager(new ListeningTestLogger());
+
+ PageLockListener pll = mgr.createPageLockTracker("test");
+
+ Assert.assertNotNull(pll);
+ Assert.assertNotSame(pll, DataStructure.NOOP_LSNR);
+
+ }finally {
+ System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE");
+ }
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testMemoryCalculation() throws Exception {
+ int[] trackerTypes = new int[] {HEAP_STACK, HEAP_LOG, OFF_HEAP_STACK, OFF_HEAP_LOG};
+
+ for (int type : trackerTypes)
+ testMemoryCalculation0(type);
+ }
+
+ /**
+ * @param type Tracker type.
+ */
+ public void testMemoryCalculation0(int type) throws Exception {
+ System.out.println(">>> Calculation mempory tracker type:" + type);
+
+ int timeOutWorkerInterval = 10_000;
+
+ System.setProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE", String.valueOf(type));
+ System.setProperty("IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL", String.valueOf(timeOutWorkerInterval));
+
+ try {
+ PageLockTrackerManager mgr = new PageLockTrackerManager(new ListeningTestLogger());
+
+ mgr.start();
+
+ printOverhead(mgr);
+
+ long heapOverhead0 = mgr.getHeapOverhead();
+ long offHeapOverhead0 = mgr.getOffHeapOverhead();
+ long totalOverhead0 = mgr.getTotalOverhead();
+
+ Assert.assertTrue(heapOverhead0 > 0);
+ Assert.assertTrue(offHeapOverhead0 >= 0);
+ Assert.assertEquals(heapOverhead0 + offHeapOverhead0, totalOverhead0);
+
+ PageLockListener pls = mgr.createPageLockTracker("test");
+
+ printOverhead(mgr);
+
+ long heapOverhead1 = mgr.getHeapOverhead();
+ long offHeapOverhead1 = mgr.getOffHeapOverhead();
+ long totalOverhead1 = mgr.getTotalOverhead();
+
+ Assert.assertTrue(heapOverhead1 > 0);
+ Assert.assertTrue(offHeapOverhead1 >= 0);
+ Assert.assertTrue(heapOverhead1 > heapOverhead0);
+ Assert.assertTrue(offHeapOverhead1 >= offHeapOverhead0);
+ Assert.assertEquals(heapOverhead1 + offHeapOverhead1, totalOverhead1);
+
+ int threads = 2_000;
+
+ int cacheId = 1;
+ long pageId = 2;
+ long page = 3;
+ long pageAdder = 4;
+
+ List<Thread> threadsList = new ArrayList<>(threads);
+
+ String threadNamePreffix = "my-thread-";
+
+ CountDownLatch startThreadsLatch = new CountDownLatch(threads);
+ CountDownLatch finishThreadsLatch = new CountDownLatch(1);
+
+ for (int i = 0; i < threads; i++) {
+ Thread th = new Thread(() -> {
+ startThreadsLatch.countDown();
+
+ pls.onBeforeReadLock(cacheId, pageId, page);
+
+ pls.onReadLock(cacheId, pageId, page, pageAdder);
+
+ pls.onReadUnlock(cacheId, pageId, page, pageAdder);
+
+ try {
+ finishThreadsLatch.await();
+ }
+ catch (InterruptedException ignored) {
+ // No-op.
+ }
+ });
+
+ th.setName(threadNamePreffix + i);
+
+ threadsList.add(th);
+
+ th.start();
+
+ System.out.println(">>> start thread:" + th.getName());
+ }
+
+ startThreadsLatch.await();
+
+ printOverhead(mgr);
+
+ long heapOverhead2 = mgr.getHeapOverhead();
+ long offHeapOverhead2 = mgr.getOffHeapOverhead();
+ long totalOverhead2 = mgr.getTotalOverhead();
+
+ Assert.assertTrue(heapOverhead2 > heapOverhead1);
+ Assert.assertTrue(offHeapOverhead2 >= offHeapOverhead1);
+ Assert.assertEquals(heapOverhead2 + offHeapOverhead2, totalOverhead2);
+
+ finishThreadsLatch.countDown();
+
+ threadsList.forEach(th -> {
+ try {
+ System.out.println(">>> await thread:" + th.getName());
+
+ th.join();
+ }
+ catch (InterruptedException ignored) {
+ // No-op.
+ }
+ });
+
+ // Await cleanup worker interval.
+ U.sleep(2 * timeOutWorkerInterval);
+
+ printOverhead(mgr);
+
+ long heapOverhead3 = mgr.getHeapOverhead();
+ long offHeapOverhead3 = mgr.getOffHeapOverhead();
+ long totalOverhead3 = mgr.getTotalOverhead();
+
+ Assert.assertTrue(heapOverhead3 > 0);
+ Assert.assertTrue(offHeapOverhead3 >= 0);
+ Assert.assertTrue(heapOverhead3 < heapOverhead2);
+ Assert.assertTrue(offHeapOverhead3 <= offHeapOverhead2);
+ Assert.assertEquals(heapOverhead3 + offHeapOverhead3, totalOverhead3);
+
+ mgr.stop();
+ }
+ finally {
+ System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE");
+ System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL");
+ }
+ }
+
+ private void printOverhead(PageLockTrackerManager mgr) {
+ System.out.println(
+ "Head:" + mgr.getHeapOverhead()
+ + ", OffHeap:" + mgr.getOffHeapOverhead()
+ + ", Total:" + mgr.getTotalOverhead());
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerTestSuit.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerTestSuit.java
new file mode 100644
index 0000000..78afb50
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerTestSuit.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessorTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.HeapArrayLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.OffHeapLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.HeapArrayLockStackTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.OffHeapLockStackTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Test suite for all tests ralated to {@link PageLockTracker}.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ PageLockTrackerManagerTest.class,
+ SharedPageLockTrackerTest.class,
+ ToFileDumpProcessorTest.class,
+ HeapArrayLockLogTest.class,
+ OffHeapLockLogTest.class,
+ HeapArrayLockStackTest.class,
+ OffHeapLockStackTest.class,
+})
+
+public class PageLockTrackerTestSuit {
+
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTrackerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTrackerTest.java
new file mode 100644
index 0000000..8611422
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTrackerTest.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class SharedPageLockTrackerTest extends AbstractPageLockTest {
+ /**
+ *
+ */
+ @Test
+ public void testTakeDumpByCount() throws Exception {
+ int[] trackerTypes = new int[] {HEAP_STACK, HEAP_LOG, OFF_HEAP_STACK, OFF_HEAP_LOG};
+
+ LockTrackerFactory.DEFAULT_CAPACITY = 512;
+
+ for (int i = 0; i < trackerTypes.length; i++) {
+ LockTrackerFactory.DEFAULT_TYPE = trackerTypes[i];
+
+ doTestTakeDumpByCount(5, 1, 10, 1);
+
+ doTestTakeDumpByCount(5, 2, 10, 2);
+
+ doTestTakeDumpByCount(10, 3, 20, 4);
+
+ doTestTakeDumpByCount(20, 6, 40, 8);
+ }
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testTakeDumpByTime() throws Exception {
+ int[] trackerTypes = new int[] {HEAP_STACK, HEAP_LOG, OFF_HEAP_STACK, OFF_HEAP_LOG};
+
+ LockTrackerFactory.DEFAULT_CAPACITY = 512;
+
+ for (int i = 0; i < trackerTypes.length; i++) {
+ LockTrackerFactory.DEFAULT_TYPE = trackerTypes[i];
+
+ doTestTakeDumpByTime(5, 1, 40_000, 1);
+
+ doTestTakeDumpByTime(5, 2, 20_000, 2);
+
+ doTestTakeDumpByTime(10, 3, 10_000, 4);
+
+ doTestTakeDumpByTime(20, 6, 10_000, 8);
+ }
+ }
+
+ /**
+ *
+ */
+ private void doTestTakeDumpByCount(
+ int pagesCnt,
+ int structuresCnt,
+ int dumpCnt,
+ int threads
+ ) throws IgniteCheckedException, InterruptedException {
+ SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker();
+
+ List<PageMeta> pageMetas = new CopyOnWriteArrayList<>();
+
+ int id = 1;
+
+ for (int i = 0; i < pagesCnt; i++)
+ pageMetas.add(new PageMeta((id++) % structuresCnt, id++, id++, id++));
+
+ List<PageLockListener> pageLsnrs = new ArrayList<>();
+
+ for (int i = 0; i < structuresCnt; i++)
+ pageLsnrs.add(sharedPageLockTracker.registrateStructure("my-structure-" + i));
+
+ AtomicBoolean stop = new AtomicBoolean();
+
+ CountDownLatch awaitThreadStartLatch = new CountDownLatch(threads);
+
+ IgniteInternalFuture f = GridTestUtils.runMultiThreadedAsync(() -> {
+ List<PageLockListener> locks = new ArrayList<>(pageLsnrs);
+ List<PageMeta> pages = new ArrayList<>();
+
+ pages.addAll(pageMetas);
+
+ while (!stop.get()) {
+ Collections.shuffle(locks);
+ Collections.shuffle(pages);
+
+ for (PageLockListener lsnr : locks) {
+ for (PageMeta pageMeta : pages) {
+ awaitRandom(50);
+
+ lsnr.onBeforeReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page);
+
+ awaitRandom(50);
+
+ lsnr.onReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+ }
+ }
+
+ awaitRandom(10);
+
+ Collections.reverse(locks);
+ Collections.reverse(pages);
+
+ for (PageLockListener lsnr : locks) {
+ for (PageMeta pageMeta : pages) {
+ awaitRandom(50);
+
+ lsnr.onReadUnlock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+ }
+ }
+
+ if (awaitThreadStartLatch.getCount() > 0)
+ awaitThreadStartLatch.countDown();
+ }
+ }, threads, "PageLocker");
+
+ awaitThreadStartLatch.await();
+
+ for (int i = 0; i < dumpCnt; i++) {
+ awaitRandom(1000);
+
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ System.out.println(toStringDump(dump));
+
+ assertEquals(threads, dump.threadStates.size());
+ assertEquals(0, dump.threadStates.stream().filter(e -> e.invalidContext != null).count());
+ }
+
+ stop.set(true);
+
+ f.get();
+ }
+
+ /**
+ *
+ */
+ private void doTestTakeDumpByTime(
+ int pagesCnt,
+ int structuresCnt,
+ int dumpTime,
+ int threads
+ ) throws IgniteCheckedException, InterruptedException {
+ SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker();
+
+ List<PageMeta> pageMetas = new CopyOnWriteArrayList<>();
+
+ int id = 1;
+
+ for (int i = 0; i < pagesCnt; i++)
+ pageMetas.add(new PageMeta((id++) % structuresCnt, id++, id++, id++));
+
+ List<PageLockListener> pageLsnrs = new ArrayList<>();
+
+ for (int i = 0; i < structuresCnt; i++)
+ pageLsnrs.add(sharedPageLockTracker.registrateStructure("my-structure-" + i));
+
+ AtomicBoolean stop = new AtomicBoolean();
+
+ CountDownLatch awaitThreadStartLatch = new CountDownLatch(threads);
+
+ IgniteInternalFuture f = GridTestUtils.runMultiThreadedAsync(() -> {
+ List<PageLockListener> locks = new ArrayList<>(pageLsnrs);
+ List<PageMeta> pages = new ArrayList<>();
+
+ pages.addAll(pageMetas);
+
+ while (!stop.get()) {
+ Collections.shuffle(locks);
+ Collections.shuffle(pages);
+
+ for (PageLockListener lsnr : locks) {
+ for (PageMeta pageMeta : pages) {
+ //awaitRandom(5);
+
+ lsnr.onBeforeReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page);
+
+ //awaitRandom(5);
+
+ lsnr.onReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+ }
+ }
+
+ Collections.reverse(locks);
+ Collections.reverse(pages);
+
+ for (PageLockListener lsnr : locks) {
+ for (PageMeta pageMeta : pages) {
+ // awaitRandom(5);
+
+ lsnr.onReadUnlock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+ }
+ }
+
+ if (awaitThreadStartLatch.getCount() > 0)
+ awaitThreadStartLatch.countDown();
+ }
+ }, threads, "PageLocker");
+
+ IgniteInternalFuture dumpFut = GridTestUtils.runAsync(() -> {
+ try {
+ awaitThreadStartLatch.await();
+ }
+ catch (InterruptedException e) {
+ // Ignore.
+ return;
+ }
+
+ while (!stop.get()) {
+ awaitRandom(20);
+
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ System.out.println(toStringDump(dump));
+
+ assertEquals(threads, dump.threadStates.size());
+ assertEquals(0, dump.threadStates.stream().filter(e -> e.invalidContext != null).count());
+ }
+ });
+
+ Thread.sleep(dumpTime);
+
+ stop.set(true);
+
+ f.get();
+
+ dumpFut.get();
+ }
+
+ /**
+ * Test for checking that internal maps is not leaked after threads stopped.
+ */
+ @Test
+ public void testMemoryLeakOnThreadTerminates() throws Exception {
+ int threadLimits = 1000;
+ int timeOutWorkerInterval = 10_000;
+ Consumer<Set<SharedPageLockTracker.State>> handler = (threads) -> {
+ };
+
+ SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker(
+ threadLimits, timeOutWorkerInterval, handler, new MemoryCalculator());
+
+ int threads = 10_000;
+
+ int cacheId = 1;
+ long pageId = 2;
+ long page = 3;
+ long pageAdder = 4;
+
+ PageLockListener lt = sharedPageLockTracker.registrateStructure("test");
+
+ List<Thread> threadsList = new ArrayList<>(threads);
+
+ String threadNamePreffix = "my-thread-";
+
+ for (int i = 0; i < threads; i++) {
+ Thread th = new Thread(() -> {
+ lt.onBeforeReadLock(cacheId, pageId, page);
+
+ lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+ lt.onReadUnlock(cacheId, pageId, page, pageAdder);
+ });
+
+ th.setName(threadNamePreffix + i);
+
+ threadsList.add(th);
+
+ th.start();
+
+ System.out.println(">>> start thread:" + th.getName());
+ }
+
+ threadsList.forEach(th -> {
+ try {
+ System.out.println(">>> await thread:" + th.getName());
+
+ th.join();
+ }
+ catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ });
+
+ sharedPageLockTracker.start();
+
+ ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+ assertTrue(dump.time > 0);
+ assertTrue(!dump.threadStates.isEmpty());
+
+ for (ThreadPageLocksDumpLock.ThreadState threadState : dump.threadStates) {
+ assertNull(threadState.invalidContext);
+ assertTrue(threadState.threadName.startsWith(threadNamePreffix));
+ assertSame(Thread.State.TERMINATED, threadState.state);
+
+ }
+
+ Assert.assertEquals(1, dump.structureIdToStrcutureName.size());
+
+ synchronized (sharedPageLockTracker) {
+ Map<Long, Thread> threadMap0 = U.field(sharedPageLockTracker, "threadIdToThreadRef");
+ Map<Long, ?> threadStacksMap0 = U.field(sharedPageLockTracker, "threadStacks");
+
+ // Stopped threads should remove from map after map limit reached.
+ assertTrue(threadMap0.size() <= threadLimits);
+ assertTrue(threadStacksMap0.size() <= threadLimits);
+ }
+
+ // Await cleanup worker interval.
+ U.sleep(timeOutWorkerInterval + 1000);
+
+ synchronized (sharedPageLockTracker) {
+ Map<Long, Thread> threadMap1 = U.field(sharedPageLockTracker, "threadIdToThreadRef");
+ Map<Long, ?> threadStacksMap1 = U.field(sharedPageLockTracker, "threadStacks");
+
+ // Cleanup worker should remove all stopped threads.
+ assertTrue(threadMap1.isEmpty());
+ assertTrue(threadStacksMap1.isEmpty());
+ }
+
+ ThreadPageLocksDumpLock dump1 = sharedPageLockTracker.dump();
+
+ assertTrue(dump1.time > 0);
+ assertTrue(dump1.threadStates.isEmpty());
+ }
+
+ /**
+ *
+ */
+ @Test
+ public void testAutoDetectHangThreads() throws Exception {
+ String thInWaitName = "threadInWait";
+ String thInRunnableName = "threadInRunnable";
+ String thInAwaitWithoutLocksName = "threadInAwaitWithoutLocks";
+
+ AtomicReference<Exception> error = new AtomicReference<>();
+
+ CountDownLatch awaitLatch = new CountDownLatch(1);
+
+ SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker(
+ 1000,
+ 10_000,
+ hangsThreads -> {
+ if (hangsThreads.isEmpty()) {
+ error.set(new Exception("No one thread is hangs."));
+
+ return;
+ }
+
+ // Checking threads.
+ for (SharedPageLockTracker.State state : hangsThreads) {
+ String name = state.thread.getName();
+
+ if (name.equals(thInAwaitWithoutLocksName)) {
+ error.set(new Exception("Thread without locks should not be here." + state));
+ continue;
+ }
+
+ if (name.equals(thInWaitName)) {
+ if (state.heldLockCnt == 0)
+ error.set(new Exception("Thread should hold lock." + state));
+
+ if (state.thread.getState() != Thread.State.WAITING)
+ error.set(new Exception("Thread should in WAITING state." + state));
+
+ continue;
+ }
+
+ if (name.equals(thInRunnableName)) {
+ if (state.heldLockCnt == 0)
+ error.set(new Exception("Thread should hold lock." + state));
+
+ if (state.thread.getState() != Thread.State.RUNNABLE)
+ error.set(new Exception("Thread should in RUNNABLE state." + state));
+
+ continue;
+ }
+ }
+
+ awaitLatch.countDown();
+ }, new MemoryCalculator()
+ );
+
+ int cacheId = 1;
+ long pageId = 2;
+ long page = 3;
+ long pageAdder = 4;
+
+ PageLockListener lt = sharedPageLockTracker.registrateStructure("test");
+
+ Thread thInWait = new Thread(() -> {
+ lt.onBeforeReadLock(cacheId, pageId, page);
+
+ lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+ try {
+ awaitLatch.await();
+ }
+ catch (InterruptedException ignored) {
+ // No-op.
+ }
+ });
+
+ thInWait.setName(thInWaitName);
+
+ Thread thInRunnable = new Thread(() -> {
+ lt.onBeforeReadLock(cacheId, pageId, page);
+
+ lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+ while (awaitLatch.getCount() > 0) {
+ // Busy wait. Can not park this thread, we should check running hangs too.
+ }
+ });
+
+ thInRunnable.setName(thInRunnableName);
+
+ Thread thInAwaitWithoutLocks = new Thread(() -> {
+ lt.onBeforeReadLock(cacheId, pageId, page);
+
+ lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+ lt.onReadUnlock(cacheId, pageId, page, pageAdder);
+
+ try {
+ awaitLatch.await();
+ }
+ catch (InterruptedException ignored) {
+ // No-op.
+ }
+ });
+
+ thInAwaitWithoutLocks.setName(thInAwaitWithoutLocksName);
+
+ sharedPageLockTracker.start();
+
+ thInWait.start();
+ thInRunnable.start();
+ thInAwaitWithoutLocks.start();
+
+ thInWait.join();
+ thInRunnable.join();
+ thInAwaitWithoutLocks.join();
+
+ if (error.get() != null)
+ throw error.get();
+ }
+
+ /**
+ *
+ */
+ private static class PageMeta {
+ /**
+ *
+ */
+ final int structureId;
+ /**
+ *
+ */
+ final long pageId;
+ /**
+ *
+ */
+ final long page;
+ /**
+ *
+ */
+ final long pageAddr;
+
+ /**
+ *
+ */
+ private PageMeta(
+ int structureId,
+ long pageId,
+ long page,
+ long pageAddr
+ ) {
+ this.structureId = structureId;
+ this.pageId = pageId;
+ this.page = page;
+ this.pageAddr = pageAddr;
+ }
+
+ @Override public String toString() {
+ return "PageMeta{" +
+ "structureId=" + structureId +
+ ", pageId=" + pageId +
+ ", page=" + page +
+ ", pageAddr=" + pageAddr +
+ '}';
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessorTest.java
new file mode 100644
index 0000000..696ab3b
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.nio.file.Paths.get;
+
+/**
+ *
+ */
+public class ToFileDumpProcessorTest {
+ /** */
+ private File file;
+
+ @Before
+ public void beforeTest() {
+ cleanFile();
+ }
+
+ @After
+ public void afterTest() {
+ // cleanFile();
+ }
+
+ /**
+ * Clean files.
+ */
+ public void cleanFile() {
+ if (file != null && file.exists())
+ file.delete();
+ }
+
+ /** */
+ @Test
+ public void toFileDump() throws Exception {
+ String igHome = U.defaultWorkDirectory();
+
+ System.out.println("IGNITE_HOME:" + igHome);
+
+ PageLockTracker pageLockTracker = LockTrackerFactory.create("test");
+
+ pageLockTracker.onBeforeReadLock(1, 2, 3);
+ pageLockTracker.onReadLock(1, 2, 3, 4);
+
+ PageLockDump pageLockDump = pageLockTracker.dump();
+
+ Assert.assertNotNull(pageLockDump);
+
+ String expDumpStr = ToStringDumpProcessor.toStringDump(pageLockDump);
+
+ String filePath = ToFileDumpProcessor.toFileDump(pageLockDump, file = new File(igHome), "test");
+
+ System.out.println("Dump saved:" + filePath);
+
+ boolean found = false;
+
+ for (File file : file.listFiles()) {
+ if (file.getAbsolutePath().equals(filePath)) {
+ found = true;
+
+ break;
+ }
+ }
+
+ Assert.assertTrue(found);
+
+ String actDumpStr;
+
+ try (FileChannel ch = FileChannel.open(get(filePath), StandardOpenOption.READ)) {
+ long size = ch.size();
+
+ ByteBuffer buf = ByteBuffer.allocate((int)size);
+
+ while (buf.position() != buf.capacity())
+ ch.read(buf);
+
+ actDumpStr = new String(buf.array());
+ }
+
+ Assert.assertEquals(expDumpStr, actDumpStr);
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/HeapArrayLockLogTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/HeapArrayLockLogTest.java
new file mode 100644
index 0000000..41fc802
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/HeapArrayLockLogTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+
+/** */
+public class HeapArrayLockLogTest extends PageLockLogTest {
+ /** {@inheritDoc} */
+ @Override protected LockLog createLogStackTracer(String name) {
+ return (LockLog)LockTrackerFactory.create(HEAP_LOG, name);
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/OffHeapLockLogTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/OffHeapLockLogTest.java
new file mode 100644
index 0000000..ef1aed4
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/OffHeapLockLogTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+
+/** */
+public class OffHeapLockLogTest extends PageLockLogTest {
+ /** {@inheritDoc} */
+ @Override protected LockLog createLogStackTracer(String name) {
+ return (LockLog)LockTrackerFactory.create(OFF_HEAP_LOG, name);
+ }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogTest.java
new file mode 100644
index 0000000..5075d5a
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogTest.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.AbstractPageLockTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot.LogEntry;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static java.lang.System.out;
+import static java.time.Duration.ofMinutes;
+import static java.util.stream.IntStream.range;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.DEFAULT_CAPACITY;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_UNLOCK;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/** */
+public abstract class PageLockLogTest extends AbstractPageLockTest {
+ /** */
+ protected static final int STRUCTURE_ID = 123;
+
+ /** */
+ protected abstract LockLog createLogStackTracer(String name);
+
+ /** */
+ private void checkLogEntry(LogEntry logEntry, long pageId, int operation, int structureId, int holdedLocks) {
+ assertEquals(pageId, logEntry.pageId);
+ assertEquals(operation, logEntry.operation);
+ assertEquals(structureId, logEntry.structureId);
+ assertEquals(holdedLocks, logEntry.holdedLocks);
+ }
+
+ /** */
+ @Test
+ public void testOneReadPageLock() {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ PageLockLogSnapshot logDump;
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, pageId, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, 0, 0, 0);
+ }
+
+ /** */
+ @Test
+ public void testTwoReadPageLock() {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long page1 = 2;
+ long page2 = 12;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+
+ PageLockLogSnapshot logDump;
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(2, logDump.locklog.size());
+
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(3, logDump.locklog.size());
+
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, 0, 0, 0);
+ }
+
+ /** */
+ @Test
+ public void testThreeReadPageLock_1() {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+
+ PageLockLogSnapshot logDump;
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(2, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(2, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, pageId3, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(3, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(4, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+ checkLogEntry(logDump.locklog.get(3), pageId3, READ_UNLOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(5, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+ checkLogEntry(logDump.locklog.get(3), pageId3, READ_UNLOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(4), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, 0, 0, 0);
+ }
+
+ /** */
+ @Test
+ public void testThreeReadPageLock_2() {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+
+ PageLockLogSnapshot logDump;
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(2, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(3, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(3, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, pageId3, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(4, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(3), pageId3, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(5, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(3), pageId3, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(4), pageId3, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, 0, 0, 0);
+ }
+
+ /** */
+ @Test
+ public void testThreeReadPageLock_3() {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+
+ PageLockLogSnapshot logDump;
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(1, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(2, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0, 0, 0);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(2, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, pageId3, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(3, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+ checkNextOp(logDump, 0,0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(4, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+ checkLogEntry(logDump.locklog.get(3), pageId2, READ_UNLOCK, STRUCTURE_ID, 2);
+ checkNextOp(logDump, 0,0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ out.println(lockLog);
+
+ logDump = lockLog.dump();
+
+ assertEquals(5, logDump.headIdx);
+ checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+ checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+ checkLogEntry(logDump.locklog.get(3), pageId2, READ_UNLOCK, STRUCTURE_ID, 2);
+ checkLogEntry(logDump.locklog.get(4), pageId3, READ_UNLOCK, STRUCTURE_ID, 1);
+ checkNextOp(logDump, 0,0, 0);
+
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ logDump = lockLog.dump();
+
+ out.println(logDump);
+
+ assertEquals(0, logDump.headIdx);
+ assertTrue(logDump.locklog.isEmpty());
+ checkNextOp(logDump, 0, 0, 0);
+ }
+
+ /** */
+ @Test
+ public void testLogOverFlow() {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ PageLockLogSnapshot log;
+
+ // Lock log should be invalid after this operation because we can get lock more that
+ // log capacity, +1 for overflow.
+ range(0, DEFAULT_CAPACITY + 1).forEach((i) -> {
+ lockLog.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+ });
+
+ out.println(lockLog);
+
+ log = lockLog.dump();
+
+ Assert.assertTrue(lockLog.isInvalid());
+
+ String msg = lockLog.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Log overflow"));
+ }
+
+ /** */
+ @Test
+ public void testThreadlog() throws IgniteCheckedException {
+ LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ int cntlogs = 5_000;
+
+ AtomicBoolean done = new AtomicBoolean();
+
+ int maxWaitTime = 500;
+
+ int maxdeep = 16;
+
+ IgniteInternalFuture f = GridTestUtils.runAsync(() -> {
+ while (!done.get()) {
+ int iter = nextRandomWaitTimeout(maxdeep);
+
+ doRunnable(iter, () -> {
+ awaitRandom(100);
+
+ lockLog.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+ awaitRandom(100);
+
+ lockLog.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+ });
+
+ try {
+ awaitRandom(maxWaitTime);
+ }
+ finally {
+ doRunnable(iter, () -> {
+ lockLog.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+ });
+ }
+ }
+ });
+
+ long totalExecutionTime = 0L;
+
+ for (int i = 0; i < cntlogs; i++) {
+ awaitRandom(50);
+
+ long time = System.nanoTime();
+
+ PageLockLogSnapshot logDump = lockLog.dump();
+
+ long logTime = System.nanoTime() - time;
+
+ if (logDump.nextOp != 0)
+ assertTrue(logDump.nextOpPageId != 0);
+
+ assertTrue(logDump.time != 0);
+ Assert.assertNotNull(logDump.name);
+
+ if (logDump.headIdx > 0) {
+ //TODO
+ /* for (int j = 0; j < log.headIdx; j++)
+ Assert.assertTrue(String.valueOf(log.headIdx), log.pageIdLocksStack[j] != 0);*/
+ }
+
+ Assert.assertNotNull(logDump);
+
+ totalExecutionTime += logTime;
+
+ assertTrue(logTime <= ofMinutes((long)(maxWaitTime + (maxWaitTime * 0.1))).toNanos());
+
+ if (i != 0 && i % 100 == 0)
+ out.println(">>> log:" + i);
+ }
+
+ done.set(true);
+
+ f.get();
+
+ out.println(">>> Avarage time log creation:" + (totalExecutionTime / cntlogs) + " ns");
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/HeapArrayLockStackTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/HeapArrayLockStackTest.java
new file mode 100644
index 0000000..942f251
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/HeapArrayLockStackTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+
+/** */
+public class HeapArrayLockStackTest extends PageLockStackTest {
+ /** {@inheritDoc} */
+ @Override protected LockStack createLockStackTracer(String name) {
+ return (LockStack)LockTrackerFactory.create(HEAP_STACK, name);
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/OffHeapLockStackTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/OffHeapLockStackTest.java
new file mode 100644
index 0000000..2f44643
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/OffHeapLockStackTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+
+/** */
+public class OffHeapLockStackTest extends PageLockStackTest {
+ /** {@inheritDoc} */
+ @Override protected LockStack createLockStackTracer(String name) {
+ return (LockStack)LockTrackerFactory.create(OFF_HEAP_STACK, name);
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackTest.java
new file mode 100644
index 0000000..d20d031
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackTest.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.AbstractPageLockTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static java.time.Duration.ofMinutes;
+import static java.util.stream.IntStream.range;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_READ_LOCK;
+
+/** */
+public abstract class PageLockStackTest extends AbstractPageLockTest {
+ /** */
+ protected static final int STRUCTURE_ID = 123;
+ /** */
+ protected abstract LockStack createLockStackTracer(String name);
+
+ /** */
+ @Test
+ public void testOneReadPageLock() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(String.valueOf(dump.pageIdLocksStack), dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(pageId, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testTwoReadPageLock() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long page1 = 2;
+ long page2 = 12;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(pageId1, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testThreeReadPageLock_1() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(pageId1, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(pageId3, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(3, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testThreeReadPageLock_2() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(pageId1, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId3, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testThreeReadPageLock_3() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(pageId1, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(2, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(pageId3, dump.nextOpPageId);
+ Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(3, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(3, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ dump = lockStack.dump();
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testUnlockUnexcpected() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ PageLockStackSnapshot dump;
+
+ // Lock stack should be invalid after this operation because we can not unlock page
+ // which was not locked.
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ dump = lockStack.dump();
+
+ Assert.assertTrue(lockStack.isInvalid());
+ String msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testUnlockUnexcpectedOnNotEmptyStack() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long page1 = 2;
+ long page2 = 12;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+ // Lock stack should be invalid after this operation because we can not unlock page
+ // which was not locked.
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+ dump = lockStack.dump();
+
+ Assert.assertTrue(lockStack.isInvalid());
+ String msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Can not find pageId in stack"));
+
+ Assert.assertEquals(1, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testUnlockUnexcpectedOnNotEmptyStackMultiLocks() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId1 = 1;
+ long pageId2 = 11;
+ long pageId3 = 111;
+ long pageId4 = 1111;
+ long page1 = 2;
+ long page2 = 12;
+ long page3 = 122;
+ long page4 = 1222;
+ long pageAddr1 = 3;
+ long pageAddr2 = 13;
+ long pageAddr3 = 133;
+ long pageAddr4 = 1333;
+
+ PageLockStackSnapshot dump;
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+ lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+ lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+ // Lock stack should be invalid after this operation because we can not unlock page
+ // which was not locked.
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId4, page4, pageAddr4);
+
+ dump = lockStack.dump();
+
+ Assert.assertTrue(lockStack.isInvalid());
+ String msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Can not find pageId in stack"));
+
+ Assert.assertEquals(3, dump.headIdx);
+ Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+ Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+ Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testStackOverFlow() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ PageLockStackSnapshot dump;
+
+ // Lock stack should be invalid after this operation because we can get lock more that
+ // stack capacity, +1 for overflow.
+ range(0, LockTrackerFactory.DEFAULT_CAPACITY + 1).forEach((i) -> {
+ lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+ });
+
+ dump = lockStack.dump();
+
+ Assert.assertTrue(lockStack.isInvalid());
+ Assert.assertTrue(lockStack.invalidContext().msg.contains("Stack overflow"));
+
+ Assert.assertEquals(LockTrackerFactory.DEFAULT_CAPACITY, dump.headIdx);
+ Assert.assertFalse(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testStackOperationAfterInvalid() {
+ LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ PageLockStackSnapshot dump;
+
+ // Lock stack should be invalid after this operation because we can not unlock page
+ // which was not locked.
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ dump = lockStack.dump();
+
+ Assert.assertTrue(lockStack.isInvalid());
+ String msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+ System.out.println(lockStack.invalidContext());
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+ Assert.assertTrue(lockStack.isInvalid());
+ msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ Assert.assertTrue(lockStack.isInvalid());
+ msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+ Assert.assertTrue(lockStack.isInvalid());
+ msg = lockStack.invalidContext().msg;
+
+ Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+ Assert.assertEquals(0, dump.headIdx);
+ Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+ Assert.assertEquals(0, dump.nextOpPageId);
+ Assert.assertEquals(0, dump.nextOp);
+ }
+
+ /** */
+ @Test
+ public void testThreadDump() throws IgniteCheckedException {
+ PageLockTracker<PageLockStackSnapshot> lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+ long pageId = 1;
+ long page = 2;
+ long pageAddr = 3;
+
+ int cntDumps = 5_000;
+
+ AtomicBoolean done = new AtomicBoolean();
+
+ int maxWaitTime = 500;
+
+ int maxdeep = 16;
+
+ IgniteInternalFuture f = GridTestUtils.runAsync(() -> {
+ while (!done.get()) {
+ int iter = nextRandomWaitTimeout(maxdeep);
+
+ doRunnable(iter, () -> {
+ awaitRandom(100);
+
+ lockStack.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+ awaitRandom(100);
+
+ lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+ });
+
+ try {
+ awaitRandom(maxWaitTime);
+ }
+ finally {
+ doRunnable(iter, () -> {
+ lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+ });
+ }
+ }
+ });
+
+ long totalExecutionTime = 0L;
+
+ for (int i = 0; i < cntDumps; i++) {
+ awaitRandom(50);
+
+ long time = System.nanoTime();
+
+ PageLockStackSnapshot dump = lockStack.dump();
+
+ long dumpTime = System.nanoTime() - time;
+
+ if (dump.nextOp != 0)
+ Assert.assertTrue(dump.nextOpPageId != 0);
+
+ Assert.assertTrue(dump.time != 0);
+ Assert.assertNotNull(dump.name);
+
+ if (dump.headIdx > 0) {
+ for (int itemIdx = 0; itemIdx < dump.headIdx; itemIdx++) {
+ Assert.assertTrue(String.valueOf(dump.headIdx), dump.pageIdLocksStack.getPageId(itemIdx) != 0);
+ Assert.assertTrue(dump.pageIdLocksStack.getOperation(itemIdx) != 0);
+ Assert.assertTrue(dump.pageIdLocksStack.getStructureId(itemIdx) != 0);
+ Assert.assertTrue(dump.pageIdLocksStack.getPageAddrHeader(itemIdx) != 0);
+ Assert.assertTrue(dump.pageIdLocksStack.getPageAddr(itemIdx) != 0);
+ }
+ }
+
+ Assert.assertNotNull(dump);
+
+ totalExecutionTime += dumpTime;
+
+ Assert.assertTrue(dumpTime <= ofMinutes((long)(maxWaitTime + (maxWaitTime * 0.1))).toNanos());
+
+ if (i != 0 && i % 100 == 0)
+ System.out.println(">>> Dump:" + i);
+ }
+
+ done.set(true);
+
+ f.get();
+
+ System.out.println(">>> Avarage time dump creation:" + (totalExecutionTime / cntDumps) + " ns");
+ }
+
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
index 43f51f7..7b81cfa 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.mem.DirectMemoryProvider;
import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider;
import org.apache.ignite.internal.pagemem.FullPageId;
import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
import org.apache.ignite.internal.processors.cache.persistence.CheckpointWriteProgressSupplier;
import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
@@ -80,7 +81,8 @@ public class BPlusTreePageMemoryImplTest extends BPlusTreeSelfTest {
null,
null,
null,
- null
+ null,
+ new CacheDiagnosticManager()
);
PageMemory mem = new PageMemoryImpl(
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
index 20d55eb..8b62314 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
@@ -80,6 +80,7 @@ public class BPlusTreeReuseListPageMemoryImplTest extends BPlusTreeReuseSelfTest
null,
null,
null,
+ null,
null
);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
index 529f723..0673623 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
@@ -25,6 +25,7 @@ import org.apache.ignite.internal.mem.DirectMemoryProvider;
import org.apache.ignite.internal.mem.file.MappedFileMemoryProvider;
import org.apache.ignite.internal.pagemem.FullPageId;
import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
import org.apache.ignite.internal.processors.cache.persistence.CheckpointWriteProgressSupplier;
import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
@@ -95,7 +96,8 @@ public class IndexStoragePageMemoryImplTest extends IndexStorageSelfTest {
null,
null,
null,
- null
+ null,
+ new CacheDiagnosticManager()
);
return new PageMemoryImpl(
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
index becdaeb..66261c4 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
@@ -89,6 +89,7 @@ public class PageMemoryImplNoLoadTest extends PageMemoryNoLoadSelfTest {
null,
null,
null,
+ null,
null
);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
index 20114f5..8e7fb35 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
@@ -315,6 +315,7 @@ public class PageMemoryImplTest extends GridCommonAbstractTest {
null,
null,
null,
+ null,
null
);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java
index 992d897..f581334 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import static org.apache.ignite.internal.pagemem.PageIdUtils.effectivePageId;
@@ -32,9 +33,20 @@ import static org.apache.ignite.internal.pagemem.PageIdUtils.effectivePageId;
*/
public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
/** {@inheritDoc} */
- @Override protected ReuseList createReuseList(int cacheId, PageMemory pageMem, long rootId, boolean initNew)
- throws IgniteCheckedException {
- return new TestReuseList(cacheId, "test", pageMem, null, rootId, initNew);
+ @Override protected ReuseList createReuseList(
+ int cacheId,
+ PageMemory pageMem,
+ long rootId,
+ boolean initNew
+ ) throws IgniteCheckedException {
+ return new TestReuseList(
+ cacheId,
+ "test",
+ pageMem,
+ null,
+ rootId,
+ initNew
+ );
}
/** {@inheritDoc} */
@@ -48,19 +60,6 @@ public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
*
*/
private static class TestReuseList extends ReuseListImpl {
- /** */
- private static ThreadLocal<Set<Long>> readLocks = new ThreadLocal<Set<Long>>() {
- @Override protected Set<Long> initialValue() {
- return new HashSet<>();
- }
- };
-
- /** */
- private static ThreadLocal<Set<Long>> writeLocks = new ThreadLocal<Set<Long>>() {
- @Override protected Set<Long> initialValue() {
- return new HashSet<>();
- }
- };
/**
* @param cacheId Cache ID.
@@ -79,9 +78,34 @@ public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
long metaPageId,
boolean initNew
) throws IgniteCheckedException {
- super(cacheId, name, pageMem, wal, metaPageId, initNew);
+ super(cacheId, name, pageMem, wal, metaPageId, initNew, new TestPageLockListener());
}
+ /**
+ *
+ */
+ static boolean checkNoLocks() {
+ return TestPageLockListener.readLocks.get().isEmpty() && TestPageLockListener.writeLocks.get().isEmpty();
+ }
+ }
+
+ /** */
+ private static class TestPageLockListener implements PageLockListener {
+
+ /** */
+ private static ThreadLocal<Set<Long>> readLocks = new ThreadLocal<Set<Long>>() {
+ @Override protected Set<Long> initialValue() {
+ return new HashSet<>();
+ }
+ };
+
+ /** */
+ private static ThreadLocal<Set<Long>> writeLocks = new ThreadLocal<Set<Long>>() {
+ @Override protected Set<Long> initialValue() {
+ return new HashSet<>();
+ }
+ };
+
/** {@inheritDoc} */
@Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
// No-op.
@@ -122,9 +146,5 @@ public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
assertTrue(writeLocks.get().remove(pageId));
}
-
- static boolean checkNoLocks() {
- return readLocks.get().isEmpty() && writeLocks.get().isEmpty();
- }
}
}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
index 14cc760..6ccf667 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
@@ -63,6 +63,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeaf
import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.util.GridConcurrentHashSet;
import org.apache.ignite.internal.util.GridRandom;
import org.apache.ignite.internal.util.GridStripedLock;
@@ -83,6 +84,7 @@ import org.junit.runners.JUnit4;
import static org.apache.ignite.internal.pagemem.PageIdUtils.effectivePageId;
import static org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree.rnd;
+import static org.apache.ignite.internal.processors.database.BPlusTreeSelfTest.TestTree.threadId;
import static org.apache.ignite.internal.util.IgniteTree.OperationType.NOOP;
import static org.apache.ignite.internal.util.IgniteTree.OperationType.PUT;
import static org.apache.ignite.internal.util.IgniteTree.OperationType.REMOVE;
@@ -143,7 +145,7 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
* Check that we do not keep any locks at the moment.
*/
protected void assertNoLocks() {
- assertTrue(TestTree.checkNoLocks());
+ assertTrue(TestPageLockListener.checkNoLocks());
}
/** {@inheritDoc} */
@@ -2733,7 +2735,8 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
* @throws IgniteCheckedException If failed.
*/
protected TestTree createTestTree(boolean canGetRow) throws IgniteCheckedException {
- TestTree tree = new TestTree(reuseList, canGetRow, CACHE_ID, pageMem, allocateMetaPage().pageId());
+ TestTree tree = new TestTree(
+ reuseList, canGetRow, CACHE_ID, pageMem, allocateMetaPage().pageId(), new TestPageLockListener());
assertEquals(0, tree.size());
assertEquals(0, tree.rootLevel());
@@ -2753,18 +2756,6 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
* Test tree.
*/
protected static class TestTree extends BPlusTree<Long, Long> {
- /** */
- private static ConcurrentMap<Object, Long> beforeReadLock = new ConcurrentHashMap<>();
-
- /** */
- private static ConcurrentMap<Object, Long> beforeWriteLock = new ConcurrentHashMap<>();
-
- /** */
- private static ConcurrentMap<Object, Map<Long, Long>> readLocks = new ConcurrentHashMap<>();
-
- /** */
- private static ConcurrentMap<Object, Map<Long, Long>> writeLocks = new ConcurrentHashMap<>();
-
/** Number of retries. */
private int numRetries = super.getLockRetries();
@@ -2776,10 +2767,27 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
* @param metaPageId Meta page ID.
* @throws IgniteCheckedException If failed.
*/
- public TestTree(ReuseList reuseList, boolean canGetRow, int cacheId, PageMemory pageMem, long metaPageId)
- throws IgniteCheckedException {
- super("test", cacheId, pageMem, null, new AtomicLong(), metaPageId, reuseList,
- new IOVersions<>(new LongInnerIO(canGetRow)), new IOVersions<>(new LongLeafIO()), null);
+ public TestTree(
+ ReuseList reuseList,
+ boolean canGetRow,
+ int cacheId,
+ PageMemory pageMem,
+ long metaPageId,
+ TestPageLockListener lsnr
+ ) throws IgniteCheckedException {
+ super(
+ "test",
+ cacheId,
+ pageMem,
+ null,
+ new AtomicLong(),
+ metaPageId,
+ reuseList,
+ new IOVersions<>(new LongInnerIO(canGetRow)),
+ new IOVersions<>(new LongLeafIO()),
+ null,
+ lsnr
+ );
PageIO.registerTest(latestInnerIO(), latestLeafIO());
@@ -2805,114 +2813,10 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
/**
* @return Thread ID.
*/
- private static Object threadId() {
+ static Object threadId() {
return Thread.currentThread().getId(); //.getName();
}
- /**
- * @param read Read or write locks.
- * @return Locks map.
- */
- private static Map<Long, Long> locks(boolean read) {
- ConcurrentMap<Object, Map<Long, Long>> m = read ? readLocks : writeLocks;
-
- Object thId = threadId();
-
- Map<Long, Long> locks = m.get(thId);
-
- if (locks == null) {
- locks = new ConcurrentLinkedHashMap<>();
-
- if (m.putIfAbsent(thId, locks) != null)
- locks = m.get(thId);
- }
-
- return locks;
- }
-
- /** {@inheritDoc} */
- @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
- if (PRINT_LOCKS)
- X.println(" onBeforeReadLock: " + U.hexLong(pageId));
-//
-// U.dumpStack();
-
- assertNull(beforeReadLock.put(threadId(), pageId));
- }
-
- /** {@inheritDoc} */
- @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
- if (PRINT_LOCKS)
- X.println(" onReadLock: " + U.hexLong(pageId));
-
- if (pageAddr != 0L) {
- long actual = PageIO.getPageId(pageAddr);
-
- checkPageId(pageId, pageAddr);
-
- assertNull(locks(true).put(pageId, actual));
- }
-
- assertEquals(Long.valueOf(pageId), beforeReadLock.remove(threadId()));
- }
-
- /** {@inheritDoc} */
- @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
- if (PRINT_LOCKS)
- X.println(" onReadUnlock: " + U.hexLong(pageId));
-
- checkPageId(pageId, pageAddr);
-
- long actual = PageIO.getPageId(pageAddr);
-
- assertEquals(Long.valueOf(actual), locks(true).remove(pageId));
- }
-
- /** {@inheritDoc} */
- @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
- if (PRINT_LOCKS)
- X.println(" onBeforeWriteLock: " + U.hexLong(pageId));
-
- assertNull(beforeWriteLock.put(threadId(), pageId));
- }
-
- /** {@inheritDoc} */
- @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
- if (PRINT_LOCKS)
- X.println(" onWriteLock: " + U.hexLong(pageId));
-//
-// U.dumpStack();
-
- if (pageAddr != 0L) {
- checkPageId(pageId, pageAddr);
-
- long actual = PageIO.getPageId(pageAddr);
-
- if (actual == 0L)
- actual = pageId; // It is a newly allocated page.
-
- assertNull(locks(false).put(pageId, actual));
- }
-
- assertEquals(Long.valueOf(pageId), beforeWriteLock.remove(threadId()));
- }
-
- /** {@inheritDoc} */
- @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
- if (PRINT_LOCKS)
- X.println(" onWriteUnlock: " + U.hexLong(pageId));
-
- assertEquals(effectivePageId(pageId), effectivePageId(PageIO.getPageId(pageAddr)));
-
- assertEquals(Long.valueOf(pageId), locks(false).remove(pageId));
- }
-
- /**
- * @return {@code true} If current thread does not keep any locks.
- */
- static boolean checkNoLocks() {
- return locks(true).isEmpty() && locks(false).isEmpty();
- }
/**
* @param b String builder.
@@ -2952,11 +2856,11 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
b.a("\n--------read---------\n");
- printLocks(b, readLocks, beforeReadLock);
+ printLocks(b, TestPageLockListener.readLocks, TestPageLockListener.beforeReadLock);
b.a("\n-+------write---------\n");
- printLocks(b, writeLocks, beforeWriteLock);
+ printLocks(b, TestPageLockListener.writeLocks, TestPageLockListener.beforeWriteLock);
return b.toString();
}
@@ -3152,4 +3056,127 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
return vals.contains(val);
}
}
+
+ private static class TestPageLockListener implements PageLockListener {
+ /** */
+ static ConcurrentMap<Object, Long> beforeReadLock = new ConcurrentHashMap<>();
+
+ /** */
+ static ConcurrentMap<Object, Long> beforeWriteLock = new ConcurrentHashMap<>();
+
+ /** */
+ static ConcurrentMap<Object, Map<Long, Long>> readLocks = new ConcurrentHashMap<>();
+
+ /** */
+ static ConcurrentMap<Object, Map<Long, Long>> writeLocks = new ConcurrentHashMap<>();
+
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+ if (PRINT_LOCKS)
+ X.println(" onBeforeReadLock: " + U.hexLong(pageId));
+//
+// U.dumpStack();
+
+ assertNull(beforeReadLock.put(threadId(), pageId));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+ if (PRINT_LOCKS)
+ X.println(" onReadLock: " + U.hexLong(pageId));
+
+ if (pageAddr != 0L) {
+ long actual = PageIO.getPageId(pageAddr);
+
+ checkPageId(pageId, pageAddr);
+
+ assertNull(locks(true).put(pageId, actual));
+ }
+
+ assertEquals(Long.valueOf(pageId), beforeReadLock.remove(threadId()));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ if (PRINT_LOCKS)
+ X.println(" onReadUnlock: " + U.hexLong(pageId));
+
+ checkPageId(pageId, pageAddr);
+
+ long actual = PageIO.getPageId(pageAddr);
+
+ assertEquals(Long.valueOf(actual), locks(true).remove(pageId));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+ if (PRINT_LOCKS)
+ X.println(" onBeforeWriteLock: " + U.hexLong(pageId));
+
+ assertNull(beforeWriteLock.put(threadId(), pageId));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+ if (PRINT_LOCKS)
+ X.println(" onWriteLock: " + U.hexLong(pageId));
+//
+// U.dumpStack();
+
+ if (pageAddr != 0L) {
+ checkPageId(pageId, pageAddr);
+
+ long actual = PageIO.getPageId(pageAddr);
+
+ if (actual == 0L)
+ actual = pageId; // It is a newly allocated page.
+
+ assertNull(locks(false).put(pageId, actual));
+ }
+
+ assertEquals(Long.valueOf(pageId), beforeWriteLock.remove(threadId()));
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+ if (PRINT_LOCKS)
+ X.println(" onWriteUnlock: " + U.hexLong(pageId));
+
+ assertEquals(effectivePageId(pageId), effectivePageId(PageIO.getPageId(pageAddr)));
+
+ assertEquals(Long.valueOf(pageId), locks(false).remove(pageId));
+ }
+
+ /**
+ * @param read Read or write locks.
+ * @return Locks map.
+ */
+ private static Map<Long, Long> locks(boolean read) {
+ ConcurrentMap<Object, Map<Long, Long>> m = read ? readLocks : writeLocks;
+
+ Object thId = threadId();
+
+ Map<Long, Long> locks = m.get(thId);
+
+ if (locks == null) {
+ locks = new ConcurrentLinkedHashMap<>();
+
+ if (m.putIfAbsent(thId, locks) != null)
+ locks = m.get(thId);
+ }
+
+ return locks;
+ }
+
+
+
+ /**
+ * @return {@code true} If current thread does not keep any locks.
+ */
+ static boolean checkNoLocks() {
+ return locks(true).isEmpty() && locks(false).isEmpty();
+ }
+
+ }
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
similarity index 98%
rename from modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
rename to modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
index a852321..d1e905c 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -42,7 +42,7 @@ import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
import org.apache.ignite.internal.processors.cache.persistence.evict.NoOpPageEvictionTracker;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.CacheVersionIO;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -59,7 +59,7 @@ import org.junit.runners.JUnit4;
*
*/
@RunWith(JUnit4.class)
-public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
+public class CacheFreeListSelfTest extends GridCommonAbstractTest {
/** */
private static final int CPUS = Runtime.getRuntime().availableProcessors();
@@ -361,7 +361,17 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
DataRegion dataRegion = new DataRegion(pageMem, plcCfg, regionMetrics, new NoOpPageEvictionTracker());
- return new CacheFreeListImpl(1, "freelist", regionMetrics, dataRegion, null, null, metaPageId, true);
+ return new CacheFreeList(
+ 1,
+ "freelist",
+ regionMetrics,
+ dataRegion,
+ null,
+ null,
+ metaPageId,
+ true,
+ null
+ );
}
/**
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
index b2e8e82..309633ba 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
@@ -111,6 +111,7 @@ public class IndexStorageSelfTest extends GridCommonAbstractTest {
null,
mem.allocatePage(cacheId, PageIdAllocator.INDEX_PARTITION, PageMemory.FLAG_IDX),
true,
+ null,
null
);
diff --git a/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java b/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java
index e9edff1..46811bd 100644
--- a/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java
+++ b/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java
@@ -1,12 +1,12 @@
/*
* Copyright 2019 GridGain Systems, Inc. and Contributors.
- *
+ *
* Licensed under the GridGain Community Edition License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- *
+ *
* 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.
@@ -21,6 +21,7 @@ import org.apache.ignite.cache.store.CacheStore;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.CacheAffinitySharedManager;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
import org.apache.ignite.internal.processors.cache.CacheOsConflictResolutionManager;
import org.apache.ignite.internal.processors.cache.CacheType;
import org.apache.ignite.internal.processors.cache.GridCacheAffinityManager;
@@ -83,7 +84,8 @@ public class GridCacheTestContext<K, V> extends GridCacheContext<K, V> {
new CacheNoopJtaManager(),
null,
null,
- null
+ null,
+ new CacheDiagnosticManager()
),
defaultCacheConfiguration(),
null,
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index 3d1ae50..e707f13 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -71,7 +71,7 @@ import org.apache.ignite.internal.processors.continuous.GridMessageListenSelfTes
import org.apache.ignite.internal.processors.database.BPlusTreeFakeReuseSelfTest;
import org.apache.ignite.internal.processors.database.BPlusTreeReuseSelfTest;
import org.apache.ignite.internal.processors.database.BPlusTreeSelfTest;
-import org.apache.ignite.internal.processors.database.CacheFreeListImplSelfTest;
+import org.apache.ignite.internal.processors.database.CacheFreeListSelfTest;
import org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest;
import org.apache.ignite.internal.processors.database.IndexStorageSelfTest;
import org.apache.ignite.internal.processors.database.SwapPathConstructionSelfTest;
@@ -202,7 +202,7 @@ public class IgniteBasicTestSuite {
suite.addTest(new JUnit4TestAdapter(BPlusTreeFakeReuseSelfTest.class));
suite.addTest(new JUnit4TestAdapter(BPlusTreeReuseSelfTest.class));
suite.addTest(new JUnit4TestAdapter(IndexStorageSelfTest.class));
- suite.addTest(new JUnit4TestAdapter(CacheFreeListImplSelfTest.class));
+ suite.addTest(new JUnit4TestAdapter(CacheFreeListSelfTest.class));
suite.addTest(new JUnit4TestAdapter(DataRegionMetricsSelfTest.class));
suite.addTest(new JUnit4TestAdapter(SwapPathConstructionSelfTest.class));
suite.addTest(new JUnit4TestAdapter(BitSetIntSetTest.class));
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java
index c199c3c..92f1202 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java
@@ -31,6 +31,13 @@ import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsPageE
import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsPartitionPreloadTest;
import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsStartWIthEmptyArchive;
import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsTransactionsHangTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManagerTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.SharedPageLockTrackerTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessorTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.HeapArrayLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.OffHeapLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.HeapArrayLockStackTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.OffHeapLockStackTest;
import org.apache.ignite.internal.processors.cache.persistence.file.FileDownloaderTest;
import org.apache.ignite.testframework.GridTestUtils;
import org.junit.runner.RunWith;
@@ -68,6 +75,15 @@ public class IgnitePdsTestSuite4 {
GridTestUtils.addTestIfNeeded(suite, IgnitePdsStartWIthEmptyArchive.class, ignoredTests);
GridTestUtils.addTestIfNeeded(suite, CorruptedTreeFailureHandlingTest.class, ignoredTests);
+ // Page lock tracker tests.
+ GridTestUtils.addTestIfNeeded(suite, PageLockTrackerManagerTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite, SharedPageLockTrackerTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite, ToFileDumpProcessorTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite, HeapArrayLockLogTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite, HeapArrayLockStackTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite, OffHeapLockLogTest.class, ignoredTests);
+ GridTestUtils.addTestIfNeeded(suite, OffHeapLockStackTest.class, ignoredTests);
+
return suite;
}
diff --git a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
index 1778498..e97f82d 100644
--- a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
@@ -1066,7 +1066,7 @@ public class GridCommandHandlerTest extends GridCommandHandlerAbstractTest {
for (CacheSubcommands cmd : CacheSubcommands.values()) {
Class<? extends Enum<? extends CommandArg>> args = cmd.getCommandArgs();
- if(args != null)
+ if (args != null)
for (Enum<? extends CommandArg> arg : args.getEnumConstants())
assertTrue(arg.toString(), p.matcher(arg.toString()).matches());
}
@@ -2454,9 +2454,53 @@ public class GridCommandHandlerTest extends GridCommandHandlerAbstractTest {
}
/**
- * Starts several long transactions in order to test --tx command.
- * Transactions will last until unlock latch is released: first transaction will wait for unlock latch directly,
- * some others will wait for key lock acquisition.
+ * Test execution of --diagnostic command.
+ *
+ * @throws Exception if failed.
+ */
+ @Test
+ public void testDiagnosticPageLocksTracker() throws Exception {
+ Ignite ignite = startGrids(4);
+
+ Collection<ClusterNode> nodes = ignite.cluster().nodes();
+
+ List<ClusterNode> nodes0 = new ArrayList<>(nodes);
+
+ ClusterNode node0 = nodes0.get(0);
+ ClusterNode node1 = nodes0.get(1);
+ ClusterNode node2 = nodes0.get(2);
+ ClusterNode node3 = nodes0.get(3);
+
+ ignite.cluster().active(true);
+
+ String dir = U.defaultWorkDirectory() + "/diagnostic/";
+
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic"));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "help"));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "help"));
+
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump"));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump_log"));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", dir));
+
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", "--all"));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump_log", "--all"));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", dir, "--all"));
+
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", "--nodes",
+ node0.id().toString(), node2.id().toString()));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", "--nodes",
+ node0.consistentId().toString(), node2.consistentId().toString()));
+
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump_log", "--nodes",
+ node1.id().toString(), node3.id().toString()));
+ assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", dir, "--nodes",
+ node1.consistentId().toString(), node3.consistentId().toString()));
+ }
+
+ /**
+ * Starts several long transactions in order to test --tx command. Transactions will last until unlock latch is
+ * released: first transaction will wait for unlock latch directly, some others will wait for key lock acquisition.
*
* @param lockLatch Lock latch. Will be released inside body of the first transaction.
* @param unlockLatch Unlock latch. Should be released externally. First transaction won't be finished until unlock
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 15e8993..b90093c 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -79,6 +79,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusMetaIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
import org.apache.ignite.internal.processors.cache.query.GridCacheQueryMarshallable;
import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
@@ -3204,6 +3205,11 @@ public class IgniteH2Indexing implements GridQueryIndexing {
int inlineSize = getInlineSize(page, grpId, pageMemory);
+ String grpName = ctx.cache().cacheGroup(grpId).cacheOrGroupName();
+
+ PageLockListener lockLsnr = ctx.cache().context().diagnostic()
+ .pageLockTracker().createPageLockTracker(grpName + "IndexTree##" + indexName);
+
BPlusTree<GridH2SearchRow, GridH2Row> tree = new BPlusTree<GridH2SearchRow, GridH2Row>(
indexName,
grpId,
@@ -3214,7 +3220,8 @@ public class IgniteH2Indexing implements GridQueryIndexing {
reuseList,
H2ExtrasInnerIO.getVersions(inlineSize, mvccEnabled),
H2ExtrasLeafIO.getVersions(inlineSize, mvccEnabled),
- ctx.failure()
+ ctx.failure(),
+ lockLsnr
) {
@Override protected int compare(BPlusIO io, long pageAddr, int idx, GridH2SearchRow row) {
throw new AssertionError();
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
index d41b9b3..cd3619e 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
@@ -156,7 +156,17 @@ public abstract class H2Tree extends BPlusTree<GridH2SearchRow, GridH2Row> {
@Nullable FailureProcessor failureProcessor,
IgniteLogger log
) throws IgniteCheckedException {
- super(name, grpId, pageMem, wal, globalRmvId, metaPageId, reuseList, failureProcessor);
+ super(
+ name,
+ grpId,
+ pageMem,
+ wal,
+ globalRmvId,
+ metaPageId,
+ reuseList,
+ failureProcessor,
+ null
+ );
if (!initNew) {
// Page is ready - read inline size from it.