Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-44559] Finish ThreadMXBean implementation for Native Image. #8430

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.core.jdk;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.TargetClass;

@SuppressWarnings({"unused"})
@TargetClass(java.lang.management.ThreadInfo.class)
final class Target_java_lang_management_ThreadInfo {

@Alias
Target_java_lang_management_ThreadInfo(Thread t, int state, Object lockObj, Thread lockOwner,
long blockedCount, long blockedTime,
long waitedCount, long waitedTime,
StackTraceElement[] stackTrace,
Object[] monitors,
int[] stackDepths,
Object[] synchronizers) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.core.jdk;

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean;
import com.oracle.svm.core.locks.Target_java_util_concurrent_locks_AbstractOwnableSynchronizer;
import com.oracle.svm.core.monitor.JavaMonitor;
import com.oracle.svm.core.thread.JavaThreads.JMXMonitoring;
import com.oracle.svm.core.thread.PlatformThreads;

import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.locks.AbstractOwnableSynchronizer;
import java.util.concurrent.locks.LockSupport;

/**
* Utils to support {@link SubstrateThreadMXBean} for providing threading information for JMX
* support. Include the {@link ThreadInfo} constructing utils, and deadlock detection utils.
*/
public class ThreadMXUtils {

public static class ThreadInfoConstructionUtils {

private static StackTraceElement[] getStackTrace(Thread thread, int maxDepth) {
StackTraceElement[] stackTrace = thread.getStackTrace();
return maxDepth == -1 || maxDepth >= stackTrace.length ? stackTrace : Arrays.copyOfRange(stackTrace, 0, maxDepth);
}

private record Blocker(Object blockObject, Thread ownerThread) {
}

private static Blocker getBlockerInfo(Thread thread) {
Object blocker = LockSupport.getBlocker(thread);

if (blocker instanceof JavaMonitor javaMonitor) {
return new Blocker(
javaMonitor.getBlockedObject(),
SubstrateThreadMXBean.getThreadById(javaMonitor.getOwnerThreadId()));
} else if (blocker instanceof AbstractOwnableSynchronizer synchronizer) {
return new Blocker(synchronizer,
SubstrateUtil.cast(synchronizer, Target_java_util_concurrent_locks_AbstractOwnableSynchronizer.class)
.getExclusiveOwnerThread());
}
return new Blocker(blocker, null);
}

private static Object[] getLockedSynchronizers(Thread thread) {
return JMXMonitoring.getThreadLocks(thread);
}

private record LockedMonitors(Object[] monitorObjects, int[] monitorDepths) {
}

private static LockedMonitors getLockedMonitors(Thread thread, int stacktraceLength) {
List<JMXMonitoring.MonitorInfo> monitors = JMXMonitoring.getThreadMonitors(thread);
Object[] monitorObjects = monitors.stream().map(JMXMonitoring.MonitorInfo::originalObject).toArray();
int[] monitorDepths = monitors.stream().mapToInt(monitorInfo -> stacktraceLength < 0 ? -1 : stacktraceLength - monitorInfo.stacksize()).toArray();
return new LockedMonitors(monitorObjects, monitorDepths);
}

private static int getThreadState(Thread thread, boolean inNative) {
int state = PlatformThreads.getThreadStatus(thread);
if (inNative) {
// set the JMM thread state native flag to true:
state |= 0x00400000;
}
return state;
}

public static ThreadInfo getThreadInfo(Thread thread, int maxDepth,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should this method return if the thread is no longer
a. alive
b. blocked

I think a lot of corner cases are missing here.

boolean withLockedMonitors, boolean withLockedSynchronizers) {
Blocker blocker = getBlockerInfo(thread);
StackTraceElement[] stackTrace = getStackTrace(thread, maxDepth);
LockedMonitors lockedMonitors = getLockedMonitors(thread, stackTrace.length);
boolean inNative = stackTrace.length > 0 && stackTrace[0].isNativeMethod();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct - you would need to access the VM-internal thread state of the isolate thread but this isn't accessible easily for threads other than the current thread.

Target_java_lang_management_ThreadInfo targetThreadInfo = new Target_java_lang_management_ThreadInfo(
thread,
getThreadState(thread, inNative),
blocker.blockObject,
blocker.ownerThread,
JMXMonitoring.getThreadTotalBlockedCount(thread),
JMXMonitoring.getThreadTotalBlockedTime(thread),
JMXMonitoring.getThreadTotalWaitedCount(thread),
JMXMonitoring.getThreadTotalWaitedTime(thread),
stackTrace,
withLockedMonitors ? lockedMonitors.monitorObjects : new Object[0],
withLockedMonitors ? lockedMonitors.monitorDepths : new int[0],
withLockedSynchronizers ? getLockedSynchronizers(thread) : new Object[0]);
return SubstrateUtil.cast(targetThreadInfo, ThreadInfo.class);
}
}

public static class DeadlockDetectionUtils {
/**
* Returns an array of thread ids of blocked threads within some given array of ThreadInfo.
*
* @param threadInfos array of ThreadInfo for the threads among which the deadlocks should
* be detected
* @param byMonitorOnly true if we are interested only in the deadlocks blocked exclusively
* on monitors
* @return array containing thread ids of deadlocked threads
*/
public static long[] findDeadlockedThreads(ThreadInfo[] threadInfos, boolean byMonitorOnly) {
HashSet<Long> deadlocked = new HashSet<>();
for (ThreadInfo threadInfo : threadInfos) {
HashSet<Long> chain = new HashSet<>();
ThreadInfo current = threadInfo;
while (current != null && current.getLockInfo() != null && !deadlocked.contains(current.getThreadId())) {
if (!chain.add(current.getThreadId())) {
if (!byMonitorOnly || chain.stream().allMatch(DeadlockDetectionUtils::isBlockedByMonitor)) {
deadlocked.addAll(chain);
}
chain.clear();
break;
}
long currentLockOwnerId = current.getLockOwnerId();
current = Arrays.stream(threadInfos).filter(ti -> ti.getThreadId() == currentLockOwnerId).findAny().orElse(null);
}
}
return deadlocked.stream().mapToLong(threadId -> threadId).toArray();
}

/**
* Anything that is deadlocked can be blocked either by monitor (the object related to
* JavaMonitor), or a lock (anything under AbstractOwnableSynchronizer).
*
* @return true if provided thread is blocked by a monitor
*/
private static boolean isBlockedByMonitor(long threadId) {
return LockSupport.getBlocker(SubstrateThreadMXBean.getThreadById(threadId)) instanceof JavaMonitor;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
*/
package com.oracle.svm.core.jdk.management;

import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.util.Arrays;
Expand All @@ -36,6 +38,8 @@
import org.graalvm.nativeimage.Platforms;

import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.jdk.ThreadMXUtils;
import com.oracle.svm.core.jdk.ThreadMXUtils.ThreadInfoConstructionUtils;
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicInteger;
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicLong;
import com.oracle.svm.core.thread.PlatformThreads;
Expand All @@ -55,9 +59,9 @@ public final class SubstrateThreadMXBean implements com.sun.management.ThreadMXB
private final AtomicInteger peakThreadCount = new AtomicInteger(0);
private final AtomicInteger threadCount = new AtomicInteger(0);
private final AtomicInteger daemonThreadCount = new AtomicInteger(0);

private boolean allocatedMemoryEnabled;
private boolean cpuTimeEnabled;
private boolean contentionMonitoringEnabled;

@Platforms(Platform.HOSTED_ONLY.class)
SubstrateThreadMXBean() {
Expand Down Expand Up @@ -150,45 +154,62 @@ public int getDaemonThreadCount() {
return daemonThreadCount.get();
}

/* All remaining methods are unsupported on Substrate VM. */

@Override
public long[] getAllThreadIds() {
return new long[0];
return Arrays.stream(PlatformThreads.getAllThreads())
.mapToLong(Thread::threadId)
.toArray();
}

public static Thread getThreadById(long id) {
return Arrays.stream(PlatformThreads.getAllThreads())
.filter(thread -> thread.threadId() == id)
.findAny().orElse(null);
}

@Override
public ThreadInfo getThreadInfo(long id) {
return null;
return getThreadInfo(id, -1);
}

@Override
public ThreadInfo[] getThreadInfo(long[] ids) {
return new ThreadInfo[0];
return getThreadInfo(ids, -1);
}

@Override
public ThreadInfo getThreadInfo(long id, int maxDepth) {
return null;
return getThreadInfo(id, maxDepth, false, false);
}

private static ThreadInfo getThreadInfo(long id, int maxDepth, boolean lockedMonitors, boolean lockedSynchronizers) {
Thread thread = getThreadById(id);
return getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
}

private static ThreadInfo getThreadInfo(Thread thread, int maxDepth, boolean lockedMonitors, boolean lockedSynchronizers) {
return thread == null ? null : ThreadInfoConstructionUtils.getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
}

@Override
public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth) {
return new ThreadInfo[0];
return (ThreadInfo[]) Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, maxDepth)).toArray();
}

@Override
public boolean isThreadContentionMonitoringSupported() {
return false;
return true;
}

@Override
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
public boolean isThreadContentionMonitoringEnabled() {
return false;
return contentionMonitoringEnabled;
}

@Override
public void setThreadContentionMonitoringEnabled(boolean enable) {
public void setThreadContentionMonitoringEnabled(boolean value) {
contentionMonitoringEnabled = value;
}

@Override
Expand Down Expand Up @@ -256,32 +277,36 @@ public void setThreadCpuTimeEnabled(boolean enable) {

@Override
public long[] findMonitorDeadlockedThreads() {
return new long[0];
ThreadInfo[] threadInfos = dumpAllThreads(true, false);
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, true);
}

@Override
public long[] findDeadlockedThreads() {
return new long[0];
ThreadInfo[] threadInfos = dumpAllThreads(true, true);
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, false);
}

@Override
public boolean isObjectMonitorUsageSupported() {
return false;
return true;
}

@Override
public boolean isSynchronizerUsageSupported() {
return false;
return true;
}

@Override
public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) {
return new ThreadInfo[0];
return Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, -1, lockedMonitors, lockedSynchronizers))
.toArray(ThreadInfo[]::new);
}

@Override
public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) {
return new ThreadInfo[0];
return Arrays.stream(PlatformThreads.getAllThreads()).map(thread -> getThreadInfo(thread, -1, lockedMonitors, lockedSynchronizers))
.toArray(ThreadInfo[]::new);
}

@Override
Expand All @@ -290,7 +315,6 @@ public long getThreadAllocatedBytes(long id) {
if (!valid) {
return -1;
}

return PlatformThreads.getThreadAllocatedBytes(id);
}

Expand Down Expand Up @@ -324,8 +348,8 @@ private static void verifyThreadId(long id) {
}

private static void verifyThreadIds(long[] ids) {
for (int i = 0; i < ids.length; i++) {
verifyThreadId(ids[i]);
for (long id : ids) {
verifyThreadId(id);
}
}

Expand Down