diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java b/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java index 7afff98649d2..6b551a66ecd3 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/action/CalculateRetainedSizeAction.java @@ -14,6 +14,7 @@ import com.intellij.debugger.memory.agent.ui.RetainedSizeDialog; import com.intellij.notification.NotificationType; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.registry.Registry; import com.intellij.xdebugger.XDebuggerManager; @@ -26,7 +27,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Arrays; public class CalculateRetainedSizeAction extends DebuggerTreeAction { - protected final Logger LOG = Logger.getInstance(this.getClass()); + protected static final Logger LOG = Logger.getInstance(CalculateRetainedSizeAction.class); @Override protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) { @@ -46,11 +47,21 @@ public class CalculateRetainedSizeAction extends DebuggerTreeAction { @Override public void contextAction(@NotNull SuspendContextImpl suspendContext) { try { + if (dialog.isDisposed()) { + return; + } + EvaluationContextImpl evaluationContext = new EvaluationContextImpl(suspendContext, suspendContext.getFrameProxy()); MemoryAgent memoryAgent = MemoryAgent.get(debugProcess); + Disposer.register(dialog.getDisposable(), () -> memoryAgent.cancelAction()); MemoryAgentActionResult> result = memoryAgent.estimateObjectSize( evaluationContext, reference, Registry.get("debugger.memory.agent.action.timeout").asInteger() ); + + if (dialog.isDisposed()) { + return; + } + if (result.executedSuccessfully()) { Pair sizesAndHeldObjects = result.getResult(); dialog.setHeldObjectsAndSizes( diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java index 28550aeeac2e..d8455291294b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgent.java @@ -30,6 +30,8 @@ public interface MemoryAgent { return MemoryAgentOperations.getAgent(debugProcess); } + void cancelAction(); + @NotNull MemoryAgentCapabilities capabilities(); diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentActionResult.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentActionResult.java index 2a92c1d0e65a..1fe6d778a553 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentActionResult.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentActionResult.java @@ -5,11 +5,17 @@ import org.jetbrains.annotations.NotNull; public class MemoryAgentActionResult { public enum ErrorCode { - OK, - TIMEOUT; + OK, TIMEOUT, CANCELLED; public static ErrorCode valueOf(int value) { - return value == 0 ? OK : TIMEOUT; + switch (value) { + case 0: + return OK; + case 1: + return TIMEOUT; + default: + return CANCELLED; + } } } @@ -27,6 +33,6 @@ public class MemoryAgentActionResult { } public boolean executedSuccessfully() { - return myErrorCode != ErrorCode.TIMEOUT; + return myErrorCode == ErrorCode.OK; } } diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java index 7a73fac597c3..c7774ea79e99 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentImpl.java @@ -3,32 +3,72 @@ package com.intellij.debugger.memory.agent; import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.io.FileUtil; import com.sun.jdi.ObjectReference; import com.sun.jdi.ReferenceType; import org.jetbrains.annotations.NotNull; +import java.io.File; +import java.io.IOException; import java.util.List; +import java.util.Random; +import java.util.concurrent.Callable; class MemoryAgentImpl implements MemoryAgent { + private enum MemoryAgentActionState { + RUNNING, FINISHED, CANCELLED + } + + private static final Logger LOG = Logger.getInstance(MemoryAgentImpl.class); + static final MemoryAgent DISABLED = new MemoryAgentImpl(MemoryAgentCapabilities.DISABLED); + private final String cancellationFileName; private final MemoryAgentCapabilities myCapabilities; + private MemoryAgentActionState myState; + private File myCancellationFile; MemoryAgentImpl(@NotNull MemoryAgentCapabilities capabilities) { + cancellationFileName = FileUtil.getTempDirectory() + "/" + "memoryAgentCancellationFile" + new Random().nextInt(); myCapabilities = capabilities; + myState = MemoryAgentActionState.FINISHED; + } + + private MemoryAgentActionResult executeOperation(Callable> callable) throws EvaluateException { + if (myState == MemoryAgentActionState.RUNNING) { + throw new EvaluateException("Some action is already running"); + } + + myState = MemoryAgentActionState.RUNNING; + + if (myCancellationFile != null) { + FileUtil.delete(myCancellationFile); + myCancellationFile = null; + } + + try { + return callable.call(); + } + catch (Exception ex) { + throw new EvaluateException(ex.getMessage()); + } + finally { + myState = MemoryAgentActionState.FINISHED; + } } @NotNull @Override public MemoryAgentActionResult> estimateObjectSize(@NotNull EvaluationContextImpl evaluationContext, - @NotNull ObjectReference reference, - long timeoutInMillis) throws EvaluateException { + @NotNull ObjectReference reference, + long timeoutInMillis) throws EvaluateException { if (!myCapabilities.canEstimateObjectSize()) { throw new UnsupportedOperationException("Memory agent can't estimate object size"); } - return MemoryAgentOperations.estimateObjectSize(evaluationContext, reference, timeoutInMillis); + + return executeOperation(() -> MemoryAgentOperations.estimateObjectSize(evaluationContext, reference, cancellationFileName, timeoutInMillis)); } @NotNull @@ -40,7 +80,7 @@ class MemoryAgentImpl implements MemoryAgent { throw new UnsupportedOperationException("Memory agent can't estimate objects sizes"); } - return MemoryAgentOperations.estimateObjectsSizes(evaluationContext, references, timeoutInMillis); + return executeOperation(() -> MemoryAgentOperations.estimateObjectsSizes(evaluationContext, references, cancellationFileName, timeoutInMillis)); } @NotNull @@ -52,7 +92,7 @@ class MemoryAgentImpl implements MemoryAgent { throw new UnsupportedOperationException("Memory agent can't get shallow size by classes"); } - return MemoryAgentOperations.getShallowSizeByClasses(evaluationContext, classes, timeoutInMillis); + return executeOperation(() -> MemoryAgentOperations.getShallowSizeByClasses(evaluationContext, classes, cancellationFileName, timeoutInMillis)); } @NotNull @@ -64,7 +104,7 @@ class MemoryAgentImpl implements MemoryAgent { throw new UnsupportedOperationException("Memory agent can't get retained size by classes"); } - return MemoryAgentOperations.getRetainedSizeByClasses(evaluationContext, classes, timeoutInMillis); + return executeOperation(() -> MemoryAgentOperations.getRetainedSizeByClasses(evaluationContext, classes, cancellationFileName, timeoutInMillis)); } @NotNull @@ -76,7 +116,7 @@ class MemoryAgentImpl implements MemoryAgent { throw new UnsupportedOperationException("Memory agent can't get shallow and retained size by classes"); } - return MemoryAgentOperations.getShallowAndRetainedSizeByClasses(evaluationContext, classes, timeoutInMillis); + return executeOperation(() -> MemoryAgentOperations.getShallowAndRetainedSizeByClasses(evaluationContext, classes, cancellationFileName, timeoutInMillis)); } @NotNull @@ -90,7 +130,20 @@ class MemoryAgentImpl implements MemoryAgent { throw new UnsupportedOperationException("Memory agent can't provide paths to closest gc roots"); } - return MemoryAgentOperations.findPathsToClosestGCRoots(evaluationContext, reference, pathsNumber, objectsNumber, timeoutInMillis); + return executeOperation(() -> MemoryAgentOperations.findPathsToClosestGCRoots(evaluationContext, reference, pathsNumber, objectsNumber, cancellationFileName, timeoutInMillis)); + } + + @Override + public void cancelAction() { + if (myState == MemoryAgentActionState.RUNNING) { + try { + myCancellationFile = FileUtil.createTempFile(cancellationFileName, "", true); + myState = MemoryAgentActionState.CANCELLED; + } + catch (IOException ex) { + LOG.error("Couldn't create memory agent cancellation file", ex); + } + } } @NotNull diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentOperations.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentOperations.java index 20a28488270b..7a02dcf25a98 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentOperations.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentOperations.java @@ -3,6 +3,8 @@ package com.intellij.debugger.memory.agent; import com.intellij.debugger.engine.DebugProcessImpl; import com.intellij.debugger.engine.DebuggerManagerThreadImpl; +import com.intellij.debugger.engine.DebuggerUtils; +import com.intellij.debugger.engine.JVMNameUtil; import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil; import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; @@ -25,17 +27,18 @@ import java.util.List; final class MemoryAgentOperations { private static final Key MEMORY_AGENT_KEY = Key.create("MEMORY_AGENT_KEY"); private static final Logger LOG = Logger.getInstance(MemoryAgentOperations.class); - - private static LongValue getTimeoutValue(@NotNull EvaluationContextImpl evaluationContext, long timeoutInMillis) { - return evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(timeoutInMillis); - } + private static final String proxyConstructorSignature = "(Ljava/lang/Object;J)V"; @NotNull static MemoryAgentActionResult> estimateObjectSize(@NotNull EvaluationContextImpl evaluationContext, @NotNull ObjectReference reference, + @NotNull String cancellationFileName, long timeoutInMillis) throws EvaluateException { - LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis); - Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.ESTIMATE_OBJECT_SIZE, Arrays.asList(reference, timeoutValue)); + Value result = callMethod( + evaluationContext, + MemoryAgentNames.Methods.ESTIMATE_OBJECT_SIZE, + cancellationFileName, timeoutInMillis, reference + ); Pair errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result); MemoryAgentActionResult.ErrorCode errCode = errCodeAndResult.getFirst(); Pair sizesAndObjects; @@ -55,10 +58,14 @@ final class MemoryAgentOperations { @NotNull static MemoryAgentActionResult estimateObjectsSizes(@NotNull EvaluationContextImpl evaluationContext, @NotNull List references, + @NotNull String cancellationFileName, long timeoutInMillis) throws EvaluateException { - LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis); ArrayReference array = wrapWithArray(evaluationContext, references); - Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.ESTIMATE_OBJECTS_SIZE, Arrays.asList(array, timeoutValue)); + Value result = callMethod( + evaluationContext, + MemoryAgentNames.Methods.ESTIMATE_OBJECTS_SIZE, + cancellationFileName, timeoutInMillis, array + ); Pair errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result); return new MemoryAgentActionResult<>( LongArrayParser.INSTANCE.parse(errCodeAndResult.getSecond()).stream().mapToLong(Long::longValue).toArray(), @@ -69,10 +76,14 @@ final class MemoryAgentOperations { @NotNull static MemoryAgentActionResult getShallowSizeByClasses(@NotNull EvaluationContextImpl evaluationContext, @NotNull List classes, + @NotNull String cancellationFileName, long timeoutInMillis) throws EvaluateException { - LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis); ArrayReference array = wrapWithArray(evaluationContext, ContainerUtil.map(classes, ReferenceType::classObject)); - Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.GET_SHALLOW_SIZE_BY_CLASSES, Arrays.asList(array, timeoutValue)); + Value result = callMethod( + evaluationContext, + MemoryAgentNames.Methods.GET_SHALLOW_SIZE_BY_CLASSES, + cancellationFileName, timeoutInMillis, array + ); Pair errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result); return new MemoryAgentActionResult<>( LongArrayParser.INSTANCE.parse(errCodeAndResult.getSecond()).stream().mapToLong(Long::longValue).toArray(), @@ -83,10 +94,14 @@ final class MemoryAgentOperations { @NotNull static MemoryAgentActionResult getRetainedSizeByClasses(@NotNull EvaluationContextImpl evaluationContext, @NotNull List classes, + @NotNull String cancellationFileName, long timeoutInMillis) throws EvaluateException { - LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis); ArrayReference array = wrapWithArray(evaluationContext, ContainerUtil.map(classes, ReferenceType::classObject)); - Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.GET_RETAINED_SIZE_BY_CLASSES, Arrays.asList(array, timeoutValue)); + Value result = callMethod( + evaluationContext, + MemoryAgentNames.Methods.GET_RETAINED_SIZE_BY_CLASSES, + cancellationFileName, timeoutInMillis, array + ); Pair errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result); return new MemoryAgentActionResult<>( LongArrayParser.INSTANCE.parse(errCodeAndResult.getSecond()).stream().mapToLong(Long::longValue).toArray(), @@ -97,10 +112,14 @@ final class MemoryAgentOperations { @NotNull static MemoryAgentActionResult> getShallowAndRetainedSizeByClasses(@NotNull EvaluationContextImpl evaluationContext, @NotNull List classes, + @NotNull String cancellationFileName, long timeoutInMillis) throws EvaluateException { - LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis); ArrayReference array = wrapWithArray(evaluationContext, ContainerUtil.map(classes, ReferenceType::classObject)); - Value result = callMethod(evaluationContext, MemoryAgentNames.Methods.GET_SHALLOW_AND_RETAINED_SIZE_BY_CLASSES, Arrays.asList(array, timeoutValue)); + Value result = callMethod( + evaluationContext, + MemoryAgentNames.Methods.GET_SHALLOW_AND_RETAINED_SIZE_BY_CLASSES, + cancellationFileName, timeoutInMillis, array + ); Pair errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result); Pair, List> shallowAndRetainedSizes = ShallowAndRetainedSizeParser.INSTANCE.parse(errCodeAndResult.getSecond()); return new MemoryAgentActionResult<>( @@ -114,15 +133,17 @@ final class MemoryAgentOperations { @NotNull static MemoryAgentActionResult findPathsToClosestGCRoots(@NotNull EvaluationContextImpl evaluationContext, - @NotNull ObjectReference reference, int pathsNumber, - int objectsNumber, long timeoutInMillis) throws EvaluateException { - LongValue timeoutValue = getTimeoutValue(evaluationContext, timeoutInMillis); + @NotNull ObjectReference reference, + int pathsNumber, int objectsNumber, + @NotNull String cancellationFileName, + long timeoutInMillis) throws EvaluateException { IntegerValue pathsNumberValue = evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(pathsNumber); IntegerValue objectsNumberValue = evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(objectsNumber); Value result = callMethod( evaluationContext, MemoryAgentNames.Methods.FIND_PATHS_TO_CLOSEST_GC_ROOTS, - Arrays.asList(reference, pathsNumberValue, objectsNumberValue, timeoutValue) + cancellationFileName, timeoutInMillis, + reference, pathsNumberValue, objectsNumberValue ); Pair errCodeAndResult = ErrorCodeParser.INSTANCE.parse(result); @@ -218,11 +239,30 @@ final class MemoryAgentOperations { return false; } + @NotNull + private static LongValue getLongValue(@NotNull EvaluationContextImpl evaluationContext, long timeoutInMillis) { + return evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(timeoutInMillis); + } + + @NotNull + private static StringReference getStringReference(@NotNull EvaluationContextImpl evaluationContext, @NotNull String string) { + return evaluationContext.getDebugProcess().getVirtualMachineProxy().mirrorOf(string); + } + private static Value callMethod(@NotNull EvaluationContextImpl evaluationContext, @NotNull String methodName, - @NotNull List args) throws EvaluateException { + @NotNull String cancellationFileName, + long timeoutInMillis, + Value... values) throws EvaluateException { ClassType proxyType = getProxyType(evaluationContext); - return callMethod(evaluationContext, proxyType, methodName, args); + return callMethod( + evaluationContext, + proxyType, + methodName, + getStringReference(evaluationContext, cancellationFileName), + getLongValue(evaluationContext, timeoutInMillis), + Arrays.asList(values) + ); } @NotNull @@ -230,6 +270,17 @@ final class MemoryAgentOperations { @NotNull ClassType proxyType, @NotNull String methodName, @NotNull List args) throws EvaluateException { + return callMethod(evaluationContext, proxyType, methodName, getStringReference(evaluationContext, ""), getLongValue(evaluationContext, -1), args); + } + + @NotNull + private static Value callMethod(@NotNull EvaluationContextImpl evaluationContext, + @NotNull ClassType proxyType, + @NotNull String methodName, + @NotNull ObjectReference cancellationFileName, + @NotNull LongValue timeoutInMillis, + @NotNull List args) throws EvaluateException { + DebuggerManagerThreadImpl.assertIsManagerThread(); long start = System.currentTimeMillis(); List methods = DebuggerUtilsEx.declaredMethodsByName(proxyType, methodName); @@ -241,13 +292,16 @@ final class MemoryAgentOperations { } Method method = methods.get(0); - if (!method.isStatic()) { - throw EvaluateExceptionUtil.createEvaluateException("Utility method should be static"); - } - - Value result = evaluationContext - .computeAndKeep(() -> evaluationContext.getDebugProcess().invokeMethod(evaluationContext, proxyType, method, args, true)); + .computeAndKeep(() -> { + DebugProcessImpl debugProcess = evaluationContext.getDebugProcess(); + Method constructor = DebuggerUtils.findMethod(proxyType, JVMNameUtil.CONSTRUCTOR_NAME, proxyConstructorSignature); + if (constructor == null) { + throw EvaluateExceptionUtil.createEvaluateException("No appropriate constructor found for proxy class"); + } + ObjectReference proxyInstance = debugProcess.newInstance(evaluationContext, proxyType, constructor, Arrays.asList(cancellationFileName, timeoutInMillis)); + return debugProcess.invokeMethod(evaluationContext, proxyInstance, method, args); + }); LOG.info("Memory agent's method \"" + methodName + "\" took " + (System.currentTimeMillis() - start) + " ms"); return result; diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentPathsToClosestGCRootsProvider.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentPathsToClosestGCRootsProvider.java index 47a515e02d56..74a050d92bd9 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentPathsToClosestGCRootsProvider.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/MemoryAgentPathsToClosestGCRootsProvider.java @@ -44,7 +44,7 @@ public class MemoryAgentPathsToClosestGCRootsProvider implements ReferringObject } private ReferringObjectsInfo getPathsToGcRoots(@NotNull EvaluationContextImpl evaluationContext, - @NotNull ObjectReference value) throws EvaluateException { + @NotNull ObjectReference value) throws EvaluateException { MemoryAgent memoryAgent = MemoryAgent.get(evaluationContext.getDebugProcess()); if (!memoryAgent.capabilities().canFindPathsToClosestGcRoots()) { throw new UnsupportedOperationException(); diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/Parsers.kt b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/Parsers.kt index 0674a0e475f4..433647a6f4b8 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/Parsers.kt +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/parsers/Parsers.kt @@ -146,7 +146,7 @@ object ShallowAndRetainedSizeParser : ResultParser, List>> object SizeAndHeldObjectsParser : ResultParser, Array>> { override fun parse(value: Value): Pair, Array> { if (value !is ArrayReference) throw UnexpectedValueFormatException("Array expected") - if (value.length() < 2) throw UnexpectedValueFormatException("long and array of objects expected") + if (value.length() < 2) throw UnexpectedValueFormatException("array of longs and array of objects expected") return Pair( LongArrayParser.parse(value.getValue(0)).toTypedArray(), ObjectReferencesParser.parse(value.getValue(1)).toTypedArray() diff --git a/java/debugger/impl/src/com/intellij/debugger/memory/agent/ui/RetainedSizeDialog.java b/java/debugger/impl/src/com/intellij/debugger/memory/agent/ui/RetainedSizeDialog.java index 9731145a5f1f..489ec05c5f6b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/memory/agent/ui/RetainedSizeDialog.java +++ b/java/debugger/impl/src/com/intellij/debugger/memory/agent/ui/RetainedSizeDialog.java @@ -82,6 +82,7 @@ public class RetainedSizeDialog extends DialogWrapper { .addToCenter(ScrollPaneFactory.createScrollPane(myTree)) .addToTop(createTopPanel()); + if (session != null) { session.addSessionListener(new XDebugSessionListener() { @Override diff --git a/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml b/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml index da544b461c6a..2dc818d84a85 100644 --- a/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml +++ b/java/debugger/memory-agent/intellij.java.debugger.memory.agent.iml @@ -10,13 +10,13 @@ - + - + - +