| /******************************************************************************* |
| * 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); |
| } |
| } |
| } |