graal
graal copied to clipboard
[GR-44559] Finish ThreadMXBean implementation for Native Image.
The functionality of ThreadMXBean
is implemented and working within native image GR-44559
Testing
Build native image with: --enable-monitoring=jmxserver,jvmstat
Run it with:
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=9996 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.enableThreadContentionMonitoring=true
Tools like VisualVM show the Thread monitoring info:
Example result of dumpAllThreads
method call:
Result of getThreadInfo
by Thread id (the locks and monitors shouldn't be included):
Deadlock detection returns an array of ids of deadlocked threads:
Thank you for your pull request and welcome to our community! To contribute, please sign the Oracle Contributor Agreement (OCA). The following contributors of this PR have not signed the OCA:
- PR author: smthelusive
- [email protected] (@smthelusive)
To sign the OCA, please create an Oracle account and sign the OCA in Oracle's Contributor Agreement Application.
When signing the OCA, please provide your GitHub username. After signing the OCA and getting an OCA approval from Oracle, this PR will be automatically updated.
If you are an Oracle employee, please make sure that you are a member of the main Oracle GitHub organization, and your membership in this organization is public.
Thank you for signing the OCA.
Haven't look at this in detail yet, but full support for ThreadMXBean would be very nice to have!
You may need to run mx eclipseformat
for that failing gate to pass.
Thank you for the tip @roberttoyonaga! Done 👍
Here's a ClassCastException
I ran into testing this:
java.lang.ClassCastException: java.util.concurrent.locks.ReentrantLock$NonfairSync cannot be cast to com.oracle.svm.core.locks.Target_java_util_concurrent_locks_AbstractOwnableSynchronizer
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.ThreadMXUtils.toTarget(ThreadMXUtils.java:56)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.ThreadMXUtils$ThreadInfoConstructionUtils.getBlockerInfo(ThreadMXUtils.java:87)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.ThreadMXUtils$ThreadInfoConstructionUtils.getThreadInfo(ThreadMXUtils.java:119)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.management.SubstrateThreadMXBean.getThreadInfo(SubstrateThreadMXBean.java:188)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.management.SubstrateThreadMXBean.lambda$getThreadInfo$2(SubstrateThreadMXBean.java:299)
at [email protected]/java.util.stream.LongPipeline$1$1.accept(LongPipeline.java:177)
at [email protected]/java.util.Spliterators$LongArraySpliterator.forEachRemaining(Spliterators.java:1223)
at [email protected]/java.util.Spliterator$OfLong.forEachRemaining(Spliterator.java:777)
at [email protected]/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at [email protected]/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at [email protected]/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
at [email protected]/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
at [email protected]/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.management.SubstrateThreadMXBean.getThreadInfo(SubstrateThreadMXBean.java:300)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.management.SubstrateThreadMXBean.dumpAllThreads(SubstrateThreadMXBean.java:305)
at org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.management.SubstrateThreadMXBean.findDeadlockedThreads(SubstrateThreadMXBean.java:283)
Hi @fniephaus and @roberttoyonaga. Thank you both for the review! I've fixed the points that you mentioned and reworked some parts.
Here is what changed:
- Fix calling
Thread.getStackTrace
multiple times. - Change the way to detect if the thread is running native code.
StackTraceElement
contains information whether the method is a native method.IsolateThread
isn't necessary for this approach, which is nice. - Optimise
SubstrateThreadMXBean.dumpAllThreads
flow, so it doesn't look up all threads twice. - Reuse
getThreadInfo(long[] ids, int maxDepth)
as suggested. - Collect monitor re-enters as stack. While testing without native image, I noticed that on monitor re-enter in the same Thread the Object is stored multiple times as a locked monitor, each time with different depth. So I changed the implementation and this also resulted in storing the depths properly.
- Handle all cases when
JavaMonitor
is created or reentered. This includes also handling theMultiThreadedMonitorSupport.additionalMonitors
use-case for the objects that don't have the monitor offset. - Not substitute the whole
AbstractOwnableSynchronizer
class. - Lock monitors and locks when reading these collections and make that piece of code more reliable.
- Solve the problem with the stack frame offset when the monitor is entered. The problem: I want to know where in the stack the monitor was actually obtained in the user code. However, while handling the monitor creation, we are putting several extra frames on top of the stack and then remove them, so they are not in the stack at the moment when we're reporting anything. It is possible to detect where exactly the monitor actually happened by checking how many top frames have module/class names of our internal code, but it's expensive. A lot cheaper is to have a fixed offset, especially since it's a single use-case with the same flow, where we always have exactly 4 extra frames on the stack… I'm not a big fan of this solution, but couldn't come up with a better one.
- Simplified deadlock detection logic.
- Fixed the years in the headers.
- @fniephaus I couldn't reproduce the
ClassCastException
, but my changes should have fixed the potential of it happening. Could you please check?
Let me know if you have more suggestions, I will do my best to fix it. :)
Hi @fniephaus and @christianhaeubl! It looks like I need some more reviews for this PR, all the previous concerns were addressed :blush: Please let me know if you have any other comments, I will be happy to fix it. Thank you!
Hey @smthelusive, thanks for the update! I'm going to run the public and internal gates on your PR again. Hopefully, we soon have enough time to review your PR as well. We are currently busy integrating other contributions from @roberttoyonaga and others.
This PR unfortunately got mirrored to #8430, sorry about that. I am adding fixes for the style gate so that other gates can run as well.
Looks like our internal gate has found a problem with the PR:
Fatal error: java.lang.AssertionError: null
at com.oracle.svm.core.thread.JavaThreads$JMXMonitoring.addThreadLock(JavaThreads.java:532)
at java.util.concurrent.locks.AbstractOwnableSynchronizer.setExclusiveOwnerThread(AbstractOwnableSynchronizer.java:62)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.initialTryLock(ReentrantLock.java:226)
at java.util.concurrent.locks.ReentrantLock$Sync.lock(ReentrantLock.java:152)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:322)
at jdk.internal.misc.InternalLock.lock(InternalLock.java:74)
at java.io.PrintStream.write(PrintStream.java:788)
at java.io.PrintStream.print(PrintStream.java:1002)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:697)
at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
at java.lang.Thread.dispatchUncaughtException(Thread.java:2901)
at java.lang.VirtualThread.run(VirtualThread.java:311)
at java.lang.VirtualThread$VThreadContinuation$1.run(VirtualThread.java:190)
This suggests that the implementation may not be working correctly with virtual threads. Could you take a look, please?
Hi all, thank you for your reviews. Given the latest feedback in the mirror PR I don't think that I can proceed with the current solution, hence closing this PR. Thank you for your time, your help, and this opportunity to learn.