Task Launcher - fixed synchronization issues

This commit is contained in:
dragonmacher 2019-05-23 17:19:29 -04:00
parent d74c392101
commit 3765dd0afe
9 changed files with 54 additions and 104 deletions

View file

@ -28,8 +28,9 @@ import ghidra.util.exception.CancelledException;
* We wish for this class to be performant. Thus, we do not synchronize the methods of this * We wish for this class to be performant. Thus, we do not synchronize the methods of this
* class, nor do we make the values thread visible via <code>volatile</code> or by any of * class, nor do we make the values thread visible via <code>volatile</code> or by any of
* the Java concurrent structures (e.g., {@link AtomicBoolean}). In order to keep the values of * the Java concurrent structures (e.g., {@link AtomicBoolean}). In order to keep the values of
* this class's fields update-to-date, we have chosen to synchronize the <code>getter</code> * this class's fields update-to-date, we have chosen to synchronize the package-level client of
* methods. Thus, when the fields are read, the most recent version will be used. * this class. <b>If this class is ever made public, then most of the methods herein need to
* be synchronized to prevent race conditions and to provide visibility.
*/ */
class BasicTaskMonitor implements TaskMonitor { class BasicTaskMonitor implements TaskMonitor {
@ -98,7 +99,7 @@ class BasicTaskMonitor implements TaskMonitor {
} }
@Override @Override
public synchronized void setMaximum(long max) { public void setMaximum(long max) {
this.maxProgress = max; this.maxProgress = max;
if (progress > max) { if (progress > max) {
progress = max; progress = max;
@ -125,7 +126,7 @@ class BasicTaskMonitor implements TaskMonitor {
boolean wasCancelled = isCancelled; boolean wasCancelled = isCancelled;
isCancelled = true; isCancelled = true;
if (!wasCancelled) { if (!wasCancelled) {
notifyChangeListeners(); notifyCancelledListeners();
} }
} }
@ -149,7 +150,7 @@ class BasicTaskMonitor implements TaskMonitor {
// stub // stub
} }
private synchronized void notifyChangeListeners() { private void notifyCancelledListeners() {
for (CancelledListener listener : listeners) { for (CancelledListener listener : listeners) {
listener.cancelled(); listener.cancelled();
} }

View file

@ -57,7 +57,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
/** If not null, then the value of the string has yet to be rendered */ /** If not null, then the value of the string has yet to be rendered */
private AtomicReference<String> newMessage = new AtomicReference<>(); private AtomicReference<String> newMessage = new AtomicReference<>();
private SwingUpdateManager messageUpdater = private SwingUpdateManager messageUpdater =
new SwingUpdateManager(() -> setStatusText(newMessage.getAndSet(null))); new SwingUpdateManager(100, 250, () -> setStatusText(newMessage.getAndSet(null)));
/** /**
* Constructor * Constructor

View file

@ -28,7 +28,6 @@ import ghidra.util.Swing;
* {@link #TaskLauncher(Task, Component, int, int)}. Alternatively, for simpler uses, * {@link #TaskLauncher(Task, Component, int, int)}. Alternatively, for simpler uses,
* see one of the many static convenience methods. * see one of the many static convenience methods.
* *
* <a name="modal_usage"></a>
* <p><b><a name="modal_usage">Modal Usage</a></b><br> * <p><b><a name="modal_usage">Modal Usage</a></b><br>
* Most clients of this class should not be concerned with where * Most clients of this class should not be concerned with where
* the dialog used by this class will appear. By default, it will be shown over * the dialog used by this class will appear. By default, it will be shown over
@ -237,7 +236,7 @@ public class TaskLauncher {
* @throws IllegalStateException if the given thread is the Swing thread * @throws IllegalStateException if the given thread is the Swing thread
*/ */
protected void runInThisBackgroundThread(Task task) { protected void runInThisBackgroundThread(Task task) {
if (Swing.isEventDispatchThread()) { if (Swing.isSwingThread()) {
throw new IllegalStateException("Must not call this method from the Swing thread"); throw new IllegalStateException("Must not call this method from the Swing thread");
} }

View file

@ -16,9 +16,7 @@
package ghidra.util.task; package ghidra.util.task;
import java.awt.Component; import java.awt.Component;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import generic.concurrent.GThreadPool; import generic.concurrent.GThreadPool;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
@ -35,17 +33,7 @@ class TaskRunner {
private int delayMs; private int delayMs;
private int dialogWidth; private int dialogWidth;
private Thread taskThread; private TaskDialog taskDialog;
private CancelledListener monitorChangeListener = () -> {
if (task.isInterruptible()) {
taskThread.interrupt();
}
if (task.isForgettable()) {
closeDialog(); // close the dialog and forget about the task
}
};
private AtomicReference<TaskDialog> taskDialog = new AtomicReference<>();
private CountDownLatch finished = new CountDownLatch(1); private CountDownLatch finished = new CountDownLatch(1);
TaskRunner(Task task, Component parent, int delayMs, int dialogWidth) { TaskRunner(Task task, Component parent, int delayMs, int dialogWidth) {
@ -67,17 +55,18 @@ class TaskRunner {
private void showDialogIfSwingOrShowLaterAndWait(WrappingTaskMonitor monitor) { private void showDialogIfSwingOrShowLaterAndWait(WrappingTaskMonitor monitor) {
Swing.runIfSwingOrRunLater(() -> showTaskDialog(monitor)); Swing.runIfSwingOrRunLater(() -> showTaskDialog(monitor));
waitForModalIfNotSwing(); waitForModalTask();
} }
private void waitForModalIfNotSwing() { private void waitForModalTask() {
if (Swing.isSwingThread() || !task.isModal()) {
// if this is the Swing thread, then the work is already done at this point; otherwise, if (!task.isModal()) {
// the task is not modal, so do not block return; // we do not wait for non-modal tasks
return;
} }
try { try {
// fun note: if this is the Swing thread, then it will not wait, as the Swing thread
// was blocked by the modal dialog in the call before this one
finished.await(); finished.await();
} }
catch (InterruptedException e) { catch (InterruptedException e) {
@ -86,39 +75,14 @@ class TaskRunner {
} }
// protected to allow for dependency injection // protected to allow for dependency injection
protected TaskDialog buildTaskDialog(Component comp) { protected TaskDialog buildTaskDialog(Component comp, TaskMonitor monitor) {
//
// This class may be used by background threads. Make sure that our GUI creation is
// on the Swing thread to prevent exceptions while painting (as seen when using the
// Nimbus Look and Feel).
//
TaskDialog dialog = createTaskDialog(comp); TaskDialog dialog = createTaskDialog(comp);
dialog.setMinimumSize(dialogWidth, 0); dialog.setMinimumSize(dialogWidth, 0);
if (task.isInterruptible() || task.isForgettable()) {
dialog.addCancelledListener(monitorChangeListener);
}
dialog.setStatusJustification(task.getStatusTextAlignment()); dialog.setStatusJustification(task.getStatusTextAlignment());
return dialog; return dialog;
} }
private void showTaskDialog(WrappingTaskMonitor monitor) {
Swing.assertThisIsTheSwingThread("Must be on the Swing thread build the Task Dialog");
if (finished.getCount() == 0) {
return;
}
TaskDialog dialog = buildTaskDialog(parent);
taskDialog.set(dialog);
monitor.setDelegate(dialog); // initialize the dialog to the current state of the monitor
dialog.show(Math.max(delayMs, 0));
}
private void startBackgroundThread(TaskMonitor monitor) { private void startBackgroundThread(TaskMonitor monitor) {
// add the task here, so we can track it before it is actually started by the thread // add the task here, so we can track it before it is actually started by the thread
@ -139,21 +103,6 @@ class TaskRunner {
}); });
} }
private void taskFinished() {
finished.countDown();
TaskDialog dialog = taskDialog.get();
if (dialog != null) {
dialog.taskProcessed();
}
}
private void closeDialog() {
TaskDialog dialog = taskDialog.get();
if (dialog != null) {
dialog.close();
}
}
private TaskDialog createTaskDialog(Component comp) { private TaskDialog createTaskDialog(Component comp) {
Component currentParent = comp; Component currentParent = comp;
if (currentParent != null) { if (currentParent != null) {
@ -165,4 +114,36 @@ class TaskRunner {
} }
return new TaskDialog(comp, task); return new TaskDialog(comp, task);
} }
private void showTaskDialog(WrappingTaskMonitor monitor) {
Swing.assertThisIsTheSwingThread("Must be on the Swing thread build the Task Dialog");
taskDialog = buildTaskDialog(parent, monitor);
monitor.setDelegate(taskDialog); // initialize the dialog to the current state of the monitor
int delay = Math.max(delayMs, 0);
try {
if (finished.await(delay, TimeUnit.MILLISECONDS)) {
return;
}
}
catch (InterruptedException e) {
// not sure about this case; proceed to show the dialog
}
taskDialog.show(delay);
}
private void taskFinished() {
finished.countDown();
// Do this later on the Swing thread to handle the race condition where the dialog
// did not exist at the time of this call, but was in the process of being created
Swing.runLater(() -> {
if (taskDialog != null) {
taskDialog.taskProcessed();
}
});
}
} }

View file

@ -105,7 +105,7 @@ public class AbstractTaskTest extends AbstractDockingTest {
return new TaskRunner(task, parent, delay, dialogWidth) { return new TaskRunner(task, parent, delay, dialogWidth) {
@Override @Override
protected TaskDialog buildTaskDialog(Component comp) { protected TaskDialog buildTaskDialog(Component comp, TaskMonitor monitor) {
dialogSpy = new TaskDialogSpy(task); dialogSpy = new TaskDialogSpy(task);
return dialogSpy; return dialogSpy;
} }

View file

@ -111,5 +111,4 @@ public class TaskDialogTest extends AbstractTaskTest {
assertFalse(dialogSpy.isCancelEnabled()); assertFalse(dialogSpy.isCancelEnabled());
} }
} }

View file

@ -220,28 +220,6 @@ public abstract class Task implements MonitoredRunnable {
return isModal; return isModal;
} }
public boolean isInterruptible() {
return isInterruptible;
}
public void setInterruptible(boolean interruptible) {
this.isInterruptible = interruptible;
}
/**
* Returns true if this task should be left alone to die when cancelled, as opposed to being
* interrupted
*
* @return true if forgettable
*/
public boolean isForgettable() {
return isForgettable;
}
public void setForgettable(boolean isForgettable) {
this.isForgettable = isForgettable;
}
/** /**
* Sets the task listener on this task. It is a programming error to call this method more * Sets the task listener on this task. It is a programming error to call this method more
* than once or to call this method if a listener was passed into the constructor of this class. * than once or to call this method if a listener was passed into the constructor of this class.

View file

@ -61,7 +61,7 @@ public class Swing {
* *
* @return true if this is the event dispatch thread -OR- is in headless mode. * @return true if this is the event dispatch thread -OR- is in headless mode.
*/ */
public static boolean isEventDispatchThread() { public static boolean isSwingThread() {
if (isInHeadlessMode()) { if (isInHeadlessMode()) {
return true; return true;
} }
@ -70,14 +70,6 @@ public class Swing {
return SwingUtilities.isEventDispatchThread(); return SwingUtilities.isEventDispatchThread();
} }
/**
* A convenience method for {@link #isEventDispatchThread()}
* @return true if this is the Swing thread
*/
public static boolean isSwingThread() {
return isEventDispatchThread();
}
/** /**
* Wait until AWT event queue (Swing) has been flushed and no more (to a point) events * Wait until AWT event queue (Swing) has been flushed and no more (to a point) events
* are pending. * are pending.
@ -102,7 +94,7 @@ public class Swing {
return; // squash during production mode return; // squash during production mode
} }
if (!isEventDispatchThread()) { if (!isSwingThread()) {
Throwable t = Throwable t =
ReflectionUtilities.filterJavaThrowable(new AssertException(errorMessage)); ReflectionUtilities.filterJavaThrowable(new AssertException(errorMessage));
Msg.error(SystemUtilities.class, errorMessage, t); Msg.error(SystemUtilities.class, errorMessage, t);

View file

@ -422,7 +422,7 @@ public class SystemUtilities {
* @return true if this is the event dispatch thread -OR- is in headless mode. * @return true if this is the event dispatch thread -OR- is in headless mode.
*/ */
public static boolean isEventDispatchThread() { public static boolean isEventDispatchThread() {
return Swing.isEventDispatchThread(); return Swing.isSwingThread();
} }
/** /**