[debugger] IDEA-363091 Introduce coroutinized getSourcePositionAsync

GitOrigin-RevId: 461f361cbfdfc6dbca70411db168395ab62d8cdc
This commit is contained in:
Maksim Zuev
2024-11-13 15:01:18 +00:00
committed by intellij-monorepo-bot
parent 7b2aeecdf4
commit bdc85458e4
10 changed files with 166 additions and 134 deletions
@@ -10,16 +10,20 @@ import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.impl.DebuggerUtilsAsync
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.impl.DebuggerUtilsImpl
import com.intellij.debugger.impl.suppressExceptions
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.execution.filters.LineNumbersMapping
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.readAction
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.fileTypes.UnknownFileType
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.blockingContext
import com.intellij.openapi.progress.checkCanceled
import com.intellij.util.ThreeState
import com.intellij.xdebugger.frame.XStackFrame
import com.sun.jdi.Location
@@ -52,21 +56,23 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR
mySourcePositionCache.clear()
}
private fun <T> iterate(position: SourcePosition, defaultValue: T?, processor: (PositionManager) -> T?): T? {
private inline fun <T> iterate(position: SourcePosition, defaultValue: T?, processor: (PositionManager) -> T?): T? {
val fileType = position.getFile().getFileType()
return iterate<T>(defaultValue, fileType, true, processor)
return iterate<T>(defaultValue, fileType, true, ProgressManager::checkCanceled, processor)
}
private fun <T> iterate(defaultValue: T?, fileType: FileType?, ignorePCE: Boolean, processor: (PositionManager) -> T?): T? {
private inline fun <T> iterate(
defaultValue: T?, fileType: FileType?, ignorePCE: Boolean,
cancellationCheck: () -> Unit,
processor: (PositionManager) -> T?,
): T? {
for (positionManager in myPositionManagers) {
if (!acceptsFileType(positionManager, fileType)) continue
try {
if (!ignorePCE) {
ProgressManager.checkCanceled()
cancellationCheck()
}
return DebuggerUtilsImpl.suppressExceptions<T?, NoDataException?>(
{ processor(positionManager) },
defaultValue, ignorePCE, NoDataException::class.java)
return suppressExceptions(defaultValue, ignorePCE, NoDataException::class.java) { processor(positionManager) }
}
catch (_: NoDataException) {
}
@@ -74,68 +80,50 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR
return defaultValue
}
private fun <T> iterateAsync(
fileType: FileType?,
ignorePCE: Boolean,
processor: (PositionManager) -> CompletableFuture<T?>,
): CompletableFuture<T?> {
var res = CompletableFuture.failedFuture<T?>(NoDataException.INSTANCE)
for (positionManager in myPositionManagers) {
if (!acceptsFileType(positionManager, fileType)) continue
res = res.exceptionallyCompose { e: Throwable ->
val unwrap = DebuggerUtilsAsync.unwrap(e)
if (unwrap is NoDataException) {
if (!ignorePCE) {
ProgressManager.checkCanceled()
}
return@exceptionallyCompose processor(positionManager)
}
CompletableFuture.failedFuture<T?>(unwrap)
}
}
return res
fun getSourcePositionFuture(location: Location?): CompletableFuture<SourcePosition?> = invokeCommandAsCompletableFuture {
getSourcePositionAsync(location)
}
override fun getSourcePositionAsync(location: Location?): CompletableFuture<SourcePosition?> =
getCachedSourcePosition(location) { fileType: FileType? ->
iterateAsync<SourcePosition?>(fileType, false) { getSourcePositionAsync(it, location) }
override suspend fun getSourcePositionAsync(location: Location?): SourcePosition? =
getCachedSourcePosition(location, { action -> readAction(action) }) { fileType: FileType? ->
iterate<SourcePosition?>(null, fileType, false, { checkCanceled() }) { getSourcePositionAsync(it, location) }
}
private fun getCachedSourcePosition(
override fun getSourcePosition(location: Location?): SourcePosition? =
getCachedSourcePosition(location, { action -> runReadAction(action) }) { fileType: FileType? ->
ReadAction.nonBlocking<SourcePosition?> {
iterate<SourcePosition?>(null, fileType, false, ProgressManager::checkCanceled) { it.getSourcePosition(location) }
}.executeSynchronously()
}
private inline fun getCachedSourcePosition(
location: Location?,
producer: (FileType?) -> CompletableFuture<SourcePosition?>,
): CompletableFuture<SourcePosition?> {
if (location == null) return CompletableFuture.completedFuture<SourcePosition?>(null)
var res: SourcePosition? = null
insideReadAction: (() -> Unit) -> Unit,
producer: (FileType?) -> SourcePosition?,
): SourcePosition? {
if (location == null) return null
try {
res = mySourcePositionCache[location]
val position = mySourcePositionCache[location]
if (position != null && checkCacheEntry(position, location.declaringType().name(), insideReadAction)) {
return position
}
}
catch (_: IllegalArgumentException) { // Invalid method id
}
if (checkCacheEntry(res, location)) return CompletableFuture.completedFuture<SourcePosition?>(res)
val fileType = runReadAction {
val sourceName = DebuggerUtilsEx.getSourceName(location, null)
if (sourceName != null) FileTypeManager.getInstance().getFileTypeByFileName(sourceName) else null
val sourceName = DebuggerUtilsEx.getSourceName(location, null)
val fileType = if (sourceName != null)
callInReadAction(insideReadAction) { FileTypeManager.getInstance().getFileTypeByFileName(sourceName) }
else
null
val position = producer(fileType)
try {
mySourcePositionCache.put(location, position)
}
return producer(fileType)
.thenApply<SourcePosition?> { p: SourcePosition? ->
try {
mySourcePositionCache.put(location, p)
}
catch (_: IllegalArgumentException) { // Invalid method id
}
p
}
}
override fun getSourcePosition(location: Location?): SourcePosition? {
return getCachedSourcePosition(location) { fileType: FileType? ->
val sourcePosition = ReadAction.nonBlocking<SourcePosition?> {
iterate<SourcePosition?>(null, fileType, false) { it.getSourcePosition(location) }
}.executeSynchronously()
CompletableFuture.completedFuture<SourcePosition?>(sourcePosition)
}.getNow(null)
catch (_: IllegalArgumentException) { // Invalid method id
}
return position
}
override fun getAllClasses(classPosition: SourcePosition): MutableList<ReferenceType?> =
@@ -173,7 +161,7 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR
}!!
fun createStackFrames(descriptor: StackFrameDescriptorImpl): MutableList<XStackFrame>? =
iterate(null, null, false) {
iterate(null, null, false, ProgressManager::checkCanceled) {
if (it is PositionManagerWithMultipleStackFrames) {
val stackFrames = it.createStackFrames(descriptor)
if (stackFrames != null) {
@@ -222,26 +210,32 @@ private fun acceptsFileType(positionManager: PositionManager, fileType: FileType
return types.contains(fileType)
}
private fun getSourcePositionAsync(positionManager: PositionManager, location: Location?): CompletableFuture<SourcePosition?> {
private suspend fun getSourcePositionAsync(positionManager: PositionManager, location: Location?): SourcePosition? {
if (positionManager is PositionManagerAsync) {
return positionManager.getSourcePositionAsync(location)
}
try {
val sourcePosition = ReadAction.nonBlocking<SourcePosition?> { positionManager.getSourcePosition(location) }.executeSynchronously()
return CompletableFuture.completedFuture<SourcePosition?>(sourcePosition)
return blockingContext {
ReadAction.nonBlocking<SourcePosition?> { positionManager.getSourcePosition(location) }.executeSynchronously()
}
}
catch (e: Exception) {
return CompletableFuture.failedFuture<SourcePosition?>(DebuggerUtilsAsync.unwrap(e))
throw DebuggerUtilsAsync.unwrap(e)
}
}
private fun checkCacheEntry(position: SourcePosition?, location: Location): Boolean {
if (position == null) return false
return runReadAction {
private inline fun checkCacheEntry(position: SourcePosition, className: String, insideReadAction: (() -> Unit) -> Unit): Boolean =
callInReadAction(insideReadAction) {
val psiFile = position.getFile()
if (!psiFile.isValid()) return@runReadAction false
val url = DebuggerUtilsEx.getAlternativeSourceUrl(location.declaringType().name(), psiFile.getProject()) ?: return@runReadAction true
if (!psiFile.isValid()) return@callInReadAction false
val url = DebuggerUtilsEx.getAlternativeSourceUrl(className, psiFile.getProject()) ?: return@callInReadAction true
val file = psiFile.getVirtualFile()
file != null && url == file.url
}
}
@Suppress("UNCHECKED_CAST")
private inline fun <T : Any?> callInReadAction(insideReadAction: (() -> Unit) -> Unit, crossinline action: () -> T): T {
var result: T? = null
insideReadAction { result = action() }
return result as T
}
@@ -1,12 +1,29 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.engine
import com.intellij.debugger.PositionManager
import com.intellij.debugger.SourcePosition
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.util.concurrency.annotations.RequiresBlockingContext
import com.sun.jdi.Location
import org.jetbrains.annotations.ApiStatus
import java.util.concurrent.CompletableFuture
@ApiStatus.Experimental
interface PositionManagerAsync {
fun getSourcePositionAsync(location: Location?): CompletableFuture<SourcePosition?>
interface PositionManagerAsync : PositionManager {
suspend fun getSourcePositionAsync(location: Location?): SourcePosition?
@RequiresBlockingContext
override fun getSourcePosition(location: Location?): SourcePosition? {
if (ApplicationManager.getApplication().isInternal
&& ApplicationManager.getApplication().isReadAccessAllowed
&& !ProgressManager.getInstance().hasProgressIndicator()) {
fileLogger().error("Call runBlocking from read action without indicator")
}
return runBlockingMaybeCancellable {
getSourcePositionAsync(location)
}
}
}
@@ -0,0 +1,54 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.impl
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.progress.ProcessCanceledException
import com.sun.jdi.InternalException
import com.sun.jdi.ObjectCollectedException
import com.sun.jdi.VMDisconnectedException
import kotlin.coroutines.cancellation.CancellationException
inline fun <T, E : Exception> suppressExceptions(
defaultValue: T?,
ignorePCE: Boolean,
rethrow: Class<E>? = null,
supplier: () -> T?,
): T? {
try {
return supplier()
}
catch (e: ProcessCanceledException) {
if (!ignorePCE) {
throw e
}
}
catch (e: CancellationException) {
throw e
}
catch (e: VMDisconnectedException) {
throw e
}
catch (e: ObjectCollectedException) {
throw e
}
catch (e: InternalException) {
fileLogger().info(e)
}
catch (e: Exception) {
if (rethrow != null && rethrow.isInstance(e)) {
throw e
}
else {
fileLogger().error(e)
}
}
catch (e: AssertionError) {
if (rethrow != null && rethrow.isInstance(e)) {
throw e
}
else {
fileLogger().error(e)
}
}
return defaultValue
}
@@ -530,7 +530,12 @@ public final class DebuggerUtilsAsync {
}
public static Throwable unwrap(@Nullable Throwable throwable) {
return throwable instanceof CompletionException || throwable instanceof ExecutionException ? throwable.getCause() : throwable;
while (throwable instanceof CompletionException || throwable instanceof ExecutionException) {
Throwable cause = throwable.getCause();
if (cause == throwable) break;
throwable = cause;
}
return throwable;
}
public static <T> T logError(@NotNull Throwable throwable) {
@@ -249,40 +249,6 @@ public final class DebuggerUtilsImpl extends DebuggerUtilsEx {
action.accept(wrapIntoThrowable ? new Throwable(e) : e);
}
public static <T, E extends Exception> T suppressExceptions(ThrowableComputable<? extends T, ? extends E> supplier,
T defaultValue) throws E {
return suppressExceptions(supplier, defaultValue, true, null);
}
public static <T, E extends Exception> T suppressExceptions(ThrowableComputable<? extends T, ? extends E> supplier,
T defaultValue,
boolean ignorePCE,
Class<E> rethrow) throws E {
try {
return supplier.compute();
}
catch (ProcessCanceledException e) {
if (!ignorePCE) {
throw e;
}
}
catch (VMDisconnectedException | ObjectCollectedException e) {
throw e;
}
catch (InternalException e) {
LOG.info(e);
}
catch (Exception | AssertionError e) {
if (rethrow != null && rethrow.isInstance(e)) {
throw e;
}
else {
LOG.error(e);
}
}
return defaultValue;
}
public static @NlsContexts.Label String getConnectionWaitStatus(@NotNull RemoteConnection connection) {
String connectionName = ObjectUtils.doIfNotNull(connection, DebuggerUtilsImpl::getConnectionDisplayName);
return connection instanceof RemoteConnectionStub
@@ -86,7 +86,7 @@ public class StackFrameDescriptorImpl extends NodeDescriptorImpl implements Stac
try {
Location location = frame.location();
CompoundPositionManager positionManager = frame.getVirtualMachine().getDebugProcess().getPositionManager();
return positionManager.getSourcePositionAsync(location);
return positionManager.getSourcePositionFuture(location);
}
catch (Exception e) {
return CompletableFuture.failedFuture(e);
@@ -17,6 +17,7 @@ package com.intellij.debugger;
import com.intellij.debugger.requests.ClassPrepareRequestor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.util.concurrency.annotations.RequiresBlockingContext;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.request.ClassPrepareRequest;
@@ -41,6 +42,7 @@ public interface PositionManager {
* @throws NoDataException if the location is not in the code managed by this {@code PositionManager}
*/
@Nullable
@RequiresBlockingContext
SourcePosition getSourcePosition(@Nullable Location location) throws NoDataException;
/**
@@ -6,6 +6,7 @@ import com.intellij.debugger.NoDataException
import com.intellij.debugger.PositionManager
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcess.JAVA_STRATUM
import com.intellij.debugger.engine.PositionManagerAsync
import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
@@ -15,6 +16,7 @@ import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
import com.intellij.util.concurrency.annotations.RequiresBlockingContext
import com.sun.jdi.*
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.idea.debugger.base.util.KotlinDebuggerConstants.KOTLIN_STRATA_NAME
@@ -136,11 +138,21 @@ fun ValueDescriptorImpl.safeCalcValue(context: EvaluationContextImpl): Value? {
return wrapEvaluateException { calcValue(context) }
}
@RequiresBlockingContext
@ApiStatus.Internal
fun PositionManager.safeGetSourcePosition(location: Location): SourcePosition? {
return try {
getSourcePosition(location)
} catch (ex: NoDataException) {
} catch (_: NoDataException) {
null
}
}
@ApiStatus.Internal
suspend fun PositionManagerAsync.safeGetSourcePositionAsync(location: Location): SourcePosition? {
return try {
getSourcePositionAsync(location)
} catch (_: NoDataException) {
null
}
}
@@ -17,12 +17,10 @@ import com.intellij.debugger.jdi.VirtualMachineProxyImpl
import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.debugger.ui.breakpoints.Breakpoint
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.readAction
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.application.smartReadAction
import com.intellij.openapi.fileTypes.FileType
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
@@ -125,23 +123,7 @@ class KotlinPositionManager(private val debugProcess: DebugProcess) : MultiReque
return listOf(KotlinStackFrame(descriptor, visibleVariables))
}
override fun getSourcePositionAsync(location: Location?): CompletableFuture<SourcePosition?> =
invokeCommandAsCompletableFuture {
getSourcePositionInternal(location)
}
override fun getSourcePosition(location: Location?): SourcePosition? {
if (ApplicationManager.getApplication().isInternal
&& ApplicationManager.getApplication().isReadAccessAllowed
&& !ProgressManager.getInstance().hasProgressIndicator()) {
LOG.error("Call runBlocking from read action without indicator")
}
return runBlockingMaybeCancellable {
getSourcePositionInternal(location)
}
}
private suspend fun getSourcePositionInternal(location: Location?): SourcePosition? {
override suspend fun getSourcePositionAsync(location: Location?): SourcePosition? {
DebuggerManagerThreadImpl.assertIsManagerThread()
if (location == null) throw NoDataException.INSTANCE
@@ -1,11 +1,11 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto
import com.intellij.debugger.PositionManager
import com.intellij.debugger.engine.PositionManagerAsync
import com.intellij.openapi.application.readAction
import com.intellij.psi.util.parentOfType
import com.sun.jdi.Location
import org.jetbrains.kotlin.idea.debugger.base.util.safeGetSourcePosition
import org.jetbrains.kotlin.idea.debugger.base.util.safeGetSourcePositionAsync
import org.jetbrains.kotlin.idea.debugger.base.util.safeMethod
import org.jetbrains.kotlin.idea.debugger.core.getInlineFunctionAndArgumentVariablesToBordersMap
import org.jetbrains.kotlin.idea.debugger.core.isInlineFunctionMarkerVariableName
@@ -60,7 +60,7 @@ internal class KotlinSmartStepTargetFiltererAdapter(
suspend fun visitTrace(
targetFilterer: KotlinSmartStepTargetFilterer,
positionManager: PositionManager
positionManager: PositionManagerAsync
): Pair<List<KotlinMethodSmartStepTarget>, List<KotlinMethodSmartStepTarget>> {
for (element in visitedTrace) {
visitTraceElement(element, targetFilterer, positionManager)
@@ -76,7 +76,7 @@ internal class KotlinSmartStepTargetFiltererAdapter(
private suspend fun visitTraceElement(
element: BytecodeTraceElement,
targetFilterer: KotlinSmartStepTargetFilterer,
positionManager: PositionManager
positionManager: PositionManagerAsync
) {
when (element) {
is BytecodeTraceElement.InlineCall -> {
@@ -122,7 +122,7 @@ private fun extractInlineCalls(location: Location): List<InlineCallInfo> = locat
// Filter already visible variable to support smart-step-into while inside an inline function
.filterNot { location.codeIndex() in it.bciRange }
private suspend fun getCalledInlineFunction(positionManager: PositionManager, location: Location): KtNamedFunction? {
val sourcePosition = positionManager.safeGetSourcePosition(location) ?: return null
private suspend fun getCalledInlineFunction(positionManager: PositionManagerAsync, location: Location): KtNamedFunction? {
val sourcePosition = positionManager.safeGetSourcePositionAsync(location) ?: return null
return readAction { sourcePosition.elementAt?.parentOfType<KtNamedFunction>() }
}