This is an automated email from the ASF dual-hosted git repository.
ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 7fc6ae85ae8 IGNITE-28418 Add more diagnostic to page lock timeouts
(#7910)
7fc6ae85ae8 is described below
commit 7fc6ae85ae8fd3a6ca977220ca21ed598e46e591
Author: Ivan Bessonov <[email protected]>
AuthorDate: Mon Apr 6 15:54:07 2026 +0300
IGNITE-28418 Add more diagnostic to page lock timeouts (#7910)
Signed-off-by: ibessonov <[email protected]>
---
.../apache/ignite/internal/thread/ThreadUtils.java | 37 +++++++++++-
.../ignite/internal/util/OffheapReadWriteLock.java | 66 ++++++++++++++++----
.../pagememory/inmemory/VolatilePageMemory.java | 10 ++--
.../pagememory/persistence/PageHeader.java | 70 +++++++++++-----------
4 files changed, 130 insertions(+), 53 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/thread/ThreadUtils.java
b/modules/core/src/main/java/org/apache/ignite/internal/thread/ThreadUtils.java
index f64f479a0dd..1f87802e466 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/thread/ThreadUtils.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/thread/ThreadUtils.java
@@ -53,6 +53,38 @@ public class ThreadUtils {
/** System line separator. */
private static final String NL = System.lineSeparator();
+ /**
+ * Performs thread dump and prints all available info to the given log
with {@code WARN} or {@code ERROR} logging level depending on
+ * {@code isErrorLevel} parameter. If there's no thread with a given ID,
or ID is invalid, then nothing is printed.
+ *
+ * @param log Logger.
+ * @param threadId ID of a thread to dump.
+ * @param isErrorLevel {@code true} if thread dump must be printed with
{@code ERROR} logging level, {@code false} if thread dump must
+ * be printed with {@code WARN} logging level.
+ */
+ public static void dumpThread(IgniteLogger log, long threadId, boolean
isErrorLevel) {
+ if (threadId <= 0) {
+ return;
+ }
+
+ // We don't really need a full stack, 64 as a default should be enough
for debugging.
+ int maxStackElements = 64;
+
+ ThreadInfo info =
ManagementFactory.getThreadMXBean().getThreadInfo(threadId, maxStackElements);
+ if (info == null) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder(THREAD_DUMP_MSG)
+
.append(THREAD_DUMP_FMT.format(Instant.ofEpochMilli(System.currentTimeMillis())))
+ .append(NL);
+
+ printThreadInfo(info, sb, Set.of());
+ sb.append(NL);
+
+ logMessage(log, sb.toString(), isErrorLevel);
+ }
+
/**
* Performs thread dump and prints all available info to the given log
* with WARN or ERROR logging level depending on {@code isErrorLevel}
parameter.
@@ -91,8 +123,9 @@ public class ThreadUtils {
sb.append(NL);
- if (info.getLockedSynchronizers() != null &&
info.getLockedSynchronizers().length > 0) {
- printSynchronizersInfo(info.getLockedSynchronizers(), sb);
+ LockInfo[] lockedSynchronizers = info.getLockedSynchronizers();
+ if (lockedSynchronizers != null && lockedSynchronizers.length > 0)
{
+ printSynchronizersInfo(lockedSynchronizers, sb);
sb.append(NL);
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/util/OffheapReadWriteLock.java
b/modules/core/src/main/java/org/apache/ignite/internal/util/OffheapReadWriteLock.java
index b4f12764c97..24e799ec912 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/util/OffheapReadWriteLock.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/util/OffheapReadWriteLock.java
@@ -25,20 +25,25 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.ignite.internal.lang.IgniteSystemProperties;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
+import org.apache.ignite.internal.thread.ThreadUtils;
import org.apache.ignite.internal.tostring.S;
import org.jetbrains.annotations.Nullable;
/**
* Lock state structure is as follows.
* <pre>
- * +----------------+---------------+---------+----------+
- * | WRITE WAIT CNT | READ WAIT CNT | TAG | LOCK CNT |
- * +----------------+---------------+---------+----------+
- * | 2 bytes | 2 bytes | 2 bytes | 2 bytes |
- * +----------------+---------------+---------+----------+
+ * +----------------+---------------+---------+----------+----------+
+ * | WRITE WAIT CNT | READ WAIT CNT | TAG | LOCK CNT | OWNER ID |
+ * +----------------+---------------+---------+----------+----------+
+ * | 2 bytes | 2 bytes | 2 bytes | 2 bytes | 8 bytes |
+ * +----------------+---------------+---------+----------+----------+
* </pre>
*/
public class OffheapReadWriteLock {
+ private final IgniteLogger log =
Loggers.forClass(OffheapReadWriteLock.class);
+
/** Default concurrency level for the lock. */
public static final int DEFAULT_CONCURRENCY_LEVEL = 128;
@@ -59,10 +64,16 @@ public class OffheapReadWriteLock {
public static final int TAG_LOCK_ALWAYS = -1;
/** Lock size. */
- public static final int LOCK_SIZE = 8;
+ public static final int LOCK_SIZE = Long.BYTES * 2;
+
+ /** Offset to the thread ID of a thread that currently holds write lock. */
+ private static final int OWNER_ID_OFFSET = Long.BYTES;
+
+ /** Placeholder value for when no one holds a write lock. See {@link
#OWNER_ID_OFFSET}. */
+ private static final long NO_OWNER_ID = -1L;
/** Maximum number of waiting threads, read or write. */
- public static final int MAX_WAITERS = 0xFFFF;
+ private static final int MAX_WAITERS = 0xFFFF;
/** Striped locks array. */
private final ReentrantLock[] locks;
@@ -142,6 +153,7 @@ public class OffheapReadWriteLock {
assert tag != 0;
GridUnsafe.putLong(lock, (long) tag << 16);
+ GridUnsafe.putLong(lock + OWNER_ID_OFFSET, NO_OWNER_ID);
}
/**
@@ -241,8 +253,14 @@ public class OffheapReadWriteLock {
public boolean tryWriteLock(long lock, int tag) {
long state = GridUnsafe.getLongVolatile(null, lock);
- return checkTag(state, tag) && canWriteLock(state)
+ boolean success = checkTag(state, tag) && canWriteLock(state)
&& GridUnsafe.compareAndSwapLong(null, lock, state,
updateState(state, -1, 0, 0));
+
+ if (success) {
+ setOwnerId(lock);
+ }
+
+ return success;
}
/**
@@ -265,6 +283,8 @@ public class OffheapReadWriteLock {
if (canWriteLock(state)) {
if (GridUnsafe.compareAndSwapLong(null, lock, state,
updateState(state, -1, 0, 0))) {
+ setOwnerId(lock);
+
return true;
} else {
// Retry CAS, do not count as spin cycle.
@@ -321,11 +341,14 @@ public class OffheapReadWriteLock {
while (true) {
long state = GridUnsafe.getLongVolatile(null, lock);
- if (lockCount(state) != -1) {
+ long ownerId = getOwnerId(lock);
+ if (lockCount(state) != -1 || ownerId != NO_OWNER_ID && ownerId !=
Thread.currentThread().getId()) {
throw new IllegalMonitorStateException("Attempted to release
write lock while not holding it "
+ "[lock=" + hexLong(lock) + ", state=" +
hexLong(state) + ']');
}
+ clearOwnerId(lock);
+
updated = releaseWithTag(state, tag);
assert updated != 0;
@@ -399,6 +422,8 @@ public class OffheapReadWriteLock {
if (lockCount(state) == 1) {
if (GridUnsafe.compareAndSwapLong(null, lock, state,
updateState(state, -2, 0, 0))) {
+ setOwnerId(lock);
+
return true;
} else {
// Retry CAS, do not count as spin cycle.
@@ -424,6 +449,8 @@ public class OffheapReadWriteLock {
if (lockCount(state) == 1) {
if (GridUnsafe.compareAndSwapLong(null, lock, state,
updateState(state, -2, 0, 0))) {
+ setOwnerId(lock);
+
return true;
} else {
continue;
@@ -520,6 +547,8 @@ public class OffheapReadWriteLock {
long updated = updateState(state, -1, 0, -1);
if (GridUnsafe.compareAndSwapLong(null, lock, state,
updated)) {
+ setOwnerId(lock);
+
return true;
}
} else {
@@ -560,6 +589,10 @@ public class OffheapReadWriteLock {
long passedNanos = System.nanoTime() - startTimeNanos;
if (passedNanos >= timeoutNanos) {
+ long ownerId = getOwnerId(lock);
+
+ ThreadUtils.dumpThread(log, ownerId, true);
+
//noinspection InfiniteLoopStatement
while (true) {
long state = GridUnsafe.getLongVolatile(null, lock);
@@ -571,7 +604,8 @@ public class OffheapReadWriteLock {
"tag", hexInt(tag), false,
"idx", lockIdx, false,
"cond", waitCond.toString(), false,
- "timeout",
TimeUnit.NANOSECONDS.toMillis(timeoutNanos) + "ms", false
+ "timeout",
TimeUnit.NANOSECONDS.toMillis(timeoutNanos) + "ms", false,
+ "ownerId", ownerId, false
));
}
}
@@ -772,4 +806,16 @@ public class OffheapReadWriteLock {
}
}
}
+
+ private static void setOwnerId(long lock) {
+ GridUnsafe.putLongVolatile(null, lock + OWNER_ID_OFFSET,
Thread.currentThread().getId());
+ }
+
+ private static void clearOwnerId(long lock) {
+ GridUnsafe.putLongVolatile(null, lock + OWNER_ID_OFFSET, NO_OWNER_ID);
+ }
+
+ private static long getOwnerId(long lock) {
+ return GridUnsafe.getLongVolatile(null, lock + OWNER_ID_OFFSET);
+ }
}
diff --git
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/inmemory/VolatilePageMemory.java
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/inmemory/VolatilePageMemory.java
index d765747a24f..ae31037f514 100644
---
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/inmemory/VolatilePageMemory.java
+++
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/inmemory/VolatilePageMemory.java
@@ -58,11 +58,11 @@ import org.apache.ignite.internal.util.StringUtils;
* <p/>
* When page is allocated and is in use:
* <pre>
- * +--------+--------+--------+--------+---------------------------+
- * |8 bytes |8 bytes |8 bytes |8 bytes | PAGE_SIZE |
- * +--------+--------+--------+--------+---------------------------+
- * | Marker |Page ID |Pin CNT | Lock | Page data |
- * +--------+--------+--------+--------+---------------------------+
+ * +--------+--------+---------+---------------------------+
+ * |8 bytes |8 bytes |16 bytes | PAGE_SIZE |
+ * +--------+--------+---------+---------------------------+
+ * | Marker |Page ID | Lock | Page data |
+ * +--------+--------+---------+---------------------------+
* </pre>
*
* <p>Note that first 8 bytes of page header are used either for page marker
or for next relative pointer depending
diff --git
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageHeader.java
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageHeader.java
index 36ba0a6996f..42c520d8b5f 100644
---
a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageHeader.java
+++
b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/persistence/PageHeader.java
@@ -18,17 +18,19 @@
package org.apache.ignite.internal.pagememory.persistence;
import static
org.apache.ignite.internal.pagememory.persistence.PersistentPageMemory.INVALID_REL_PTR;
+import static org.apache.ignite.internal.util.GridUnsafe.compareAndSwapInt;
import static org.apache.ignite.internal.util.GridUnsafe.decrementAndGetInt;
import static org.apache.ignite.internal.util.GridUnsafe.getInt;
import static org.apache.ignite.internal.util.GridUnsafe.getIntVolatile;
import static org.apache.ignite.internal.util.GridUnsafe.getLong;
import static org.apache.ignite.internal.util.GridUnsafe.putInt;
-import static org.apache.ignite.internal.util.GridUnsafe.putIntVolatile;
import static org.apache.ignite.internal.util.GridUnsafe.putLong;
import static org.apache.ignite.internal.util.GridUnsafe.putLongVolatile;
+import static org.apache.ignite.internal.util.IgniteUtils.isPow2;
import org.apache.ignite.internal.pagememory.FullPageId;
import org.apache.ignite.internal.util.GridUnsafe;
+import org.apache.ignite.internal.util.OffheapReadWriteLock;
/**
* Helper class for working with the page header that is stored in memory for
{@link PersistentPageMemory}.
@@ -36,7 +38,7 @@ import org.apache.ignite.internal.util.GridUnsafe;
* <p>Page header has the following structure:</p>
* <pre>
*
+-----------------+---------------------+--------+--------+---------+----------+----------+----------------------+
- * | 8 bytes | 4 bytes |4 bytes |8 bytes |4 bytes |4
bytes |8 bytes | 8 bytes |
+ * | 8 bytes | 4 bytes |4 bytes |8 bytes |4 bytes |4
bytes |16 bytes | 8 bytes |
*
+-----------------+---------------------+--------+--------+---------+----------+----------+----------------------+
* |Marker/Timestamp |Partition generation |Flags |Page ID |Group ID |Pin
count |Lock data |Checkpoint tmp buffer |
*
+-----------------+---------------------+--------+--------+---------+----------+----------+----------------------+
@@ -62,32 +64,32 @@ public class PageHeader {
/** Unknown partition generation. */
static final int UNKNOWN_PARTITION_GENERATION = -1;
- /** 8b Marker/timestamp, 4b Partition generation, 4b flags, 8b Page ID, 4b
Group ID, 4b Pin count, 8b Lock, 8b Temporary buffer. */
- public static final int PAGE_OVERHEAD = 48;
-
/** Marker or timestamp offset. */
private static final int MARKER_OR_TIMESTAMP_OFFSET = 0;
/** Partition generation offset. */
- private static final int PARTITION_GENERATION_OFFSET = 8;
+ private static final int PARTITION_GENERATION_OFFSET =
MARKER_OR_TIMESTAMP_OFFSET + Long.BYTES;
/** Flags offset. */
- private static final int FLAGS_OFFSET = 12;
+ private static final int FLAGS_OFFSET = PARTITION_GENERATION_OFFSET +
Integer.BYTES;
/** Page ID offset. */
- private static final int PAGE_ID_OFFSET = 16;
+ private static final int PAGE_ID_OFFSET = FLAGS_OFFSET + Integer.BYTES;
/** Page group ID offset. */
- private static final int PAGE_GROUP_ID_OFFSET = 24;
+ private static final int PAGE_GROUP_ID_OFFSET = PAGE_ID_OFFSET +
Long.BYTES;
/** Page pin counter offset. */
- private static final int PAGE_PIN_CNT_OFFSET = 28;
+ private static final int PAGE_PIN_CNT_OFFSET = PAGE_GROUP_ID_OFFSET +
Integer.BYTES;
/** Page lock offset. */
- public static final int PAGE_LOCK_OFFSET = 32;
+ public static final int PAGE_LOCK_OFFSET = PAGE_PIN_CNT_OFFSET +
Integer.BYTES;
/** Page temp copy buffer relative pointer offset. */
- private static final int PAGE_TMP_BUF_OFFSET = 40;
+ private static final int PAGE_TMP_BUF_OFFSET = PAGE_LOCK_OFFSET +
OffheapReadWriteLock.LOCK_SIZE;
+
+ /** 8b Marker/timestamp, 4b Partition generation, 4b flags, 8b Page ID, 4b
Group ID, 4b Pin count, 16b Lock, 8b Temporary buffer. */
+ public static final int PAGE_OVERHEAD = PAGE_TMP_BUF_OFFSET + Long.BYTES;
/**
* Initializes the header of the page.
@@ -109,7 +111,7 @@ public class PageHeader {
* @param absPtr Absolute pointer.
*/
public static boolean dirty(long absPtr) {
- return flag(absPtr, DIRTY_FLAG, false);
+ return flag(absPtr, DIRTY_FLAG);
}
/**
@@ -120,21 +122,21 @@ public class PageHeader {
* @return Previous value of dirty flag.
*/
public static boolean dirty(long absPtr, boolean dirty) {
- return flag(absPtr, DIRTY_FLAG, dirty, false);
+ return flag(absPtr, DIRTY_FLAG, dirty);
}
/**
* Reads the value of a header validity flag. Does it using a volatile
memory access.
*/
public static boolean headerIsValid(long absPtr) {
- return flag(absPtr, HEADER_IS_VALID_FLAG, true);
+ return flag(absPtr, HEADER_IS_VALID_FLAG);
}
/**
* Updates the value of a header validity flag. Does it using a volatile
memory access.
*/
public static void headerIsValid(long absPtr, boolean valid) {
- flag(absPtr, HEADER_IS_VALID_FLAG, valid, true);
+ flag(absPtr, HEADER_IS_VALID_FLAG, valid);
}
/**
@@ -142,13 +144,12 @@ public class PageHeader {
*
* @param absPtr Absolute pointer.
* @param flagMask Flag mask.
- * @param volatileAccess Whether to use volatile memory access.
*/
- private static boolean flag(long absPtr, int flagMask, boolean
volatileAccess) {
+ private static boolean flag(long absPtr, int flagMask) {
assert (flagMask & 0xFFFFFF) == 0 : Integer.toHexString(flagMask);
- assert Long.bitCount(flagMask) == 1 : Integer.toHexString(flagMask);
+ assert isPow2(flagMask) : Integer.toHexString(flagMask);
- int flags = volatileAccess ? getIntVolatile(null, absPtr +
FLAGS_OFFSET) : getInt(absPtr + FLAGS_OFFSET);
+ int flags = getIntVolatile(null, absPtr + FLAGS_OFFSET);
return (flags & flagMask) != 0;
}
@@ -159,30 +160,27 @@ public class PageHeader {
* @param absPtr Absolute pointer.
* @param flagMask Flag mask.
* @param set New flag value.
- * @param volatileAccess Whether to use volatile memory access.
* @return Previous flag value.
*/
- private static boolean flag(long absPtr, int flagMask, boolean set,
boolean volatileAccess) {
+ private static boolean flag(long absPtr, int flagMask, boolean set) {
assert (flagMask & 0xFFFFFF) == 0 : Integer.toHexString(flagMask);
- assert Long.bitCount(flagMask) == 1 : Integer.toHexString(flagMask);
+ assert isPow2(flagMask) : Integer.toHexString(flagMask);
- int flags = volatileAccess ? getIntVolatile(null, absPtr +
FLAGS_OFFSET) : getInt(absPtr + FLAGS_OFFSET);
+ while (true) {
+ int flags = getIntVolatile(null, absPtr + FLAGS_OFFSET);
- boolean was = (flags & flagMask) != 0;
+ boolean was = (flags & flagMask) != 0;
- if (set) {
- flags |= flagMask;
- } else {
- flags &= ~flagMask;
- }
+ if (was == set) {
+ return was;
+ }
- if (volatileAccess) {
- putIntVolatile(null, absPtr + FLAGS_OFFSET, flags);
- } else {
- putInt(absPtr + FLAGS_OFFSET, flags);
- }
+ int newFlags = set ? (flags | flagMask) : (flags & ~flagMask);
- return was;
+ if (compareAndSwapInt(null, absPtr + FLAGS_OFFSET, flags,
newFlags)) {
+ return was;
+ }
+ }
}
/**