blob: 5e0a0c0eeb7c51ebc2d31c3865b1540f1b720821 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.ui.monitors;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
/**
* Manager for the thread and monitor model.
*/
public class ThreadMonitorManager implements IDebugEventSetListener, IPropertyChangeListener {
private static ThreadMonitorManager fDefaultManager;
/**
* HashMap IJavaThread -> JavaMonitorThread
*/
private HashMap<IDebugElement, Object> fJavaMonitorThreads;
/**
* HashMap IJavaObject -> JavaMonitor
*/
private HashMap<IDebugElement, Object> fJavaMonitors;
private boolean fIsEnabled;
/**
* Returns the default ThreadMonitorManager object.
*/
public static ThreadMonitorManager getDefault() {
if (fDefaultManager == null) {
fDefaultManager= new ThreadMonitorManager();
}
return fDefaultManager;
}
private ThreadMonitorManager() {
fJavaMonitorThreads= new HashMap<>();
fJavaMonitors= new HashMap<>();
IPreferenceStore preferenceStore = JDIDebugUIPlugin.getDefault().getPreferenceStore();
preferenceStore.addPropertyChangeListener(this);
fIsEnabled= preferenceStore.getBoolean(IJavaDebugUIConstants.PREF_SHOW_MONITOR_THREAD_INFO);
if (fIsEnabled) {
DebugPlugin.getDefault().addDebugEventListener(this);
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.IDebugEventSetListener#handleDebugEvents(org.eclipse.debug.core.DebugEvent[])
*/
@Override
public void handleDebugEvents(DebugEvent[] events) {
for (int i= 0; i < events.length; i++) {
DebugEvent debugEvent= events[i];
Object eventSource= debugEvent.getSource();
int eventKind= debugEvent.getKind();
IJavaThread javaThread = null;
if (eventSource instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable)eventSource;
javaThread = adaptable.getAdapter(IJavaThread.class);
if (javaThread != null) {
switch (eventKind) {
case DebugEvent.SUSPEND:
case DebugEvent.RESUME:
// refresh on suspend/resume
if (debugEvent.getDetail() != DebugEvent.EVALUATION_IMPLICIT) {
handleSuspendResume();
}
break;
case DebugEvent.TERMINATE:
// clean the thread map when a thread terminates
handleThreadTerminate(javaThread);
break;
}
} else {
IJavaDebugTarget target = adaptable.getAdapter(IJavaDebugTarget.class);
if (target != null) {
switch (eventKind) {
case DebugEvent.SUSPEND:
case DebugEvent.RESUME:
// refresh on suspend/resume
if (debugEvent.getDetail() != DebugEvent.EVALUATION_IMPLICIT) {
handleSuspendResume();
}
break;
case DebugEvent.TERMINATE:
// clean the maps when a target terminates
handleDebugTargetTerminate(target);
break;
}
}
}
}
}
}
private void handleSuspendResume() {
JavaMonitorThread[] threads = getJavaMonitorThreads();
for (int i = 0; i < threads.length; i++) {
threads[i].setToUpdate();
}
DebugPlugin.getDefault().asyncExec(new RefreshAndDetectDeadlock());
}
private void handleThreadTerminate(IJavaThread thread) {
// remove this thread
synchronized(fJavaMonitorThreads) {
fJavaMonitorThreads.remove(thread);
}
}
private void handleDebugTargetTerminate(IJavaDebugTarget debugTarget) {
// remove the threads and monitors for this debug target.
clean(fJavaMonitors, debugTarget);
clean(fJavaMonitorThreads, debugTarget);
}
private void clean(Map<IDebugElement, Object> map, IJavaDebugTarget debugTarget) {
IDebugElement debugElements[] = null;
synchronized(map) {
debugElements = new IDebugElement[map.size()];
debugElements = map.keySet().toArray(debugElements);
}
for(int i = 0; i < debugElements.length; ++i) {
if (debugElements[i].getDebugTarget().equals(debugTarget)) {
synchronized(map) {
map.remove(debugElements[i]);
}
}
}
}
/**
* Returns the unique JavaMonitorThread object for the given thread.
*/
protected JavaMonitorThread getJavaMonitorThread(IJavaThread thread, IThread originalThread) {
synchronized (fJavaMonitorThreads) {
JavaMonitorThread javaMonitorThread= (JavaMonitorThread) fJavaMonitorThreads.get(thread);
if (javaMonitorThread == null) {
javaMonitorThread= new JavaMonitorThread(thread, originalThread);
fJavaMonitorThreads.put(thread, javaMonitorThread);
DebugPlugin.getDefault().asyncExec(new DetectDeadlock());
} else if (originalThread != null) {
javaMonitorThread.setOriginalThread(originalThread);
}
return javaMonitorThread;
}
}
/**
* Returns the unique JavaMonitor object for the given monitor.
*/
protected JavaMonitor getJavaMonitor(IJavaObject monitor) {
synchronized (fJavaMonitors) {
JavaMonitor javaMonitor= (JavaMonitor) fJavaMonitors.get(monitor);
if (javaMonitor == null) {
javaMonitor= new JavaMonitor(monitor);
fJavaMonitors.put(monitor, javaMonitor);
}
return javaMonitor;
}
}
/**
* Removes a monitor from the monitor map.
*/
protected void removeJavaMonitor(JavaMonitor monitor) {
synchronized(fJavaMonitors) {
fJavaMonitors.remove(monitor.getMonitor());
}
}
/**
* Returns the monitor the given thread is waiting for.
*/
public JavaContendedMonitor getContendedMonitor(IThread thread) {
IJavaThread javaThread = thread.getAdapter(IJavaThread.class);
if (javaThread == null || !fIsEnabled || !((IJavaDebugTarget)javaThread.getDebugTarget()).supportsMonitorInformation()) {
return null;
}
return getJavaMonitorThread(javaThread, thread).getContendedMonitor();
}
/**
* Returns the monitors the given thread owns.
*/
public JavaOwnedMonitor[] getOwnedMonitors(IThread thread) {
IJavaThread javaThread = thread.getAdapter(IJavaThread.class);
if (javaThread == null || !fIsEnabled || !((IJavaDebugTarget)javaThread.getDebugTarget()).supportsMonitorInformation()) {
return new JavaOwnedMonitor[0];
}
return getJavaMonitorThread(javaThread, thread).getOwnedMonitors();
}
/**
* Runnable to be run asynchronously, to refresh the model and
* look for deadlocks.
*/
class RefreshAndDetectDeadlock extends DetectDeadlock {
@Override
public void run() {
JavaMonitorThread[] threads= getJavaMonitorThreads();
for (int i = 0; i < threads.length; i++) {
threads[i].refresh();
}
super.run();
}
}
class DetectDeadlock implements Runnable {
@Override
public void run() {
JavaMonitorThread[] threads= getJavaMonitorThreads();
JavaMonitor[] monitors= getJavaMonitors();
List<Object> inDeadlock= new ArrayList<>();
for (int i = 0; i < threads.length; i++) {
JavaMonitorThread thread= threads[i];
List<JavaMonitorThread> threadStack= new ArrayList<>();
List<JavaMonitor> monitorStack= new ArrayList<>();
while (thread != null) {
boolean isInDeadlock= false;
if (inDeadlock.contains(thread) || threadStack.contains(thread)) {
isInDeadlock= true;
} else {
JavaMonitor monitor = thread.getContendedMonitor0();
if (monitor == null) {
thread= null;
} else if (inDeadlock.contains(monitor)) {
isInDeadlock= true;
} else {
threadStack.add(thread);
monitorStack.add(monitor);
thread= monitor.getOwningThread0();
}
}
if (isInDeadlock) {
// is in a deadlock, set the elements of the back trace as 'in a deadlock'
for (Iterator<JavaMonitorThread> iter = threadStack.iterator(); iter.hasNext();) {
inDeadlock.add(iter.next());
}
for (Iterator<JavaMonitor> iter = monitorStack.iterator(); iter.hasNext();) {
inDeadlock.add(iter.next());
}
thread= null;
}
}
}
for (int i = 0; i < threads.length; i++) {
JavaMonitorThread thread= threads[i];
thread.setInDeadlock(inDeadlock.contains(thread));
}
for (int i = 0; i < monitors.length; i++) {
JavaMonitor monitor= monitors[i];
monitor.setInDeadlock(inDeadlock.contains(monitor));
}
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(IJavaDebugUIConstants.PREF_SHOW_MONITOR_THREAD_INFO)) {
fIsEnabled= JDIDebugUIPreferenceInitializer.getBoolean(event);
if (fIsEnabled) {
DebugPlugin.getDefault().addDebugEventListener(this);
} else {
DebugPlugin.getDefault().removeDebugEventListener(this);
}
}
}
/**
* Returns <code>true</code> if SHOW_MONITOR_THREAD_INFO is on and the given thread is
* in a deadlock, <code>false</code> otherwise.
*/
public boolean isInDeadlock(IThread thread) {
IJavaThread javaThread = thread.getAdapter(IJavaThread.class);
if (!fIsEnabled || !((IJavaDebugTarget)javaThread.getDebugTarget()).supportsMonitorInformation()) {
return false;
}
return getJavaMonitorThread(javaThread, thread).isInDeadlock();
}
private JavaMonitor[] getJavaMonitors() {
synchronized(fJavaMonitors) {
JavaMonitor[] monitors = new JavaMonitor[fJavaMonitors.size()];
return fJavaMonitors.values().toArray(monitors);
}
}
private JavaMonitorThread[] getJavaMonitorThreads() {
synchronized(fJavaMonitorThreads) {
JavaMonitorThread[] threads = new JavaMonitorThread[fJavaMonitorThreads.size()];
return fJavaMonitorThreads.values().toArray(threads);
}
}
}