mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger] speed up gathering of virtual threads for thread dump, IDEA-367627, IDEA-367848
Evaluate everything in helper, no JDI requests for virtual threads GitOrigin-RevId: d1f367e9def432554623189f673bd05759ec088b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b0c6c0d7bf
commit
e3a60db2aa
@@ -189,6 +189,8 @@
|
||||
description="Timeout (in ms) for Get Thread Dump action to wait until we suspend the VM to perform extended dump collection (virtual threads, coroutines, ...)"/>
|
||||
<registryKey key="debugger.thread.dump.include.virtual.threads" defaultValue="true"
|
||||
description="Try to get virtual threads during debugger's Get Thread Dump action"/>
|
||||
<registryKey key="debugger.thread.dump.virtual.threads.with.monitors.max.count" defaultValue="1000"
|
||||
description="Maximum number of virtual threads when debugger still tries to collect information about owned/contended monitors"/>
|
||||
</extensions>
|
||||
|
||||
<actions>
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
@@ -35,16 +36,14 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.lang.Long
|
||||
import java.lang.Long as JLong
|
||||
import java.util.concurrent.CancellationException
|
||||
import kotlin.Int
|
||||
import kotlin.Pair
|
||||
import kotlin.String
|
||||
import kotlin.Throwable
|
||||
import kotlin.checkNotNull
|
||||
import kotlin.let
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.to
|
||||
|
||||
class ThreadDumpAction : DumbAwareAction() {
|
||||
@@ -174,7 +173,7 @@ private fun renderObject(monitor: ObjectReference): String {
|
||||
catch (e: Throwable) {
|
||||
monitorTypeName = "Error getting object type: '" + e.message + "'"
|
||||
}
|
||||
return "<0x" + Long.toHexString(monitor.uniqueID()) + "> (a " + monitorTypeName + ")"
|
||||
return "<0x" + JLong.toHexString(monitor.uniqueID()) + "> (a " + monitorTypeName + ")"
|
||||
}
|
||||
|
||||
private fun threadStatusToJavaThreadState(status: Int): String {
|
||||
@@ -203,10 +202,24 @@ private fun threadStatusToState(status: Int): String {
|
||||
}
|
||||
}
|
||||
|
||||
private fun threadName(threadReference: ThreadReference): String {
|
||||
return threadReference.name() + "@" + threadReference.uniqueID()
|
||||
private fun javaThreadStateToState(javaThreadState: String): String {
|
||||
return when (javaThreadState) {
|
||||
Thread.State.BLOCKED.name -> "waiting for monitor entry"
|
||||
Thread.State.NEW.name -> "not started"
|
||||
Thread.State.RUNNABLE.name -> "runnable"
|
||||
Thread.State.TIMED_WAITING.name -> "sleeping"
|
||||
Thread.State.WAITING.name -> "waiting"
|
||||
Thread.State.TERMINATED.name -> "zombie"
|
||||
else -> "undefined"
|
||||
}
|
||||
}
|
||||
|
||||
private fun threadName(threadReference: ThreadReference): String =
|
||||
threadName(threadReference.name(), threadReference)
|
||||
|
||||
private fun threadName(threadNameRaw: String, threadReference: ObjectReference): String =
|
||||
threadNameRaw + "@" + threadReference.uniqueID()
|
||||
|
||||
private fun getThreadField(
|
||||
fieldName: String,
|
||||
threadType: ReferenceType, threadObj: ThreadReference,
|
||||
@@ -228,82 +241,98 @@ private fun getThreadField(
|
||||
|
||||
private fun buildThreadStates(
|
||||
vmProxy: VirtualMachineProxyImpl,
|
||||
virtualThreads: List<Pair<ThreadReference, String>>,
|
||||
virtualThreads: List<Triple<ThreadReference, String, Long>>,
|
||||
): List<ThreadState> {
|
||||
|
||||
// By default it includes only platform threads. Unless JDWP's option includevirtualthreads is enabled.
|
||||
// TODO: remove duplicates if includevirtualthreads is enabled.
|
||||
val platformThreads = getPlatformThreadsWithStackTraces(vmProxy)
|
||||
|
||||
val allThreads = platformThreads + virtualThreads.asSequence()
|
||||
|
||||
val result = mutableListOf<ThreadState>()
|
||||
val nameToThreadMap = mutableMapOf<String, ThreadState>()
|
||||
val waitingMap = mutableMapOf<String, String>() // key 'waits_for' value
|
||||
for ((threadReference, rawStackTrace) in allThreads) {
|
||||
|
||||
fun processOne(threadReference: ThreadReference, virtualThreadInfo: Pair<String, Long>?) {
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
val buffer = StringBuilder()
|
||||
val threadStatus = threadReference.status()
|
||||
if (threadStatus == ThreadReference.THREAD_STATUS_ZOMBIE) {
|
||||
continue
|
||||
val threadName: String
|
||||
val stateString: String
|
||||
val javaThreadStateString: String
|
||||
val isVirtual: Boolean
|
||||
val isDaemon: Boolean
|
||||
val tid: Long?
|
||||
val prio: Int?
|
||||
val rawStackTrace: String
|
||||
if (virtualThreadInfo != null) {
|
||||
val nameStateAndStackTrace = splitFirstTwoAndRemainingLines(virtualThreadInfo.first)
|
||||
val nameRaw = nameStateAndStackTrace.first
|
||||
javaThreadStateString = nameStateAndStackTrace.second
|
||||
rawStackTrace = nameStateAndStackTrace.third
|
||||
|
||||
if (javaThreadStateString == Thread.State.TERMINATED.name) return
|
||||
|
||||
threadName = threadName(nameRaw, threadReference)
|
||||
stateString = javaThreadStateToState(javaThreadStateString)
|
||||
|
||||
tid = virtualThreadInfo.second
|
||||
|
||||
isVirtual = true
|
||||
isDaemon = false
|
||||
prio = null
|
||||
}
|
||||
val threadName = threadName(threadReference)
|
||||
val threadState = ThreadState(threadName, threadStatusToState(threadStatus))
|
||||
nameToThreadMap[threadName] = threadState
|
||||
result += threadState
|
||||
threadState.javaThreadState = threadStatusToJavaThreadState(threadStatus)
|
||||
else {
|
||||
val threadStatus = threadReference.status()
|
||||
if (threadStatus == ThreadReference.THREAD_STATUS_ZOMBIE) return
|
||||
|
||||
buffer.append("\"").append(threadName).append("\"")
|
||||
threadName = threadName(threadReference)
|
||||
stateString = threadStatusToState(threadStatus)
|
||||
javaThreadStateString = threadStatusToJavaThreadState(threadStatus)
|
||||
|
||||
isVirtual = threadReference is ThreadReferenceImpl && threadReference.isVirtual
|
||||
|
||||
rawStackTrace = getStackTrace(threadReference)
|
||||
|
||||
val threadType = threadReference.referenceType()
|
||||
if (threadType != null) {
|
||||
// Since Project Loom some of Thread's fields are encapsulated into FieldHolder,
|
||||
// so we try to look up fields in the thread itself and in its holder.
|
||||
val threadType = threadReference.referenceType()
|
||||
val (holderObj, holderType) = when (val value = getThreadField("holder", threadType, threadReference, null, null)) {
|
||||
is ObjectReference -> value to value.referenceType()
|
||||
else -> null to null
|
||||
}
|
||||
|
||||
when (val value = getThreadField("daemon", threadType, threadReference, holderType, holderObj)) {
|
||||
is BooleanValue ->
|
||||
if (value.booleanValue()) {
|
||||
buffer.append(" daemon")
|
||||
threadState.isDaemon = true
|
||||
}
|
||||
}
|
||||
|
||||
when (val value = getThreadField("priority", threadType, threadReference, holderType, holderObj)) {
|
||||
is IntegerValue ->
|
||||
buffer.append(" prio=").append(value.intValue())
|
||||
}
|
||||
|
||||
when (val value = getThreadField("tid", threadType, threadReference, holderType, holderObj)) {
|
||||
is LongValue -> {
|
||||
buffer.append(" tid=0x").append(Long.toHexString(value.longValue()))
|
||||
buffer.append(" nid=NA")
|
||||
}
|
||||
}
|
||||
isDaemon = (getThreadField("daemon", threadType, threadReference, holderType, holderObj) as BooleanValue?)?.booleanValue() ?: false
|
||||
prio = (getThreadField("priority", threadType, threadReference, holderType, holderObj) as IntegerValue?)?.intValue()
|
||||
tid = (getThreadField("tid", threadType, threadReference, holderType, holderObj) as LongValue?)?.longValue()
|
||||
}
|
||||
|
||||
if (threadReference is ThreadReferenceImpl && threadReference.isVirtual()) {
|
||||
val threadState = ThreadState(threadName, stateString)
|
||||
threadState.javaThreadState = javaThreadStateString
|
||||
nameToThreadMap[threadName] = threadState
|
||||
result += threadState
|
||||
|
||||
val buffer = StringBuilder()
|
||||
buffer.append('"').append(threadName).append('"')
|
||||
|
||||
if (isDaemon) {
|
||||
buffer.append(" daemon")
|
||||
threadState.isDaemon = true
|
||||
}
|
||||
if (prio != null) {
|
||||
buffer.append(" prio=").append(prio)
|
||||
}
|
||||
if (tid != null) {
|
||||
buffer.append(" tid=0x").append(JLong.toHexString(tid))
|
||||
buffer.append(" nid=NA")
|
||||
}
|
||||
if (isVirtual) {
|
||||
buffer.append(" virtual")
|
||||
threadState.isVirtual = true
|
||||
}
|
||||
|
||||
//ThreadGroupReference groupReference = threadReference.threadGroup();
|
||||
//if (groupReference != null) {
|
||||
// buffer.append(", ").append(JavaDebuggerBundle.message("threads.export.attribute.label.group", groupReference.name()));
|
||||
//}
|
||||
val state = threadState.state
|
||||
if (state != null) {
|
||||
buffer.append(" ").append(state)
|
||||
}
|
||||
buffer.append(" ").append(threadState.state)
|
||||
|
||||
buffer.append("\n java.lang.Thread.State: ").append(threadState.javaThreadState)
|
||||
|
||||
// There could be too many virtual threads and it's too expensive to collect locking information for all of them.
|
||||
val collectMonitorsInfo = virtualThreadInfo == null ||
|
||||
virtualThreads.size < Registry.intValue("debugger.thread.dump.virtual.threads.with.monitors.max.count", 1000)
|
||||
try {
|
||||
if (vmProxy.canGetOwnedMonitorInfo() && vmProxy.canGetMonitorInfo()) {
|
||||
if (collectMonitorsInfo && vmProxy.canGetOwnedMonitorInfo() && vmProxy.canGetMonitorInfo()) {
|
||||
val list = threadReference.ownedMonitors()
|
||||
for (reference in list) {
|
||||
if (!vmProxy.canGetMonitorFrameInfo()) { // java 5 and earlier
|
||||
@@ -318,7 +347,7 @@ private fun buildThreadStates(
|
||||
}
|
||||
}
|
||||
|
||||
val waitedMonitor = if (vmProxy.canGetCurrentContendedMonitor()) threadReference.currentContendedMonitor() else null
|
||||
val waitedMonitor = if (collectMonitorsInfo && vmProxy.canGetCurrentContendedMonitor()) threadReference.currentContendedMonitor() else null
|
||||
if (waitedMonitor != null) {
|
||||
if (vmProxy.canGetMonitorInfo()) {
|
||||
val waitedMonitorOwner = waitedMonitor.owningThread()
|
||||
@@ -332,7 +361,7 @@ private fun buildThreadStates(
|
||||
}
|
||||
|
||||
val lockedAt = mutableMapOf<Int, MutableList<ObjectReference>>()
|
||||
if (vmProxy.canGetMonitorFrameInfo()) {
|
||||
if (collectMonitorsInfo && vmProxy.canGetMonitorFrameInfo()) {
|
||||
for (m in threadReference.ownedMonitorsAndFrames()) {
|
||||
if (m is MonitorInfo) { // see JRE-937
|
||||
val monitors = lockedAt.getOrPut(m.stackDepth()) { mutableListOf() }
|
||||
@@ -364,11 +393,26 @@ private fun buildThreadStates(
|
||||
catch (_: IncompatibleThreadStateException) {
|
||||
buffer.append("\n\t Incompatible thread state: thread not suspended")
|
||||
}
|
||||
|
||||
val hasEmptyStack = rawStackTrace.isEmpty()
|
||||
threadState.setStackTrace(buffer.toString(), hasEmptyStack)
|
||||
ThreadDumpParser.inferThreadStateDetail(threadState)
|
||||
}
|
||||
|
||||
// By default, it includes only platform threads. Unless JDWP's option includevirtualthreads is enabled.
|
||||
val threadsFromVM = vmProxy.virtualMachine.allThreads()
|
||||
threadsFromVM.forEach {
|
||||
processOne(it, null)
|
||||
}
|
||||
|
||||
val threadsFromVMSet = threadsFromVM.toSet()
|
||||
virtualThreads.forEach { (vthread, stackTrace, tid) ->
|
||||
// thread might be already processed if JDWP's option includevirtualthreads is enabled.
|
||||
if (vthread !in threadsFromVMSet) {
|
||||
processOne(vthread, stackTrace to tid)
|
||||
}
|
||||
}
|
||||
|
||||
for ((waiting, awaited) in waitingMap) {
|
||||
val waitingThread = nameToThreadMap[waiting] ?: continue // continue if zombie
|
||||
val awaitedThread = nameToThreadMap[awaited] ?: continue // continue if zombie
|
||||
@@ -390,35 +434,40 @@ private fun buildThreadStates(
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getPlatformThreadsWithStackTraces(vmProxy: VirtualMachineProxyImpl): Sequence<Pair<ThreadReference, String>> {
|
||||
return vmProxy.virtualMachine.allThreads().asSequence().map { threadReference ->
|
||||
ProgressManager.checkCanceled()
|
||||
private fun getStackTrace(threadReference: ThreadReference): String {
|
||||
val frames =
|
||||
try {
|
||||
threadReference.frames()
|
||||
}
|
||||
catch (e: IncompatibleThreadStateException) {
|
||||
logger<ThreadDumpAction>().error(e)
|
||||
return "Incompatible thread state: thread not suspended"
|
||||
}
|
||||
|
||||
val frames =
|
||||
return buildString {
|
||||
for (stackFrame in frames) {
|
||||
if (this.isNotEmpty()) {
|
||||
append('\n')
|
||||
}
|
||||
append("\t")
|
||||
try {
|
||||
threadReference.frames()
|
||||
append(ThreadDumpAction.renderLocation(stackFrame.location()))
|
||||
}
|
||||
catch (_: IncompatibleThreadStateException) {
|
||||
return@map threadReference to "Incompatible thread state: thread not suspended"
|
||||
}
|
||||
|
||||
threadReference to buildString {
|
||||
for (stackFrame in frames) {
|
||||
if (this.isNotEmpty()) {
|
||||
append('\n')
|
||||
}
|
||||
append("\t")
|
||||
try {
|
||||
append(ThreadDumpAction.renderLocation(stackFrame.location()))
|
||||
}
|
||||
catch (e: InvalidStackFrameException) {
|
||||
append("Invalid stack frame: ").append(e.message)
|
||||
}
|
||||
catch (e: InvalidStackFrameException) {
|
||||
logger<ThreadDumpAction>().error(e)
|
||||
append("Invalid stack frame: ").append(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitFirstTwoAndRemainingLines(text: String): Triple<String, String, String> {
|
||||
val first = text.lineSequence().first()
|
||||
val second = text.lineSequence().drop(1).first()
|
||||
val remaining = text.lineSequence().drop(2).joinToString("\n")
|
||||
return Triple(first, second, remaining)
|
||||
}
|
||||
|
||||
private class JavaThreadsProvider : ThreadDumpItemsProviderFactory() {
|
||||
override fun getProvider(context: DebuggerContextImpl) = object : ThreadDumpItemsProvider {
|
||||
val vm = context.debugProcess!!.virtualMachineProxy
|
||||
@@ -441,7 +490,7 @@ private class JavaThreadsProvider : ThreadDumpItemsProviderFactory() {
|
||||
.map(::JavaThreadDumpItem)
|
||||
}
|
||||
|
||||
private fun evaluateAndGetAllVirtualThreads(suspendContext: SuspendContextImpl): List<Pair<ThreadReference, String>> {
|
||||
private fun evaluateAndGetAllVirtualThreads(suspendContext: SuspendContextImpl): List<Triple<ThreadReference, String, Long>> {
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, suspendContext.frameProxy)
|
||||
|
||||
val lookupImpl = getMethodHandlesImplLookup(evaluationContext)
|
||||
@@ -461,19 +510,23 @@ private class JavaThreadsProvider : ThreadDumpItemsProviderFactory() {
|
||||
thisLogger().error(e)
|
||||
return emptyList()
|
||||
}
|
||||
val packedThreadsAndStackTraces = (evaluated as ArrayReference?)?.values ?: emptyList()
|
||||
if (evaluated == null) return emptyList()
|
||||
|
||||
val (packedThreadsAndStackTraces, threadIds) = (evaluated as ArrayReference).values.map { (it as ArrayReference).values }
|
||||
|
||||
ProgressManager.checkCanceled()
|
||||
return buildList {
|
||||
var i = 0
|
||||
while (i < packedThreadsAndStackTraces.size) {
|
||||
val stackTrace = (packedThreadsAndStackTraces[i++] as StringReference).value()
|
||||
var tidIdx = 0
|
||||
var stIdx = 0
|
||||
while (stIdx < packedThreadsAndStackTraces.size) {
|
||||
val stackTrace = (packedThreadsAndStackTraces[stIdx++] as StringReference).value()
|
||||
while (true) {
|
||||
val thread = packedThreadsAndStackTraces[i++]
|
||||
val thread = packedThreadsAndStackTraces[stIdx++]
|
||||
if (thread == null) {
|
||||
break
|
||||
}
|
||||
add(thread as ThreadReference to stackTrace)
|
||||
val threadId = (threadIds[tidIdx++] as LongValue).value()
|
||||
add(Triple(thread as ThreadReference, stackTrace, threadId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class JavaThreadDumpItem(private val threadState: ThreadState) : DumpItem {
|
||||
|
||||
private inner class JavaMergeableToken : MergeableToken {
|
||||
private val comparableStackTrace: String =
|
||||
stackTrace.substringAfter("\n").replace("<0x.+>\\s".toRegex(), "<merged>")
|
||||
stackTrace.substringAfter("\n").replace("<0x\\d+>\\s".toRegex(), "<merged>")
|
||||
|
||||
override val item: JavaThreadDumpItem get() = this@JavaThreadDumpItem
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ public final class VirtualThreadDumper {
|
||||
static MethodHandle containerThreadsHandle;
|
||||
|
||||
static MethodHandle threadIsVirtualHandle;
|
||||
static MethodHandle threadThreadState;
|
||||
|
||||
private static boolean init(MethodHandles.Lookup lookup) {
|
||||
if (!initialized) {
|
||||
@@ -35,6 +36,8 @@ public final class VirtualThreadDumper {
|
||||
|
||||
//noinspection JavaLangInvokeHandleSignature
|
||||
threadIsVirtualHandle = lookup.findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class));
|
||||
//noinspection JavaLangInvokeHandleSignature
|
||||
threadThreadState = lookup.findVirtual(Thread.class, "threadState", MethodType.methodType(Thread.State.class));
|
||||
|
||||
successfully = true;
|
||||
} catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
|
||||
@@ -46,7 +49,7 @@ public final class VirtualThreadDumper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all virtual threads with stack traces.
|
||||
* Returns all virtual threads with stack traces (along with a name and thread state) and parallel array of thread IDs.
|
||||
* <br/>
|
||||
* They are grouped by equal stack traces and packed into the plain `Object` array in the following way:
|
||||
* <ul>
|
||||
@@ -56,7 +59,7 @@ public final class VirtualThreadDumper {
|
||||
* <li>Then we have a new group of stack trace and threads, or the array ends.</li>
|
||||
* </ul>
|
||||
* <br/>
|
||||
* Returns an empty array if there are no virtual threads or some error occurred.
|
||||
* Returns {@code null} if there are no virtual threads or some error occurred.
|
||||
*/
|
||||
public static Object[] getAllVirtualThreadsWithStackTraces(MethodHandles.Lookup lookup) throws Throwable {
|
||||
if (!init(lookup)) {
|
||||
@@ -71,8 +74,18 @@ public final class VirtualThreadDumper {
|
||||
HashMap<String, ArrayList<Thread>> groupedByStackTrace = new HashMap<>();
|
||||
for (Thread t : threads) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
// "Stack trace" format, in such a way it should be shared between multiple threads and easily processed on the debugger side:
|
||||
// <name>
|
||||
// <javaThreadState>
|
||||
// <stack trace elements...>
|
||||
|
||||
String name = t.getName();
|
||||
Thread.State javaThreadState = (Thread.State)threadThreadState.invoke(t);
|
||||
buffer.append(name).append('\n').append(javaThreadState);
|
||||
|
||||
for (StackTraceElement ste : t.getStackTrace()) {
|
||||
buffer.append("\tat ").append(ste).append('\n');
|
||||
buffer.append("\n\tat ").append(ste);
|
||||
}
|
||||
String stackTrace = buffer.toString();
|
||||
|
||||
@@ -84,21 +97,25 @@ public final class VirtualThreadDumper {
|
||||
similarThreads.add(t);
|
||||
}
|
||||
|
||||
long[] tids = new long[threads.size()];
|
||||
int tidIdx = 0;
|
||||
|
||||
Object[] allStackTraceAndThreads = new Object[threads.size() + groupedByStackTrace.size() * 2];
|
||||
int i = 0;
|
||||
int stIdx = 0;
|
||||
|
||||
for (Map.Entry<String, ArrayList<Thread>> e : groupedByStackTrace.entrySet()) {
|
||||
String st = e.getKey();
|
||||
ArrayList<Thread> ts = e.getValue();
|
||||
allStackTraceAndThreads[i++] = st;
|
||||
allStackTraceAndThreads[stIdx++] = st;
|
||||
for (Thread t : ts) {
|
||||
allStackTraceAndThreads[i++] = t;
|
||||
allStackTraceAndThreads[stIdx++] = t;
|
||||
tids[tidIdx++] = t.getId();
|
||||
}
|
||||
allStackTraceAndThreads[i++] = null;
|
||||
allStackTraceAndThreads[stIdx++] = null;
|
||||
}
|
||||
assert i == allStackTraceAndThreads.length;
|
||||
assert stIdx == allStackTraceAndThreads.length;
|
||||
|
||||
return allStackTraceAndThreads;
|
||||
return new Object[] { allStackTraceAndThreads, tids };
|
||||
}
|
||||
|
||||
private static ArrayList<Thread> getAllVirtualThreads(MethodHandles.Lookup lookup) throws Throwable {
|
||||
|
||||
@@ -34,6 +34,7 @@ public final class ThreadDumpParser {
|
||||
private static final Pattern ourIdleTimerThreadPattern = Pattern.compile("java\\.lang\\.Object\\.wait\\([^()]+\\)\\s+at java\\.util\\.TimerThread\\.mainLoop");
|
||||
private static final Pattern ourIdleSwingTimerThreadPattern = Pattern.compile("java\\.lang\\.Object\\.wait\\([^()]+\\)\\s+at javax\\.swing\\.TimerQueue\\.run");
|
||||
private static final String AT_JAVA_LANG_OBJECT_WAIT = "java.lang.Object.wait(";
|
||||
private static final String ourLockedOwnableSynchronizersHeader = "Locked ownable synchronizers";
|
||||
private static final Pattern ourLockedOwnableSynchronizersPattern = Pattern.compile("- <(0x[\\da-f]+)> \\(.*\\)");
|
||||
|
||||
private static final String[] IMPORTANT_THREAD_DUMP_WORDS = ContainerUtil.ar("tid", "nid", "wait", "parking", "prio", "os_prio", "java");
|
||||
@@ -217,6 +218,11 @@ public final class ThreadDumpParser {
|
||||
}
|
||||
|
||||
private static @Nullable String findLockedOwnableSynchronizers(final String stackTrace) {
|
||||
if (!stackTrace.contains(ourLockedOwnableSynchronizersHeader)) {
|
||||
// It's a fast path, otherwise regex below takes too much time.
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher m = ourLockedOwnableSynchronizersPattern.matcher(stackTrace);
|
||||
if (m.find()) {
|
||||
return m.group(1);
|
||||
|
||||
Reference in New Issue
Block a user