mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-253712 improved ProgressManager diagnostics
(cherry picked from commit dc41a034b2f8142f4743d1d01a689aafe757483b) (IJ-CR-220046 IJPL-253712 improved ProgressManager diagnostics) GitOrigin-RevId: 88012734da3d286fe61bd226c03da9f5c5fdee7b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f6b69f2728
commit
ecd362eccd
@@ -299,6 +299,9 @@ interface ThreadingSupport {
|
||||
return NAME
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun dumpSomeDiagnosticInfo(thread: Thread): List<String>
|
||||
}
|
||||
|
||||
typealias CleanupAction = () -> Unit
|
||||
+65
-24
@@ -9,6 +9,7 @@ import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.ThreadingSupport;
|
||||
import com.intellij.openapi.application.WriteIntentReadAction;
|
||||
import com.intellij.openapi.application.ex.ApplicationEx;
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
||||
@@ -41,6 +42,7 @@ import com.intellij.util.SystemProperties;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.containers.ConcurrentLongObjectMap;
|
||||
import com.intellij.util.containers.Java11Shim;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.ui.EDT;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import kotlinx.coroutines.Job;
|
||||
@@ -53,8 +55,12 @@ import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.management.LockInfo;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -71,6 +77,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.openapi.application.ModalityKt.currentThreadContextModality;
|
||||
import static com.intellij.openapi.progress.impl.ProgressManagerScopeKt.ProgressManagerScope;
|
||||
@@ -89,11 +96,9 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
|
||||
// THashMap is avoided here because of tombstones overhead
|
||||
private static final Map<ProgressIndicator, Set<Thread>> threadsUnderIndicator = new HashMap<>(); // guarded by threadsUnderIndicator
|
||||
// the active indicator for the thread id
|
||||
private static final ConcurrentLongObjectMap<ProgressIndicator> currentIndicators =
|
||||
Java11Shim.Companion.createConcurrentLongObjectMap();
|
||||
private static final ConcurrentLongObjectMap<ProgressIndicator> currentIndicators = Java11Shim.Companion.createConcurrentLongObjectMap();
|
||||
// top-level indicators for the thread id
|
||||
private static final ConcurrentLongObjectMap<ProgressIndicator> threadTopLevelIndicators =
|
||||
Java11Shim.Companion.createConcurrentLongObjectMap();
|
||||
private static final ConcurrentLongObjectMap<ProgressIndicator> threadTopLevelIndicators = Java11Shim.Companion.createConcurrentLongObjectMap();
|
||||
// threads which are running under canceled indicator
|
||||
private static final Set<Thread> threadsUnderCanceledIndicator = new HashSet<>(); // guarded by threadsUnderIndicator
|
||||
|
||||
@@ -132,7 +137,7 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
|
||||
private static final Map<ProgressIndicator, AtomicInteger> nonStandardIndicators = new ConcurrentHashMap<>();
|
||||
|
||||
public CoreProgressManager() {
|
||||
ProgressIndicatorDumper.INSTANCE.setProgressIndicatorDumper(this::getProgressStateRepresentation);
|
||||
ProgressIndicatorDumper.INSTANCE.setProgressIndicatorDumper(() -> getProgressStateRepresentation());
|
||||
}
|
||||
|
||||
// must be under threadsUnderIndicator lock
|
||||
@@ -1097,7 +1102,7 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
|
||||
return contextModality;
|
||||
}
|
||||
|
||||
ProgressManager progressManager = ProgressManager.getInstanceOrNull();
|
||||
ProgressManager progressManager = getInstanceOrNull();
|
||||
ModalityState progressModality = progressManager == null ? null : progressManager.getCurrentProgressModality();
|
||||
return progressModality == null ? ModalityState.nonModal() : progressModality;
|
||||
}
|
||||
@@ -1180,32 +1185,68 @@ public class CoreProgressManager extends ProgressManager implements Disposable {
|
||||
/**
|
||||
* A utility method for diagnosing state of progress indicator in monitoring facilities, like JStack
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public @Nullable String getProgressStateRepresentation() {
|
||||
private static @NotNull String getProgressStateRepresentation() {
|
||||
synchronized (threadsUnderIndicator) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (threadsUnderIndicator.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int totalIndicators = threadsUnderIndicator.size();
|
||||
String result = totalIndicators+" indicators registered:\n";
|
||||
MultiMap<Thread, ProgressIndicator> threadIndicators = new MultiMap<>();
|
||||
for (Map.Entry<ProgressIndicator, Set<Thread>> entry : threadsUnderIndicator.entrySet()) {
|
||||
ProgressIndicator indicator = entry.getKey();
|
||||
Set<Thread> threads = entry.getValue();
|
||||
result.append("Indicator ").append(renderProgressIndicator(indicator)).append(" corresponds to the following threads:\n");
|
||||
for (Thread thread : threads) {
|
||||
result.append(" - ").append(thread).append(";\n");
|
||||
threadIndicators.putValue(thread, indicator);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
Map<Long, ThreadInfo> threadInfos = Arrays.stream(ThreadDumper.getThreadInfos()).collect(Collectors.toMap(info -> info.getThreadId(), info -> info));
|
||||
ThreadingSupport threadingSupport = ApplicationManager.getApplication().getThreadingSupport();
|
||||
boolean writeActionPending = threadingSupport != null && threadingSupport.isWriteActionPending();
|
||||
boolean writeActionInProgress = threadingSupport != null && threadingSupport.isWriteActionInProgress();
|
||||
for (Map.Entry<Thread, Collection<ProgressIndicator>> entry : threadIndicators.toHashMap().entrySet()) {
|
||||
Thread thread = entry.getKey();
|
||||
Collection<ProgressIndicator> indicators = entry.getValue();
|
||||
long threadId = thread.getId();
|
||||
ProgressIndicator current = currentIndicators.get(threadId);
|
||||
ProgressIndicator topLevel = threadTopLevelIndicators.get(threadId);
|
||||
List<String> readActionStatus = threadingSupport == null ? Collections.emptyList() : threadingSupport.dumpSomeDiagnosticInfo(thread);
|
||||
result += readableThreadInfo(threadInfos.get(threadId)) + "\n" +
|
||||
(readActionStatus.isEmpty() && !writeActionPending && !writeActionInProgress ? "" :
|
||||
" rw action status:" + readActionStatus + (writeActionPending || writeActionInProgress ? "(writeActionPending:"+writeActionPending+", writeActionInProgress:"+writeActionInProgress+")" : "") + "\n") +
|
||||
(current == null ? "" :
|
||||
" current indicator: " + current+"\n") +
|
||||
(current == topLevel ? "" :
|
||||
" top level indicator: " + topLevel + "\n") +
|
||||
(indicators.isEmpty() ? "" :
|
||||
" owns " + indicators.size() + " indicators:"+"\n");
|
||||
for (ProgressIndicator indicator : indicators) {
|
||||
result +=
|
||||
" " + indicator + "("+indicator.getClass()+" canceled: " + indicator.isCanceled() + ", running:" + indicator.isRunning() + ")" + "\n";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static String renderProgressIndicator(ProgressIndicator indicator) {
|
||||
String presentationBuilder = indicator.toString() +
|
||||
" (canceled: " +
|
||||
indicator.isCanceled() +
|
||||
", running:" +
|
||||
indicator.isRunning() +
|
||||
")";
|
||||
return presentationBuilder;
|
||||
private static String readableThreadInfo(@Nullable ThreadInfo info) {
|
||||
if (info == null) return "";
|
||||
String sb = info.getThreadName() + " Id=" + info.getThreadId() + " " + info.getThreadState();
|
||||
if (info.getLockName() != null) {
|
||||
sb += " on " + info.getLockName();
|
||||
}
|
||||
if (info.getLockOwnerName() != null) {
|
||||
sb += " owned by \"" + info.getLockOwnerName() + "\" Id=" + info.getLockOwnerId();
|
||||
}
|
||||
if (info.isSuspended()) {
|
||||
sb += " (suspended)";
|
||||
}
|
||||
if (info.isInNative()) {
|
||||
sb += " (in native)";
|
||||
}
|
||||
LockInfo[] locks = info.getLockedSynchronizers();
|
||||
if (locks.length > 0) {
|
||||
sb += "\n\tNumber of locked synchronizers = " + locks.length + '\n';
|
||||
for (LockInfo li : locks) {
|
||||
sb += "\t- " + li + '\n';
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Arrays
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
@@ -1681,6 +1682,22 @@ class NestedLocksThreadingSupport : ThreadingSupport {
|
||||
"The executor must run the action synchronously"
|
||||
}
|
||||
}
|
||||
|
||||
override fun dumpSomeDiagnosticInfo(thread: Thread): List<String> {
|
||||
val r = mutableListOf<String>()
|
||||
val readActionsInThread = pokeThreadLocalValueWithStick(thread, myReadActionsInThread)
|
||||
if (readActionsInThread != null && readActionsInThread.toString() != "0") {
|
||||
r += "(nested read actions: $readActionsInThread)"
|
||||
}
|
||||
val topMostReadAction = pokeThreadLocalValueWithStick(thread, myTopmostReadAction)
|
||||
if (topMostReadAction != null && topMostReadAction.toString() != "false") {
|
||||
r += "(in top-most read action)"
|
||||
}
|
||||
if (myWriteAcquired == thread) {
|
||||
r += "(write action acquired)"
|
||||
}
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1770,3 +1787,30 @@ private data class PermitWaitingInterceptor(
|
||||
val consumer: (Deferred<*>) -> Unit,
|
||||
)
|
||||
|
||||
private fun pokeThreadLocalValueWithStick(targetThread: Thread, targetThreadLocal: ThreadLocal<*>): Any? {
|
||||
try {
|
||||
// 1. Get the 'threadLocals' field from the target Thread object
|
||||
val threadLocalsField = Thread::class.java.getDeclaredField("threadLocals")
|
||||
threadLocalsField.setAccessible(true)
|
||||
val threadLocalMap = threadLocalsField.get(targetThread)
|
||||
if (threadLocalMap == null) {
|
||||
return null // Map hasn't been initialized yet
|
||||
}
|
||||
// 2. Locate the 'getEntry' method inside ThreadLocalMap
|
||||
val getEntryMethod = Class.forName($$"java.lang.ThreadLocal$ThreadLocalMap").getDeclaredMethod("getEntry", ThreadLocal::class.java)
|
||||
getEntryMethod.setAccessible(true)
|
||||
// 3. Invoke 'getEntry' to extract the map entry for your ThreadLocal key
|
||||
val entry = getEntryMethod.invoke(threadLocalMap, targetThreadLocal) as WeakReference<*>?
|
||||
if (entry == null) {
|
||||
return null
|
||||
}
|
||||
// 4. Extract the 'value' field from that Entry
|
||||
val valueField = Class.forName($$"java.lang.ThreadLocal$ThreadLocalMap$Entry").getDeclaredField("value")
|
||||
valueField.setAccessible(true)
|
||||
return valueField.get(entry)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ object ProgressIndicatorDumper {
|
||||
private const val PROGRESS_INDICATOR_DUMP_HEADER: @NonNls String = "---------- ProgressIndicator dump ----------"
|
||||
|
||||
@Volatile
|
||||
private var PROGRESS_INDICATOR_DUMPER: Supplier<String?>? = null
|
||||
private var PROGRESS_INDICATOR_DUMPER: Supplier<String>? = null
|
||||
|
||||
fun dumpProgressIndicatorState(): String {
|
||||
return (PROGRESS_INDICATOR_DUMPER?.get() ?: "No progress indicator dump")
|
||||
@@ -26,7 +26,7 @@ object ProgressIndicatorDumper {
|
||||
return PROGRESS_INDICATOR_DUMP_HEADER + "\n" + this
|
||||
}
|
||||
|
||||
fun setProgressIndicatorDumper(dumpProvider: Supplier<String?>?) {
|
||||
fun setProgressIndicatorDumper(dumpProvider: Supplier<String>?) {
|
||||
PROGRESS_INDICATOR_DUMPER = dumpProvider
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user