diff options
15 files changed, 660 insertions, 15 deletions
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java index 2de29b8730..451abe9f7d 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java @@ -4164,13 +4164,36 @@ void sendEvent (EventTable table, Event event) { try { sendEventCount++; if (!filterEvent (event)) { - if (table != null) table.sendEvent (event); + if (table != null) { + sendPreEvent(event); + try { + table.sendEvent (event); + } finally { + sendPostEvent(event); + } + } } } finally { sendEventCount--; } } +void sendPreEvent(Event event) { + if (event == null || (event.type != SWT.PreEvent && event.type != SWT.PostEvent)) { + if (this.eventTable != null && this.eventTable.hooks(SWT.PreEvent)) { + sendEvent(SWT.PreEvent, null); + } + } +} + +void sendPostEvent(Event event) { + if (event == null || (event.type != SWT.PreEvent && event.type != SWT.PostEvent)) { + if (this.eventTable != null && this.eventTable.hooks(SWT.PostEvent)) { + sendEvent(SWT.PostEvent, null); + } + } +} + static NSString getApplicationName() { NSString name = null; int pid = OS.getpid (); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java index fbad42d760..38462dfca2 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/SWT.java @@ -770,6 +770,28 @@ public class SWT { * @since 3.8 */ public static final int Segments = 49; + + /** + * The PreEvent event type (value is 50). + * + * <p> + * This event is sent before an event is dispatched. + * </p> + * + * @since 3.103 + */ + public static final int PreEvent = 50; + + /** + * The PostEvent event type (value is 51). + * + * <p> + * This event is sent after an event is dispatched. + * </p> + * + * @since 3.103 + */ + public static final int PostEvent = 51; /* Event Details */ diff --git a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Synchronizer.java b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Synchronizer.java index 080871c428..87916c0198 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Synchronizer.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/widgets/Synchronizer.java @@ -14,7 +14,7 @@ package org.eclipse.swt.widgets; import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.Compatibility; - + /** * Instances of this class provide synchronization support * for displays. A default instance is created automatically @@ -25,7 +25,7 @@ import org.eclipse.swt.internal.Compatibility; * needs to deal with this class. It is provided only to * allow applications which require non-standard * synchronization behavior to plug in the support they - * require. <em>Subclasses which override the methods in + * require. <em>Subclasses which override the methods in * this class must ensure that the superclass methods are * invoked in their implementations</em> * </p> @@ -49,13 +49,13 @@ public class Synchronizer { /** * Constructs a new instance of this class. - * + * * @param display the display to create the synchronizer on */ public Synchronizer (Display display) { this.display = display; } - + void addLast (RunnableLock lock) { boolean wake = false; synchronized (messageLock) { @@ -67,14 +67,14 @@ void addLast (RunnableLock lock) { } messages [messageCount++] = lock; wake = messageCount == 1; - } + } if (wake) display.wakeThread (); } /** * Causes the <code>run()</code> method of the runnable to - * be invoked by the user-interface thread at the next - * reasonable opportunity. The caller of this method continues + * be invoked by the user-interface thread at the next + * reasonable opportunity. The caller of this method continues * to run in parallel, and is not notified when the * runnable has completed. * @@ -131,12 +131,14 @@ boolean runAsyncMessages (boolean all) { run = true; synchronized (lock) { syncThread = lock.thread; + display.sendPreEvent(null); try { - lock.run (); + lock.run(); } catch (Throwable t) { lock.throwable = t; SWT.error (SWT.ERROR_FAILED_EXEC, t); } finally { + display.sendPostEvent(null); syncThread = null; lock.notifyAll (); } @@ -147,7 +149,7 @@ boolean runAsyncMessages (boolean all) { /** * Causes the <code>run()</code> method of the runnable to - * be invoked by the user-interface thread at the next + * be invoked by the user-interface thread at the next * reasonable opportunity. The thread which calls this method * is suspended until the runnable completes. * @@ -177,7 +179,14 @@ protected void syncExec (Runnable runnable) { } } if (lock == null) { - if (runnable != null) runnable.run (); + if (runnable != null) { + display.sendPreEvent(null); + try { + runnable.run(); + } finally { + display.sendPostEvent(null); + } + } return; } synchronized (lock) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index cf8f092123..d16b403b05 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -4380,7 +4380,32 @@ void sendEvent (int eventType, Event event) { event.type = eventType; if (event.time == 0) event.time = getLastEventTime (); if (!filterEvent (event)) { - if (eventTable != null) eventTable.sendEvent (event); + if (eventTable != null) sendEvent(eventTable, event); + } +} + +void sendEvent(EventTable eventTable, Event event) { + sendPreEvent(event); + try { + eventTable.sendEvent (event); + } finally { + sendPostEvent(event); + } +} + +void sendPreEvent(Event event) { + if (event == null || (event.type != SWT.PreEvent && event.type != SWT.PostEvent)) { + if (this.eventTable != null && this.eventTable.hooks(SWT.PreEvent)) { + sendEvent(SWT.PreEvent, null); + } + } +} + +void sendPostEvent(Event event) { + if (event == null || (event.type != SWT.PreEvent && event.type != SWT.PostEvent)) { + if (this.eventTable != null && this.eventTable.hooks(SWT.PostEvent)) { + sendEvent(SWT.PostEvent, null); + } } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java index 95492b9677..8b16a21efb 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java @@ -1384,7 +1384,7 @@ public void removeDisposeListener (DisposeListener listener) { void sendEvent (Event event) { Display display = event.display; if (!display.filterEvent (event)) { - if (eventTable != null) eventTable.sendEvent (event); + if (eventTable != null) display.sendEvent(eventTable, event); } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java index b9c45043d7..54bce596db 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java @@ -4341,10 +4341,34 @@ void sendEvent (int eventType, Event event) { event.type = eventType; if (event.time == 0) event.time = getLastEventTime (); if (!filterEvent (event)) { - if (eventTable != null) eventTable.sendEvent (event); + if (eventTable != null) sendEvent(eventTable, event); } } +void sendEvent(EventTable eventTable, Event event) { + sendPreEvent(event); + try { + eventTable.sendEvent (event); + } finally { + sendPostEvent(event); + } +} + +void sendPreEvent(Event event) { + if (event == null || (event.type != SWT.PreEvent && event.type != SWT.PostEvent)) { + if (this.eventTable != null && this.eventTable.hooks(SWT.PreEvent)) { + sendEvent(SWT.PreEvent, null); + } + } +} + +void sendPostEvent(Event event) { + if (event == null || (event.type != SWT.PreEvent && event.type != SWT.PostEvent)) { + if (this.eventTable != null && this.eventTable.hooks(SWT.PostEvent)) { + sendEvent(SWT.PostEvent, null); + } + } +} /** * Sets the location of the on-screen pointer relative to the top left corner * of the screen. <b>Note: It is typically considered bad practice for a diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java index 365fc237aa..7530bf8033 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Widget.java @@ -1055,7 +1055,7 @@ boolean sendDragEvent (int button, int stateMask, int x, int y) { void sendEvent (Event event) { Display display = event.display; if (!display.filterEvent (event)) { - if (eventTable != null) eventTable.sendEvent (event); + if (eventTable != null) display.sendEvent(eventTable, event); } } diff --git a/examples/org.eclipse.swt.examples.watchdog/.classpath b/examples/org.eclipse.swt.examples.watchdog/.classpath new file mode 100644 index 0000000000..64c5e31b7a --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/examples/org.eclipse.swt.examples.watchdog/.project b/examples/org.eclipse.swt.examples.watchdog/.project new file mode 100644 index 0000000000..00b90f38e5 --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.swt.examples.watchdog</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/examples/org.eclipse.swt.examples.watchdog/META-INF/MANIFEST.MF b/examples/org.eclipse.swt.examples.watchdog/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..48230ae2cd --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Event Watchdog +Bundle-SymbolicName: org.eclipse.swt.examples.watchdog;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.swt.examples.watchdog.WatchdogPlugin +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-ActivationPolicy: lazy +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.swt, + org.eclipse.ui.workbench diff --git a/examples/org.eclipse.swt.examples.watchdog/build.properties b/examples/org.eclipse.swt.examples.watchdog/build.properties new file mode 100644 index 0000000000..34d2e4d2da --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/examples/org.eclipse.swt.examples.watchdog/plugin.xml b/examples/org.eclipse.swt.examples.watchdog/plugin.xml new file mode 100644 index 0000000000..e3281e757f --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/plugin.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.4"?> +<plugin> + <extension point="org.eclipse.ui.startup"> + <startup class="org.eclipse.swt.examples.watchdog.WatchdogPlugin"/> + </extension> +</plugin> diff --git a/examples/org.eclipse.swt.examples.watchdog/src/org/eclipse/swt/examples/watchdog/TimedEventWatchdog.java b/examples/org.eclipse.swt.examples.watchdog/src/org/eclipse/swt/examples/watchdog/TimedEventWatchdog.java new file mode 100644 index 0000000000..c09ea6babe --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/src/org/eclipse/swt/examples/watchdog/TimedEventWatchdog.java @@ -0,0 +1,351 @@ +/******************************************************************************* + * Copyright (c) 2013, Google Inc. 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: + * Google Inc - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.examples.watchdog; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Synchronizer; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; + +/** + * This watchdog uses a {@link Timer} to take a stack trace during long events, trying to catch a + * single long-running event in action. + */ +class TimedEventWatchdog implements Listener { + /** + * Information captured about a set of events. + * <p> + * When a set contains more than 1 event, it means that additional events were processed + * recursively before the first event finished. If events are invoked recursively and any event + * is longer than a watchdog's threshold, each enclosing event will also be longer than the + * threshold and will be reported as the stack unwinds. + * + * @noextend This class is not intended to be subclassed by clients. + */ + private static class LongEventInfo { + /** + * The sequence number of the first event in this set + */ + public final int startingSequenceNumber; + + /** + * The sequence number of the last event in this set + */ + public final int endingSequenceNumber; + + /** + * The start time of the first event, in milliseconds since 00:00 of 1 January 1970 Z. + * @see System#currentTimeMillis + */ + public final long start; + + /** + * The total duration of all events, in milliseconds + */ + public final long duration; + + /** + * The recursive depth of calls to events when this LongEventInfo was created. + */ + public final int depth; + + /** + * The maximum recursive depth of calls to events during this set of events + */ + public final int maxDepth; + + /** + * Constructs an event snapshot object from a contiguous range of events. + * + * @param startSeqNo the first event in this snapshot + * @param endSeqNo the last event in this snapshot + * @param start the start timestamp in milliseconds since 00:00 of 1 Jan 1970 + * @param duration the duration of the captured events, in milliseconds + * @param depth the depth at which this snapshot started and ended + * @param maxDepth the maximum depth reached by any event captured by this snapshot + */ + public LongEventInfo(int startSeqNo, int endSeqNo, long start, long duration, int depth, + int maxDepth) { + this.startingSequenceNumber = startSeqNo; + this.endingSequenceNumber = endSeqNo; + this.start = start; + this.duration = duration; + this.depth = depth; + this.maxDepth = maxDepth; + } + } + + private static final class StackTrace { + public final Date captureTime; + public final StackTraceElement[] stack; + + public StackTrace(Thread thread, long time_ms) { + captureTime = new Date(time_ms); + stack = thread.getStackTrace(); + } + } + + private class StackNode { + public int startingSequenceNumber = -1; + public long startTime = -1; + } + + private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); + + private static final String EVENT_STR_FORMAT = + "Event #%1$d-#%2$d: %3$dms from %4$s [depth = %5$d, max = %6$d]"; + + private final long dataCollectionDelay; // in milliseconds + private final long threshold_ms; // in milliseconds + + private StackNode[] stack = new StackNode[16]; + + // Count of how many nested {@link Dispatch#readAndDisplay} calls are on the stack + private int recursiveDepth = 0; + + // Sequence numbers should always be >0 so the logged sequence numbers progress in an intuitive + // way and -1 can be used as a sentinel value + private int dispatchSequenceNumber = 1; + + private int maxRecursiveDepth = 0; + + private final Timer timer; + private final TimerTask onTickTask; + private final Thread uiThread; + + private volatile StackTrace stackTrace; + private volatile long grabStackAt; + + public TimedEventWatchdog(Thread uiThread, long threshold_ms) { + if (uiThread == null) { + throw new NullPointerException(); + } + + this.threshold_ms = threshold_ms; + this.dataCollectionDelay = threshold_ms / 2; + + this.uiThread = uiThread; + this.timer = new Timer("Monitoring data collection timer", true); + this.onTickTask = new TimerTask() { + @Override + public void run() { + poll(); + } + }; + + grabStackAt = Long.MAX_VALUE; + timer.scheduleAtFixedRate(onTickTask, 0, Math.max(dataCollectionDelay / 2, 1)); + } + + @Override + public void handleEvent(Event event) { + if (event.type == SWT.PreEvent) { + beginEvent(); + } else if (event.type == SWT.PostEvent) { + endEvent(); + } + } + + /** + * Called at the start of every event, just before invoking the event handler. This function is + * called on the UI thread very frequently and can have a significant impact on performance, so + * it should be as fast as possible. + */ + public void beginEvent() { + int depth = recursiveDepth++; + int seqNo = dispatchSequenceNumber; + seqNo = (seqNo < Integer.MAX_VALUE ? seqNo + 1 : 1); + dispatchSequenceNumber = seqNo; + + if (depth > maxRecursiveDepth) { + maxRecursiveDepth = depth; + } + + StackNode s; + if (depth < stack.length) { + s = stack[depth]; + } else { + s = null; + StackNode[] newStack = new StackNode[2 * depth]; + System.arraycopy(stack, 0, newStack, 0, stack.length); + stack = newStack; + } + + if (s == null) { + stack[depth] = s = new StackNode(); + } + + s.startTime = getTimestamp(); + s.startingSequenceNumber = seqNo; + + // This function gets called very often, so it needs to be fast! + stackTrace = null; + grabStackAt = System.currentTimeMillis() + dataCollectionDelay; // Linearization point + } + + /** + * Called at the end of every event, after that event has returned. This function is called on + * the UI thread very frequently and can have a significant impact on performance, so it + * should be as fast as possible. + */ + public void endEvent() { + if (recursiveDepth == 0) return; + int depth = --recursiveDepth; + + // If a subscriber is added during an asyncExec event (the typical way to subscribe), + // then we don't have any start information collected and stack[depth] will still be + // null, so skip reporting that event. + StackNode s = depth < stack.length ? stack[depth] : null; + if (s != null) { + int duration = (int) (getTimestamp() - s.startTime); + LongEventInfo info = null; + + if (duration >= threshold_ms) { + if (info == null) { + info = new LongEventInfo(s.startingSequenceNumber, + dispatchSequenceNumber, s.startTime, duration, depth, + maxRecursiveDepth); + } + + onLongEvent(info); + } + } + + if (depth == 0) { + maxRecursiveDepth = 0; + } + + // This function gets called very often, so it needs to be fast! + grabStackAt = Long.MAX_VALUE; // Linearization point + } + + private long getTimestamp() { + return System.currentTimeMillis(); + } + + /** + * If the duration of any single event was longer than a client's duration threshold, this + * callback is invoked after the event completes and before {@link #endEvent}. If the event + * completed faster than the threshold, this callback is not invoked. + * <p> + * When an event at depth > 0 is longer than a client's threshold, the enclosing event will + * also be longer and will be reported separately as the call stack unwinds. + * <p> + * This example simply logs stack traces for off-line analysis. + * <p> + * Example output: + * <pre> + * !ENTRY org.eclipse.swt.examples.watchdog 1 0 2013-05-20 11:43:59.253 + * !MESSAGE Event #217-#217: 250ms from 11:43:59.002 [depth = 1, max = 1] + * Trace 11:43:59.150 (+148.0ms) + * org.eclipse.swt.examples.watchdog.WatchdogPlugin.longExample(WatchdogPlugin.java:68) + * org.eclipse.swt.examples.watchdog.WatchdogPlugin$1.run(WatchdogPlugin.java:62) + * org.eclipse.swt.widgets.Synchronizer.instrumentedRun(Synchronizer.java:247) + * org.eclipse.swt.widgets.Synchronizer.syncExec(Synchronizer.java:223) + * org.eclipse.ui.internal.UISynchronizer.syncExec(UISynchronizer.java:150) + * org.eclipse.swt.widgets.Display.syncExec(Display.java:4491) + * org.eclipse.swt.examples.watchdog.WatchdogPlugin$2.run(WatchdogPlugin.java:78) + * ... 27 more + * </pre> + * @param event captured information about the long event + */ + public void onLongEvent(LongEventInfo event) { + grabStackAt = Long.MAX_VALUE; // Linearization point + StackTrace trace = stackTrace; + stackTrace = null; + + if (trace != null) { + String msg = String.format(EVENT_STR_FORMAT, event.startingSequenceNumber, + event.endingSequenceNumber, event.duration, + TIME_FORMAT.format(new Date(event.start)), event.depth, event.maxDepth); + + StringBuilder str = new StringBuilder(msg); + + str.append('\n'); + str.append('\t').append("Trace ").append(TIME_FORMAT.format(trace.captureTime)); + + // Calculate when the stack trace happened relative to the start of the dispatch. + double deltaTimeFromEventStart = trace.captureTime.getTime() - event.start; + String unit = "ms"; + if (deltaTimeFromEventStart > 1000.0) { + deltaTimeFromEventStart /= 1000.0; + unit = "s"; + } + deltaTimeFromEventStart = Math.round(deltaTimeFromEventStart * 10.0) / 10.0; + str.append(" (+").append(deltaTimeFromEventStart).append(unit).append(')').append('\n'); + + final String displayClassName = Display.class.getName(); + final String syncClassName = Synchronizer.class.getName(); + + int numPrinted = 0; + int maxToPrint = -1; + for (StackTraceElement e : trace.stack) { + str.append('\t').append('\t').append(e.toString()).append('\n'); + ++numPrinted; + + // Limit number of stack elements printed to reasonable size + if (traceElementIs(e, displayClassName, "readAndDispatch")) { + maxToPrint = 0; + } else if (traceElementIs(e, displayClassName, "syncExec")) { + maxToPrint = 3; + } else if (traceElementIs(e, syncClassName, "syncExec")) { + maxToPrint = 3; + } + + if (maxToPrint == 0) { + str.append('\t').append('\t').append("... ") + .append(trace.stack.length - numPrinted).append(" more").append('\n'); + break; + } else if (maxToPrint > 0) { + maxToPrint--; + } + } + + WatchdogPlugin.getDefault().getLog().log(new Status(IStatus.INFO, + WatchdogPlugin.getDefault().getBundle().getSymbolicName(), str.toString())); + } + } + + private static boolean traceElementIs(StackTraceElement e, String className, String method) { + return className.equals(e.getClassName()) && method.equals(e.getMethodName()); + } + + /** + * The data-collection is kept very simple for this example to focus on how to use the API + * without adding too much multithreading complexity. + * <p> + * For additional data collection, you could collect multiple stack traces, then log some or + * all of them in {@link #onLongEvent}. The timer can also log if too much time elapses between + * {@link #beginEvent} and {@link #endEvent} to help diagnose UI freezes and potential + * deadlocks. + * <p> + * This example uses {@link Timer} for simplicity. Using Timer does introduce an upper-bound on + * how much data can be collected. More in-depth or precise collection may require replacing + * the {@link Timer}-based polling with a dedicated thread. + */ + private void poll() { + if (stackTrace == null) { + long currTime = System.currentTimeMillis(); + + if (currTime - grabStackAt > 0) { // Linearization point + stackTrace = new StackTrace(uiThread, currTime); + } + } + } +} diff --git a/examples/org.eclipse.swt.examples.watchdog/src/org/eclipse/swt/examples/watchdog/WatchdogPlugin.java b/examples/org.eclipse.swt.examples.watchdog/src/org/eclipse/swt/examples/watchdog/WatchdogPlugin.java new file mode 100644 index 0000000000..4712bab223 --- /dev/null +++ b/examples/org.eclipse.swt.examples.watchdog/src/org/eclipse/swt/examples/watchdog/WatchdogPlugin.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2013, Google Inc. 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: + * Google Inc - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.examples.watchdog; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IStartup; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +public class WatchdogPlugin extends AbstractUIPlugin implements IStartup { + private static final int EVENT_DURATION_THRESHOLD = 1001; + + private static WatchdogPlugin plugin; + private final Display display; + private final TimedEventWatchdog watchdog; + + public WatchdogPlugin() { + super(); + plugin = this; + + IWorkbench workbench = getWorkbench(); + display = (workbench != null) ? workbench.getDisplay() : null; + + Thread displayThread = (display != null) ? display.getThread() : null; + this.watchdog = displayThread != null + ? new TimedEventWatchdog(displayThread, EVENT_DURATION_THRESHOLD) + : null; + } + + @Override + public void earlyStartup() { + if (display != null) { + display.asyncExec(new Runnable() { + @Override + public void run() { + display.addListener(SWT.PreEvent, watchdog); + display.addListener(SWT.PostEvent, watchdog); + } + }); + } + } + + @Override + public void stop(BundleContext context) throws Exception { + try { + if (display != null && !display.isDisposed()) { + display.asyncExec(new Runnable() { + @Override + public void run() { + if (!display.isDisposed()) { + display.removeListener(SWT.PreEvent, watchdog); + display.removeListener(SWT.PostEvent, watchdog); + } + } + }); + } + } finally { + super.stop(context); + } + } + + public static WatchdogPlugin getDefault() { + return plugin; + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Display.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Display.java index 74aebcf65f..b0ff1dbb89 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Display.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Display.java @@ -23,6 +23,7 @@ import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.ILongEventWatchdog; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; @@ -794,6 +795,62 @@ public void test_readAndDispatch() { // test_postLorg_eclipse_swt_widgets_Event() } +public void test_LongEventWatchdog() { + final int DURATION_MILLIS = 10; + Display display = new Display(); + + try { + final boolean[] beginCalled = {false}; + final boolean[] eventSent = {false}; + final boolean[] endCalled = {false}; + final boolean[] eventHasRun = {false}; + + ILongEventWatchdog callback = new ILongEventWatchdog() { + public void beginEvent(int depth) { + beginCalled[0] = true; + } + + public void onLongEvent(LongEventInfo event) { + eventSent[0] = true; + } + + public void endEvent(int depth) { + endCalled[0] = true; + } + }; + + display.getSynchronizer().registerLongDispatchWatchdogCallback(callback, DURATION_MILLIS); + + display.asyncExec(new Runnable() { + public void run() { + try { + Thread.sleep(DURATION_MILLIS); + } catch (InterruptedException e) { + fail(); + } + eventHasRun[0] = true; + } + }); + + // Detect falling edge of readAndDispatch's return value. It must always go high at least + // once for the runnable this test asyncExec'd. + boolean prevMoreToDispatch = false; + boolean moreToDispatch = false; + + while (!eventHasRun[0] && !(prevMoreToDispatch && !moreToDispatch)) { + prevMoreToDispatch = moreToDispatch; + moreToDispatch = display.readAndDispatch(); + assertTrue(beginCalled[0] == endCalled[0]); + assertTrue(!eventHasRun[0] || eventSent[0]); // eventHasRun[0] -> eventSent[0] + } + + display.getSynchronizer().unregisterLongDispatchWatchdogCallback(callback); + assertTrue(beginCalled[0] && endCalled[0] && eventHasRun[0]); + } finally { + display.dispose(); + } +} + public void test_removeFilterILorg_eclipse_swt_widgets_Listener() { final int CLOSE_CALLBACK = 0; final int DISPOSE_CALLBACK = 1; @@ -1142,6 +1199,7 @@ public static java.util.Vector methodNames() { methodNames.addElement("test_timerExecILjava_lang_Runnable"); methodNames.addElement("test_update"); methodNames.addElement("test_wake"); + methodNames.addElement("test_LongEventWatchdog"); methodNames.addAll(Test_org_eclipse_swt_graphics_Device.methodNames()); // add superclass method names return methodNames; } @@ -1185,6 +1243,7 @@ protected void runTest() throws Throwable { else if (getName().equals("test_mapLorg_eclipse_swt_widgets_ControlLorg_eclipse_swt_widgets_ControlLorg_eclipse_swt_graphics_Rectangle")) test_mapLorg_eclipse_swt_widgets_ControlLorg_eclipse_swt_widgets_ControlLorg_eclipse_swt_graphics_Rectangle(); else if (getName().equals("test_postLorg_eclipse_swt_widgets_Event")) test_postLorg_eclipse_swt_widgets_Event(); else if (getName().equals("test_readAndDispatch")) test_readAndDispatch(); + else if (getName().equals("test_LongEventWatchdog")) test_LongEventWatchdog(); else if (getName().equals("test_removeFilterILorg_eclipse_swt_widgets_Listener")) test_removeFilterILorg_eclipse_swt_widgets_Listener(); else if (getName().equals("test_removeListenerILorg_eclipse_swt_widgets_Listener")) test_removeListenerILorg_eclipse_swt_widgets_Listener(); else if (getName().equals("test_setAppNameLjava_lang_String")) test_setAppNameLjava_lang_String(); |