Bug 552328 - Debug view hangs UI for extended periods

- throttle thread name change events dispatching by max 1 second
- discard all but one name change events on same thread appearing during
wait time

Change-Id: I973fdb321f0e3bce3ad53e1f159fa1da39bf4f0b
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java
index 5ed7a3d..b707948 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/breakpoints/ThreadNameChangeTests.java
@@ -84,7 +84,7 @@
 			events.get().clear();
 
 			resumeToLineBreakpoint(thread, bp2);
-			TestUtil.waitForJobs(getName(), 100, 3000);
+			TestUtil.waitForJobs(getName(), 1000, 3000);
 
 			// expect one single "CHANGE" event for second thread
 			List<DebugEvent> changeEvents = getStateChangeEvents(events, second);
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java
index 4002c5e..96ab09a 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.java
@@ -61,6 +61,7 @@
 	public static String JDIDebugTarget_Unable_to_create_class_prepare_request__3;
 	public static String JDIDebugTarget_Unable_to_retrieve_types___VM_disconnected__4;
 	public static String JDIDebugTarget_0;
+	public static String JDIDebugTarget_ThreadNameNotifier;
 
 	public static String JDIFieldVariable_exception_modifying_value;
 	public static String JDIFieldVariable_exception_retrieving_field_name;
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties
index c110c4d..6c32a5f 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugModelMessages.properties
@@ -207,3 +207,4 @@
 JDIDebugTarget_1=Error retrieving top level thread groups
 JDIDebugTarget_2=Unable to retrieve name
 JDIDebugTarget_4=Unable to retrieve version
+JDIDebugTarget_ThreadNameNotifier=Thread name change notifier
diff --git a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
index cd34bb4..bb604cc 100644
--- a/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
+++ b/org.eclipse.jdt.debug/model/org/eclipse/jdt/internal/debug/core/model/JDIDebugTarget.java
@@ -2391,12 +2391,14 @@
 		private static final String METHOD_SIGNATURE = "(Ljava/lang/String;)V"; //$NON-NLS-1$
 
 		private EventRequest request;
+		private ThreadChangeNotifierJob notfierJob;
 
 		ThreadNameChangeHandler() {
 			String disableListenerSystemProperty = System.getProperty(DISABLE_THREAD_NAME_CHANGE_LISTENER);
 			boolean isDisabled = String.valueOf(Boolean.TRUE).equals(disableListenerSystemProperty);
 			if (!isDisabled) {
 				createRequest();
+				notfierJob = new ThreadChangeNotifierJob();
 			}
 		}
 
@@ -2450,6 +2452,9 @@
 			if (request != null) {
 				removeJDIEventListener(this, request);
 			}
+			if (notfierJob != null) {
+				notfierJob.stop();
+			}
 		}
 
 		@Override
@@ -2461,7 +2466,7 @@
 			}
 			if (thread != null) {
 				// trigger updates on the thread
-				DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { new DebugEvent(thread, DebugEvent.CHANGE, DebugEvent.STATE) });
+				notfierJob.notifyAboutChange(thread);
 			}
 			// we never suspend the thread
 			return true;
@@ -2484,6 +2489,59 @@
 		}
 	}
 
+	/**
+	 * Job to throttle thread name change events notification.
+	 */
+	class ThreadChangeNotifierJob extends Job {
+
+		private final LinkedHashSet<JDIThread> queue;
+
+		public ThreadChangeNotifierJob() {
+			super(JDIDebugModelMessages.JDIDebugTarget_ThreadNameNotifier);
+			setSystem(true);
+			setPriority(Job.DECORATE);
+			queue = new LinkedHashSet<>();
+		}
+
+		public void notifyAboutChange(JDIThread thread) {
+			synchronized (queue) {
+				if (queue.add(thread)) {
+					// if there are too many threads changing names, they may slow down debugger
+					int delay = Math.min(1000, 300 * queue.size());
+					schedule(delay);
+				}
+			}
+		}
+
+		void stop() {
+			synchronized (queue) {
+				queue.clear();
+			}
+			cancel();
+		}
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			DebugEvent[] events;
+			synchronized (queue) {
+				events = queue.stream().map(t -> new DebugEvent(t, DebugEvent.CHANGE, DebugEvent.STATE)).toArray(DebugEvent[]::new);
+				queue.clear();
+			}
+			if (monitor.isCanceled()) {
+				return Status.CANCEL_STATUS;
+			}
+			// Dispatch known events
+			DebugPlugin.getDefault().fireDebugEventSet(events);
+			return Status.OK_STATUS;
+		}
+
+
+		@Override
+		public boolean belongsTo(Object family) {
+			return family == JDIDebugTarget.this;
+		}
+	}
+
 	class CleanUpJob extends Job {
 
 		/**