blob: c1f1f87db1a9cc58310816e97dbf1622ce7b517d [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.debugger.engine;
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.sun.jdi.InternalException;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.request.EventRequest;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author lex
*/
public class SuspendManagerImpl implements SuspendManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.SuspendManager");
private final LinkedList<SuspendContextImpl> myEventContexts = new LinkedList<SuspendContextImpl>();
/**
* contexts, paused at breakpoint or another debugger event requests. Note that thread, explicitly paused by user is not considered as
* "paused at breakpoint" and JDI prohibits data queries on its stack frames
*/
private final LinkedList<SuspendContextImpl> myPausedContexts = new LinkedList<SuspendContextImpl>();
private final Set<ThreadReferenceProxyImpl> myFrozenThreads = Collections.synchronizedSet(new HashSet<ThreadReferenceProxyImpl>());
private final DebugProcessImpl myDebugProcess;
public int suspends = 0;
public SuspendManagerImpl(@NotNull DebugProcessImpl debugProcess) {
myDebugProcess = debugProcess;
myDebugProcess.addDebugProcessListener(new DebugProcessAdapterImpl() {
@Override
public void processDetached(DebugProcessImpl process, boolean closedByUser) {
myEventContexts.clear();
myPausedContexts.clear();
myFrozenThreads.clear();
}
});
}
@Override
public SuspendContextImpl pushSuspendContext(final int suspendPolicy, int nVotes) {
SuspendContextImpl suspendContext = new SuspendContextImpl(myDebugProcess, suspendPolicy, nVotes, null) {
@Override
protected void resumeImpl() {
if (LOG.isDebugEnabled()) {
LOG.debug("Start resuming...");
}
myDebugProcess.logThreads();
switch(getSuspendPolicy()) {
case EventRequest.SUSPEND_ALL:
int resumeAttempts = 5;
while (--resumeAttempts > 0) {
try {
myDebugProcess.getVirtualMachineProxy().resume();
break;
}
catch (InternalException e) {
//InternalException 13 means that there are running threads that we are trying to resume
//On MacOS it happened that native thread didn't stop while some java thread reached breakpoint
//noinspection StatementWithEmptyBody
if (/*Patches.MAC_RESUME_VM_HACK && */e.errorCode() == 13) {
//Its funny, but second resume solves the problem
}
else {
LOG.error(e);
break;
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("VM resumed ");
}
break;
case EventRequest.SUSPEND_EVENT_THREAD:
getThread().resume();
if(LOG.isDebugEnabled()) {
LOG.debug("Thread resumed : " + getThread().toString());
}
break;
case EventRequest.SUSPEND_NONE:
if (LOG.isDebugEnabled()) {
LOG.debug("None resumed");
}
break;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Suspends = " + suspends);
}
myDebugProcess.logThreads();
}
};
pushContext(suspendContext);
return suspendContext;
}
@Override
public SuspendContextImpl pushSuspendContext(final EventSet set) {
SuspendContextImpl suspendContext = new SuspendContextImpl(myDebugProcess, set.suspendPolicy(), set.size(), set) {
@Override
protected void resumeImpl() {
if (LOG.isDebugEnabled()) {
LOG.debug("Start resuming eventSet " + set.toString() + " suspendPolicy = " + set.suspendPolicy() + ",size = " + set.size());
}
myDebugProcess.logThreads();
//final ThreadReferenceProxyImpl thread = getThread();
//
//if (thread != null) { // check that thread is suspended at the moment
// try {
// if (!thread.isSuspended()) {
// final int status = thread.status();
// if ((status != ThreadReference.THREAD_STATUS_ZOMBIE) && (status != ThreadReference.THREAD_STATUS_NOT_STARTED) && (status != ThreadReference.THREAD_STATUS_UNKNOWN)) {
// LOG.error("Context thread must be suspended");
// }
// }
// }
// catch (ObjectCollectedException ignored) {}
//}
int attempts = 5;
while (--attempts > 0) {
try {
set.resume();
break;
}
catch (ObjectCollectedException e) {
// according to error reports set.resume() may throw this if one of the threads has been collected
LOG.info(e);
}
catch (InternalException e) {
//InternalException 13 means that there are running threads that we are trying to resume
//On MacOS it happened that native thread didn't stop while some java thread reached breakpoint
//noinspection StatementWithEmptyBody
if (/*Patches.MAC_RESUME_VM_HACK && */e.errorCode() == 13 && set.suspendPolicy() == EventRequest.SUSPEND_ALL) {
//Its funny, but second resume solves the problem
}
else {
LOG.error(e);
break;
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Set resumed ");
}
myDebugProcess.logThreads();
}
};
pushContext(suspendContext);
return suspendContext;
}
private void pushContext(SuspendContextImpl suspendContext) {
DebuggerManagerThreadImpl.assertIsManagerThread();
myEventContexts.addFirst(suspendContext);
suspends++;
if (LOG.isDebugEnabled()) {
LOG.debug("Push context : Suspends = " + suspends);
}
}
@Override
public void resume(SuspendContextImpl context) {
SuspendManagerUtil.prepareForResume(context);
myDebugProcess.logThreads();
final int suspendPolicy = context.getSuspendPolicy();
popContext(context);
context.resume();
myDebugProcess.clearCashes(suspendPolicy);
}
@Override
public void popFrame(SuspendContextImpl suspendContext) {
popContext(suspendContext);
SuspendContextImpl newSuspendContext = pushSuspendContext(suspendContext.getSuspendPolicy(), 0);
newSuspendContext.setThread(suspendContext.getThread().getThreadReference());
notifyPaused(newSuspendContext);
}
@Override
public SuspendContextImpl getPausedContext() {
return !myPausedContexts.isEmpty() ? myPausedContexts.getFirst() : null;
}
public void popContext(SuspendContextImpl suspendContext) {
DebuggerManagerThreadImpl.assertIsManagerThread();
suspends--;
if (LOG.isDebugEnabled()) {
LOG.debug("popContext, suspends = " + suspends);
}
myEventContexts.remove(suspendContext);
myPausedContexts.remove(suspendContext);
}
void pushPausedContext(SuspendContextImpl suspendContext) {
if(LOG.isDebugEnabled()) {
LOG.assertTrue(myEventContexts.contains(suspendContext));
}
myPausedContexts.addFirst(suspendContext);
}
public boolean hasEventContext(SuspendContextImpl suspendContext) {
DebuggerManagerThreadImpl.assertIsManagerThread();
return myEventContexts.contains(suspendContext);
}
@Override
public List<SuspendContextImpl> getEventContexts() {
DebuggerManagerThreadImpl.assertIsManagerThread();
return Collections.unmodifiableList(myEventContexts);
}
@Override
public boolean isFrozen(ThreadReferenceProxyImpl thread) {
return myFrozenThreads.contains(thread);
}
@Override
public boolean isSuspended(ThreadReferenceProxyImpl thread) throws ObjectCollectedException{
DebuggerManagerThreadImpl.assertIsManagerThread();
boolean suspended = false;
if (isFrozen(thread)) {
suspended = true;
}
else {
for (SuspendContextImpl suspendContext : myEventContexts) {
if (suspendContext.suspends(thread)) {
suspended = true;
break;
}
}
}
//bug in JDI : newly created thread may be resumed even when suspendPolicy == SUSPEND_ALL
//if(LOG.isDebugEnabled() && suspended) {
// LOG.assertTrue(thread.suspends(), thread.name());
//}
return suspended && (thread == null || thread.isSuspended());
}
@Override
public void suspendThread(SuspendContextImpl context, ThreadReferenceProxyImpl thread) {
LOG.assertTrue(thread != context.getThread(), "Thread is already suspended at the breakpoint");
if(context.isExplicitlyResumed(thread)) {
context.myResumedThreads.remove(thread);
thread.suspend();
}
}
@Override
public void resumeThread(SuspendContextImpl context, ThreadReferenceProxyImpl thread) {
LOG.assertTrue(thread != context.getThread(), "Use resume() instead of resuming breakpoint thread");
LOG.assertTrue(!context.isExplicitlyResumed(thread));
if(context.myResumedThreads == null) {
context.myResumedThreads = new HashSet<ThreadReferenceProxyImpl>();
}
context.myResumedThreads.add(thread);
thread.resume();
}
@Override
public void freezeThread(ThreadReferenceProxyImpl thread) {
if (myFrozenThreads.add(thread)) {
thread.suspend();
}
}
@Override
public void unfreezeThread(ThreadReferenceProxyImpl thread) {
if (myFrozenThreads.remove(thread)) {
thread.resume();
}
}
private void processVote(final SuspendContextImpl suspendContext) {
LOG.assertTrue(suspendContext.myVotesToVote > 0);
suspendContext.myVotesToVote--;
if (LOG.isDebugEnabled()) {
LOG.debug("myVotesToVote = " + suspendContext.myVotesToVote);
}
if(suspendContext.myVotesToVote == 0) {
if(suspendContext.myIsVotedForResume) {
// resume in a separate request to allow other requests be processed (e.g. dependent bpts enable)
myDebugProcess.getManagerThread().schedule(new DebuggerCommandImpl() {
@Override
protected void action() throws Exception {
resume(suspendContext);
}
@Override
public Priority getPriority() {
return Priority.HIGH;
}
});
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("vote paused");
}
myDebugProcess.logThreads();
myDebugProcess.cancelRunToCursorBreakpoint();
final ThreadReferenceProxyImpl thread = suspendContext.getThread();
myDebugProcess.deleteStepRequests(thread != null? thread.getThreadReference() : null);
notifyPaused(suspendContext);
}
}
}
public void notifyPaused(SuspendContextImpl suspendContext) {
pushPausedContext(suspendContext);
myDebugProcess.myDebugProcessDispatcher.getMulticaster().paused(suspendContext);
}
@Override
public void voteResume(SuspendContextImpl suspendContext) {
if (LOG.isDebugEnabled()) {
LOG.debug("Resume voted");
}
processVote(suspendContext);
}
@Override
public void voteSuspend(SuspendContextImpl suspendContext) {
suspendContext.myIsVotedForResume = false;
processVote(suspendContext);
}
LinkedList<SuspendContextImpl> getPausedContexts() {
return myPausedContexts;
}
}