001: /*
002: * Swing Explorer. Tool for developers exploring Java/Swing-based application internals.
003: * Copyright (C) 2008, Maxim Zakharenkov
004: *
005: * This program is free software; you can redistribute it and/or modify
006: * it under the terms of the GNU General Public License as published by
007: * the Free Software Foundation; either version 2 of the License, or
008: * (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * You should have received a copy of the GNU General Public License along
016: * with this program; if not, write to the Free Software Foundation, Inc.,
017: * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018: *
019: * $Header: /cvs/swingexplorer/src/org/swingexplorer/edt_monitor/EDTDebugQueue.java,v 1.1 2008/03/07 11:46:52 maxz1 Exp $
020: */
021: package org.swingexplorer.edt_monitor;
022:
023: import java.awt.AWTEvent;
024: import java.awt.EventQueue;
025: import java.awt.Toolkit;
026: import java.awt.event.WindowEvent;
027: import java.text.MessageFormat;
028: import java.util.LinkedList;
029: import java.util.Timer;
030: import java.util.TimerTask;
031:
032: import org.swingexplorer.Problem;
033: import org.swingexplorer.ProblemListener;
034:
035: /**
036: *
037: * @author Elliott Hughes <enh@jessies.org>
038: *
039: * Advice, bug fixes, and test cases from
040: * Alexander Potochkin and Oleg Sukhodolsky.
041: *
042: * https://swinghelper.dev.java.net/
043: *
044: * Adoption for Swing Explorer by Maxim Zakharenkov
045: * https://swingexplorer.dev.java.net/
046: */
047: public class EDTDebugQueue extends EventQueue {
048:
049: // Time to wait between checks that the event dispatch thread isn't hung.
050: private static final long CHECK_INTERVAL_MS = 100;
051:
052: // Help distinguish multiple hangs in the log, and match start and end too.
053: // Only access this via getNewHangNumber.
054: private static int hangCount = 0;
055:
056: // Prevents us complaining about hangs during start-up, which are probably
057: // the JVM vendor's fault.
058: private boolean haveShownSomeComponent = false;
059:
060: // The currently outstanding event dispatches. The implementation of
061: // modal dialogs is a common cause for multiple outstanding dispatches.
062: private final LinkedList<DispatchInfo> dispatches = new LinkedList<DispatchInfo>();
063:
064: static long minimalMonitoredHangTime = 1000;
065: static EDTDebugQueue instance = new EDTDebugQueue();
066: static ProblemListener problemListener;
067:
068: public static void setProblemListener(
069: ProblemListener _problemListener) {
070: problemListener = _problemListener;
071: }
072:
073: private static void notifyProblemListener(long delay, AWTEvent event) {
074: if (problemListener == null) {
075: return;
076: }
077:
078: // obtain stack information
079: StackTraceElement[] trace = Thread.currentThread()
080: .getStackTrace();
081: final StackTraceElement[] newTrace = new StackTraceElement[trace.length - 3];
082: System.arraycopy(trace, 3, newTrace, 0, newTrace.length);
083:
084: // notify
085: String description = MessageFormat.format(
086: "Warning: EDT delay for {0}ms in the event {1}", delay,
087: event.paramString());
088: problemListener.problemOccured(new Problem(description,
089: newTrace));
090: }
091:
092: public static EDTDebugQueue getInstance() {
093: return instance;
094: }
095:
096: public static void initMonitoring() {
097: Toolkit.getDefaultToolkit().getSystemEventQueue()
098: .push(instance);
099: }
100:
101: public static long getMinimalMonitoredHangTime() {
102: return minimalMonitoredHangTime;
103: }
104:
105: public static void setMinimalMonitoredHangTime(
106: long _minimalMonitoredHangTime) {
107: minimalMonitoredHangTime = _minimalMonitoredHangTime;
108: }
109:
110: private static class DispatchInfo {
111: // The last-dumped hung stack trace for this dispatch.
112: private StackTraceElement[] lastReportedStack;
113: // If so; what was the identifying hang number?
114: private int hangNumber;
115:
116: // The EDT for this dispatch (for the purpose of getting stack traces).
117: // I don't know of any API for getting the event dispatch thread,
118: // but we can assume that it's the current thread if we're in the
119: // middle of dispatching an AWT event...
120: // We can't cache this because the EDT can die and be replaced by a
121: // new EDT if there's an uncaught exception.
122: private final Thread eventDispatchThread = Thread
123: .currentThread();
124:
125: // The last time in milliseconds at which we saw a dispatch on the above thread.
126: private long lastDispatchTimeMillis = System
127: .currentTimeMillis();
128:
129: public DispatchInfo() {
130: // All initialization is done by the field initializers.
131: }
132:
133: public void checkForHang() {
134: if (timeSoFar() > getMinimalMonitoredHangTime()) {
135: examineHang();
136: }
137: }
138:
139: // We can't use StackTraceElement.equals because that insists on checking the filename and line number.
140: // That would be version-specific.
141: private static boolean stackTraceElementIs(StackTraceElement e,
142: String className, String methodName, boolean isNative) {
143: return e.getClassName().equals(className)
144: && e.getMethodName().equals(methodName)
145: && e.isNativeMethod() == isNative;
146: }
147:
148: // Checks whether the given stack looks like it's waiting for another event.
149: // This relies on JDK implementation details.
150: private boolean isWaitingForNextEvent(
151: StackTraceElement[] currentStack) {
152: return stackTraceElementIs(currentStack[0],
153: "java.lang.Object", "wait", true)
154: && stackTraceElementIs(currentStack[1],
155: "java.lang.Object", "wait", false)
156: && stackTraceElementIs(currentStack[2],
157: "java.awt.EventQueue", "getNextEvent",
158: false);
159: }
160:
161: private void examineHang() {
162: StackTraceElement[] currentStack = eventDispatchThread
163: .getStackTrace();
164:
165: if (isWaitingForNextEvent(currentStack)) {
166: // Don't be fooled by a modal dialog if it's waiting for its next event.
167: // As long as the modal dialog's event pump doesn't get stuck, it's okay for the outer pump to be suspended.
168: return;
169: }
170:
171: if (stacksEqual(lastReportedStack, currentStack)) {
172: // Don't keep reporting the same hang every time the timer goes off.
173: return;
174: }
175:
176: hangNumber = getNewHangNumber();
177: lastReportedStack = currentStack;
178:
179: String description = MessageFormat.format(
180: "EDT delay for {0}ms", timeSoFar());
181: fireProblem(new Problem(description, currentStack));
182: }
183:
184: private static boolean stacksEqual(StackTraceElement[] a,
185: StackTraceElement[] b) {
186: if (a == null) {
187: return false;
188: }
189: if (a.length != b.length) {
190: return false;
191: }
192: for (int i = 0; i < a.length; ++i) {
193: if (a[i].equals(b[i]) == false) {
194: return false;
195: }
196: }
197: return true;
198: }
199:
200: /**
201: * Returns how long this dispatch has been going on (in milliseconds).
202: */
203: private long timeSoFar() {
204: return (System.currentTimeMillis() - lastDispatchTimeMillis);
205: }
206:
207: public void dispose() {
208: if (lastReportedStack != null) {
209: String description = MessageFormat.format(
210: "EDT delay end after {0}ms", timeSoFar());
211: fireProblem(new Problem(description, lastReportedStack));
212: }
213: }
214:
215: private void fireProblem(Problem problem) {
216: if (problemListener != null) {
217: problemListener.problemOccured(problem);
218: }
219: }
220: }
221:
222: private EDTDebugQueue() {
223: initTimer();
224: }
225:
226: /**
227: * Sets up a timer to check for hangs frequently.
228: */
229: private void initTimer() {
230: final long initialDelayMs = 0;
231: final boolean isDaemon = true;
232: Timer timer = new Timer("EventDispatchThreadHangMonitor",
233: isDaemon);
234: timer.schedule(new HangChecker(), initialDelayMs,
235: CHECK_INTERVAL_MS);
236: }
237:
238: private class HangChecker extends TimerTask {
239: @Override
240: public void run() {
241: synchronized (dispatches) {
242: if (dispatches.isEmpty() || !haveShownSomeComponent) {
243: // Nothing to do.
244: // We don't destroy the timer when there's nothing happening
245: // because it would mean a lot more work on every single AWT
246: // event that gets dispatched.
247: return;
248: }
249: // Only the most recent dispatch can be hung; nested dispatches
250: // by their nature cause the outer dispatch pump to be suspended.
251: dispatches.getLast().checkForHang();
252: }
253: }
254: }
255:
256: /**
257: * Overrides EventQueue.dispatchEvent to call our pre and post hooks either
258: * side of the system's event dispatch code.
259: */
260: @Override
261: protected void dispatchEvent(AWTEvent event) {
262: try {
263: preDispatchEvent();
264: super .dispatchEvent(event);
265: } finally {
266: postDispatchEvent();
267: if (!haveShownSomeComponent && event instanceof WindowEvent
268: && event.getID() == WindowEvent.WINDOW_OPENED) {
269: haveShownSomeComponent = true;
270: }
271: }
272: }
273:
274: private void debug(String which) {
275: if (false) {
276: for (int i = dispatches.size(); i >= 0; --i) {
277: System.out.print(' ');
278: }
279: System.out.println(which);
280: }
281: }
282:
283: /**
284: * Starts tracking a dispatch.
285: */
286: private synchronized void preDispatchEvent() {
287: debug("pre");
288: synchronized (dispatches) {
289: dispatches.addLast(new DispatchInfo());
290: }
291: }
292:
293: /**
294: * Stops tracking a dispatch.
295: */
296: private synchronized void postDispatchEvent() {
297: synchronized (dispatches) {
298: // We've finished the most nested dispatch, and don't need it any longer.
299: DispatchInfo justFinishedDispatch = dispatches.removeLast();
300: justFinishedDispatch.dispose();
301:
302: // The other dispatches, which have been waiting, need to be credited extra time.
303: // We do this rather simplistically by pretending they've just been redispatched.
304: Thread currentEventDispatchThread = Thread.currentThread();
305: for (DispatchInfo dispatchInfo : dispatches) {
306: if (dispatchInfo.eventDispatchThread == currentEventDispatchThread) {
307: dispatchInfo.lastDispatchTimeMillis = System
308: .currentTimeMillis();
309: }
310: }
311: }
312: debug("post");
313: }
314:
315: private static String stackTraceToString(
316: StackTraceElement[] stackTrace) {
317: StringBuilder result = new StringBuilder();
318: // We used to avoid showing any code above where this class gets
319: // involved in event dispatch, but that hides potentially useful
320: // information when dealing with modal dialogs. Maybe we should
321: // reinstate that, but search from the other end of the stack?
322: for (StackTraceElement stackTraceElement : stackTrace) {
323: String indentation = " ";
324: result.append("\n" + indentation + stackTraceElement);
325: }
326: return result.toString();
327: }
328:
329: private synchronized static int getNewHangNumber() {
330: return ++hangCount;
331: }
332: }
|