From 8dffd377fb0e587cec6011b8f9c91a9a309733c0 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 16 May 2019 15:42:24 -0400 Subject: [PATCH 1/6] GT-2875 - Unswingable - Code to allow task launching to give-up on the Swing thread to prevent deadlocks --- .../task/BackgroundThreadTaskLauncher.java | 43 ++++ .../util/task/NonSwingTaskLauncher.java | 32 +++ .../ghidra/util/task/SwingTaskLauncher.java | 128 +++++++++++ .../java/ghidra/util/task/TaskLauncher.java | 108 +++------ .../ghidra/util/task/AbstractTaskTest.java | 207 ++++++++++++++++++ .../java/ghidra/util/task/TaskDialogSpy.java | 36 +++ .../java/ghidra/util/task/TaskDialogTest.java | 185 +--------------- .../ghidra/util/task/TaskLauncherTest.java | 177 +++++++++++++++ .../java/ghidra/util/SystemUtilities.java | 83 ++++++- .../exception/UnableToSwingException.java | 30 +++ 10 files changed, 776 insertions(+), 253 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java create mode 100644 Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java create mode 100644 Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java create mode 100644 Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java create mode 100644 Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogSpy.java create mode 100644 Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskLauncherTest.java create mode 100644 Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/UnableToSwingException.java diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java new file mode 100644 index 0000000000..679524d418 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java @@ -0,0 +1,43 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.task; + +import ghidra.util.TaskUtilities; + +/** + * Helper class to launch the given task in a background thread This helper will not + * show a task dialog. See {@link TaskLauncher}. + */ +class BackgroundThreadTaskLauncher { + + private Task task; + + BackgroundThreadTaskLauncher(Task task) { + this.task = task; + } + + void run(TaskMonitor monitor) { + // add the task here, so we can track it before it is actually started by the thread + TaskUtilities.addTrackedTask(task, monitor); + + String name = "Task - " + task.getTaskTitle(); + Thread taskThread = new Thread(() -> { + task.monitoredRun(monitor); + }, name); + taskThread.setPriority(Thread.MIN_PRIORITY); + taskThread.start(); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java new file mode 100644 index 0000000000..c0896e55c0 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.task; + +/** + * Helper class to launch the given task in the current thread. See {@link TaskLauncher}. + */ +class CurrentThreadTaskLauncher { + + private Task task; + + CurrentThreadTaskLauncher(Task task) { + this.task = task; + } + + void run() { + task.monitoredRun(TaskMonitor.DUMMY); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java new file mode 100644 index 0000000000..cbc65ddb45 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java @@ -0,0 +1,128 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.task; + +import java.awt.Component; + +import javax.swing.SwingUtilities; + +import generic.util.WindowUtilities; +import ghidra.util.*; + +/** + * Helper class to launch the given task in a background thread, showing a task dialog if + * this task takes to long. See {@link TaskLauncher}. + */ +class SwingTaskLauncher { + + protected Task task; + private Component parent; + private int delay; + private int dialogWidth; + private TaskDialog taskDialog; + private Thread taskThread; + private CancelledListener monitorChangeListener = () -> { + if (task.isInterruptible()) { + taskThread.interrupt(); + } + if (task.isForgettable()) { + taskDialog.close(); // close the dialog and forget about the task + } + }; + + SwingTaskLauncher(Task task, Component parent, int delay, int dialogWidth) { + this.task = task; + this.parent = parent; + this.delay = delay; + this.dialogWidth = dialogWidth; + } + + void run() { + this.taskDialog = buildTaskDialog(parent); + + startBackgroundThread(taskDialog); + + taskDialog.show(Math.max(delay, 0)); + + waitIfNotSwing(); + } + + private void waitIfNotSwing() { + if (SwingUtilities.isEventDispatchThread() || !task.isModal()) { + return; + } + + try { + taskThread.join(); + } + catch (InterruptedException e) { + Msg.debug(this, "Task Launcher unexpectedly interrupted waiting for task thread", e); + } + } + + protected TaskDialog buildTaskDialog(Component comp) { + + // + // 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). + // + SystemUtilities.runSwingNow(() -> { + taskDialog = createTaskDialog(comp); + taskDialog.setMinimumSize(dialogWidth, 0); + }); + + if (task.isInterruptible() || task.isForgettable()) { + taskDialog.addCancelledListener(monitorChangeListener); + } + + taskDialog.setStatusJustification(task.getStatusTextAlignment()); + + return taskDialog; + } + + private void startBackgroundThread(TaskMonitor monitor) { + + // add the task here, so we can track it before it is actually started by the thread + TaskUtilities.addTrackedTask(task, monitor); + + String name = "Task - " + task.getTaskTitle(); + taskThread = new Thread(() -> { + task.monitoredRun(monitor); + taskProcessed(); + }, name); + taskThread.setPriority(Thread.MIN_PRIORITY); + taskThread.start(); + } + + private void taskProcessed() { + if (taskDialog != null) { + taskDialog.taskProcessed(); + } + } + + protected TaskDialog createTaskDialog(Component comp) { + Component currentParent = comp; + if (currentParent != null) { + currentParent = WindowUtilities.windowForComponent(comp); + } + + if (currentParent == null) { + return new TaskDialog(task); + } + return new TaskDialog(comp, task); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java index c3c3ef3104..e7698601e7 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java @@ -16,12 +16,12 @@ package ghidra.util.task; import java.awt.Component; +import java.util.concurrent.TimeUnit; import javax.swing.SwingUtilities; -import generic.util.WindowUtilities; -import ghidra.util.Msg; -import ghidra.util.TaskUtilities; +import ghidra.util.SystemUtilities; +import ghidra.util.exception.UnableToSwingException; /** * Class to initiate a Task in a new Thread, and to show a progress dialog that indicates @@ -168,18 +168,6 @@ public class TaskLauncher { /** The time, for modal tasks, to try and run before blocking and showing a dialog */ public static final int INITIAL_MODAL_DELAY = 500; - protected Task task; - private TaskDialog taskDialog; - private Thread taskThread; - private CancelledListener monitorChangeListener = () -> { - if (task.isInterruptible()) { - taskThread.interrupt(); - } - if (task.isForgettable()) { - taskDialog.close(); // close the dialog and forget about the task - } - }; - private static Component getParent(Component parent) { if (parent == null) { return null; @@ -243,26 +231,11 @@ public class TaskLauncher { */ public TaskLauncher(Task task, Component parent, int delay, int dialogWidth) { - this.task = task; - this.taskDialog = buildTaskDialog(parent, dialogWidth); - - startBackgroundThread(taskDialog); - - taskDialog.show(Math.max(delay, 0)); - - waitForModalIfNotSwing(); - } - - private void waitForModalIfNotSwing() { - if (SwingUtilities.isEventDispatchThread() || !task.isModal()) { - return; - } - try { - taskThread.join(); + runSwing(task, parent, delay, dialogWidth); } - catch (InterruptedException e) { - Msg.debug(this, "Task Launcher unexpectedly interrupted waiting for task thread", e); + catch (UnableToSwingException e) { + runInThisBackgroundThread(task); } } @@ -274,59 +247,50 @@ public class TaskLauncher { *

See notes on modal usage * * @param task task to run in another thread (other than the Swing Thread) - * @param taskMonitor the monitor to use while running the task. + * @param monitor the monitor to use while running the task. */ - public TaskLauncher(Task task, TaskMonitor taskMonitor) { - - this.task = task; - - startBackgroundThread(taskMonitor); - + public TaskLauncher(Task task, TaskMonitor monitor) { + BackgroundThreadTaskLauncher runner = new BackgroundThreadTaskLauncher(task); + runner.run(monitor); } - private TaskDialog buildTaskDialog(Component comp, int dialogWidth) { + private void runSwing(Task task, Component parent, int delay, int dialogWidth) + throws UnableToSwingException { - taskDialog = createTaskDialog(comp); - taskDialog.setMinimumSize(dialogWidth, 0); - - if (task.isInterruptible() || task.isForgettable()) { - taskDialog.addCancelledListener(monitorChangeListener); + SwingTaskLauncher swinger = buildSwingLauncher(task, parent, delay, dialogWidth); + if (SwingUtilities.isEventDispatchThread()) { + swinger.run(); + return; } - taskDialog.setStatusJustification(task.getStatusTextAlignment()); + // + // Not on the Swing thread. Try to execute on the Swing thread, timing-out if it takes + // too long (this prevents deadlocks). + // - return taskDialog; + // This will throw an exception if we could not get the Swing lock. When that happens, + // the task was NOT run. + int timeout = getSwingTimeoutInSeconds(); + SystemUtilities.runSwingNow(() -> { + swinger.run(); + }, timeout, TimeUnit.SECONDS); } - private void startBackgroundThread(TaskMonitor monitor) { - - // add the task here, so we can track it before it is actually started by the thread - TaskUtilities.addTrackedTask(task, monitor); - - String name = "Task - " + task.getTaskTitle(); - taskThread = new Thread(() -> { - task.monitoredRun(monitor); - taskProcessed(); - }, name); - taskThread.setPriority(Thread.MIN_PRIORITY); - taskThread.start(); + protected int getSwingTimeoutInSeconds() { + return 2; } - private void taskProcessed() { - if (taskDialog != null) { - taskDialog.taskProcessed(); - } + protected SwingTaskLauncher buildSwingLauncher(Task task, Component parent, int delay, + int dialogWidth) { + return new SwingTaskLauncher(task, parent, delay, dialogWidth); } - protected TaskDialog createTaskDialog(Component comp) { - Component parent = comp; - if (parent != null) { - parent = WindowUtilities.windowForComponent(comp); + protected void runInThisBackgroundThread(Task task) { + if (SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException("Must not call this method from the Swing thread"); } - if (parent == null) { - return new TaskDialog(task); - } - return new TaskDialog(comp, task); + CurrentThreadTaskLauncher runner = new CurrentThreadTaskLauncher(task); + runner.run(); } } diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java new file mode 100644 index 0000000000..32a77bb55e --- /dev/null +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java @@ -0,0 +1,207 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.task; + +import static org.junit.Assert.*; + +import java.awt.Component; +import java.util.Deque; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import docking.test.AbstractDockingTest; + +public class AbstractTaskTest extends AbstractDockingTest { + + protected static final int DELAY_FAST = 10; + protected static final int DELAY_SLOW = 100; + protected static final int DELAY_LAUNCHER = DELAY_FAST * 2; + + // 2 - 1 for the task itself; 1 for the launcher + protected CountDownLatch threadsFinished = new CountDownLatch(2); + protected Deque eventQueue = new LinkedBlockingDeque<>(); + + protected volatile TaskLauncherSpy taskLauncherSpy; + protected volatile TaskDialogSpy dialogSpy; + protected AtomicBoolean didRunInBackground = new AtomicBoolean(); + + protected void assertDidNotRunInSwing() { + for (TDEvent e : eventQueue) { + assertFalse(e.getThreadName().contains("AWT")); + } + } + + protected void assertRanInSwingThread() { + assertFalse("Task was not run in the Swing thread", didRunInBackground.get()); + } + + protected void assertSwingThreadBlockedForTask() { + TDEvent lastEvent = eventQueue.peekLast(); + boolean swingIsLast = lastEvent.getThreadName().contains("AWT"); + if (!swingIsLast) { + fail("The Swing thread did not block until the task finished"); + } + } + + protected void assertSwingThreadFinishedBeforeTask() { + TDEvent lastEvent = eventQueue.peekLast(); + boolean swingIsLast = lastEvent.getThreadName().contains("AWT"); + if (swingIsLast) { + fail("The Swing thread blocked until the task finished"); + } + } + + protected void waitForTask() throws Exception { + threadsFinished.await(2, TimeUnit.SECONDS); + } + + protected void launchTask(Task task) { + launchTaskFromSwing(task); + } + + protected void launchTaskFromSwing(Task task) { + + runSwing(() -> { + taskLauncherSpy = new TaskLauncherSpy(task); + postEvent("After task launcher"); + threadsFinished.countDown(); + }); + } + + protected void postEvent(String message) { + eventQueue.add(new TDEvent(message)); + } + + protected class TaskLauncherSpy extends TaskLauncher { + + public TaskLauncherSpy(Task task) { + super(task, null, DELAY_LAUNCHER); + } + + @Override + protected SwingTaskLauncher buildSwingLauncher(Task task, Component parent, int delay, + int dialogWidth) { + + return new SwingTaskLauncher(task, parent, delay, dialogWidth) { + @Override + protected TaskDialog buildTaskDialog(Component comp) { + dialogSpy = new TaskDialogSpy(task); + return dialogSpy; + } + }; + } + + @Override + protected int getSwingTimeoutInSeconds() { + return 1; // speed-up for tests + } + + @Override + protected void runInThisBackgroundThread(Task task) { + didRunInBackground.set(true); + super.runInThisBackgroundThread(task); + } + + TaskDialogSpy getDialogSpy() { + return dialogSpy; + } + + boolean didRunInBackground() { + return didRunInBackground.get(); + } + } + + protected class FastModalTask extends Task { + + public FastModalTask() { + super("Fast Modal Task", true, true, true); + } + + @Override + public void run(TaskMonitor monitor) { + postEvent(getName() + " started..."); + sleep(DELAY_FAST); + threadsFinished.countDown(); + postEvent(getName() + " finished."); + } + } + + protected class FastNonModalTask extends Task { + + public FastNonModalTask() { + super("Fast Non-modal Task", true, true, false); + } + + @Override + public void run(TaskMonitor monitor) { + postEvent(getName() + " started..."); + sleep(DELAY_FAST); + postEvent(getName() + " finished."); + threadsFinished.countDown(); + } + } + + protected class SlowModalTask extends Task { + + public SlowModalTask() { + super("Slow Modal Task", true, true, true); + } + + @Override + public void run(TaskMonitor monitor) { + postEvent(getName() + " started..."); + sleep(DELAY_SLOW); + threadsFinished.countDown(); + postEvent(getName() + " finished."); + } + } + + protected class SlowNonModalTask extends Task { + + public SlowNonModalTask() { + super("Slow Non-modal Task", true, true, false); + } + + @Override + public void run(TaskMonitor monitor) { + postEvent(getName() + " started..."); + sleep(DELAY_SLOW); + threadsFinished.countDown(); + postEvent(getName() + " finished."); + } + } + + protected class TDEvent { + + protected String threadName = Thread.currentThread().getName(); + protected String message; + + TDEvent(String message) { + this.message = message; + + // Msg.out(message + " from " + threadName); + } + + String getThreadName() { + return threadName; + } + + @Override + public String toString() { + return message + " - thread [" + threadName + ']'; + } + } +} diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogSpy.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogSpy.java new file mode 100644 index 0000000000..52b4329000 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogSpy.java @@ -0,0 +1,36 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.task; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class TaskDialogSpy extends TaskDialog { + private AtomicBoolean shown = new AtomicBoolean(); + + public TaskDialogSpy(Task task) { + super(task); + } + + @Override + protected void doShow() { + shown.set(true); + super.doShow(); + } + + boolean wasShown() { + return shown.get(); + } +} diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java index 51a4ef2857..fd53e60a52 100644 --- a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskDialogTest.java @@ -17,26 +17,10 @@ package ghidra.util.task; import static org.junit.Assert.*; -import java.awt.Component; -import java.util.Deque; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - import org.junit.After; import org.junit.Test; -import docking.test.AbstractDockingTest; - -public class TaskDialogTest extends AbstractDockingTest { - - private static final int DELAY_FAST = 10; - private static final int DELAY_SLOW = 100; - private static final int DELAY_LAUNCHER = DELAY_FAST * 2; - - private CountDownLatch threadsFinished = new CountDownLatch(2); - - private Deque eventQueue = new LinkedBlockingDeque<>(); +public class TaskDialogTest extends AbstractTaskTest { @After public void tearDown() { @@ -48,7 +32,7 @@ public class TaskDialogTest extends AbstractDockingTest { FastModalTask task = new FastModalTask(); - TaskDialogSpy dialogSpy = launchTask(task); + launchTask(task); waitForTask(); @@ -60,7 +44,7 @@ public class TaskDialogTest extends AbstractDockingTest { public void testModalDialog_SlowTask_Dialog() throws Exception { SlowModalTask task = new SlowModalTask(); - TaskDialogSpy dialogSpy = launchTask(task); + launchTask(task); waitForTask(); @@ -73,7 +57,7 @@ public class TaskDialogTest extends AbstractDockingTest { FastNonModalTask task = new FastNonModalTask(); - TaskDialogSpy dialogSpy = launchTask(task); + launchTask(task); waitForTask(); @@ -86,7 +70,7 @@ public class TaskDialogTest extends AbstractDockingTest { SlowNonModalTask task = new SlowNonModalTask(); - TaskDialogSpy dialogSpy = launchTask(task); + launchTask(task); waitForTask(); @@ -100,7 +84,7 @@ public class TaskDialogTest extends AbstractDockingTest { @Test public void testTaskCancel() throws Exception { SlowModalTask task = new SlowModalTask(); - TaskDialogSpy dialogSpy = launchTask(task); + launchTask(task); dialogSpy.doShow(); @@ -118,7 +102,7 @@ public class TaskDialogTest extends AbstractDockingTest { @Test public void testTaskNoCancel() throws Exception { SlowModalTask task = new SlowModalTask(); - TaskDialogSpy dialogSpy = launchTask(task); + launchTask(task); dialogSpy.doShow(); dialogSpy.setCancelEnabled(false); @@ -128,159 +112,4 @@ public class TaskDialogTest extends AbstractDockingTest { assertFalse(dialogSpy.isCancelEnabled()); } - private void assertSwingThreadBlockedForTask() { - TDEvent lastEvent = eventQueue.peekLast(); - boolean swingIsLast = lastEvent.getThreadName().contains("AWT"); - if (!swingIsLast) { - fail("The Swing thread did not block until the task finished"); - } - } - - private void assertSwingThreadFinishedBeforeTask() { - TDEvent lastEvent = eventQueue.peekLast(); - boolean swingIsLast = lastEvent.getThreadName().contains("AWT"); - if (swingIsLast) { - fail("The Swing thread blocked until the task finished"); - } - } - - private void waitForTask() throws Exception { - threadsFinished.await(2, TimeUnit.SECONDS); - } - - private TaskDialogSpy launchTask(Task task) { - AtomicReference ref = new AtomicReference<>(); - runSwing(() -> { - TaskLauncherSpy launcherSpy = new TaskLauncherSpy(task); - postEvent("After task launcher"); - TaskDialogSpy dialogSpy = launcherSpy.getDialogSpy(); - ref.set(dialogSpy); - threadsFinished.countDown(); - }); - return ref.get(); - } - - private void postEvent(String message) { - eventQueue.add(new TDEvent(message)); - } - - private class TaskLauncherSpy extends TaskLauncher { - - private TaskDialogSpy dialogSpy; - - public TaskLauncherSpy(Task task) { - super(task, null, DELAY_LAUNCHER); - } - - @Override - protected TaskDialog createTaskDialog(Component comp) { - dialogSpy = new TaskDialogSpy(task); - return dialogSpy; - } - - TaskDialogSpy getDialogSpy() { - return dialogSpy; - } - } - - private class TaskDialogSpy extends TaskDialog { - - private AtomicBoolean shown = new AtomicBoolean(); - - public TaskDialogSpy(Task task) { - super(task); - } - - @Override - protected void doShow() { - shown.set(true); - super.doShow(); - } - - boolean wasShown() { - return shown.get(); - } - } - - private class FastModalTask extends Task { - - public FastModalTask() { - super("Fast Modal Task", true, true, true); - } - - @Override - public void run(TaskMonitor monitor) { - postEvent(" started..."); - sleep(DELAY_FAST); - threadsFinished.countDown(); - postEvent(" finished."); - } - } - - private class FastNonModalTask extends Task { - - public FastNonModalTask() { - super("Fast Non-modal Task", true, true, false); - } - - @Override - public void run(TaskMonitor monitor) { - postEvent(" started..."); - sleep(DELAY_FAST); - postEvent(" finished."); - threadsFinished.countDown(); - } - } - - private class SlowModalTask extends Task { - - public SlowModalTask() { - super("Slow Modal Task", true, true, true); - } - - @Override - public void run(TaskMonitor monitor) { - postEvent(" started..."); - sleep(DELAY_SLOW); - threadsFinished.countDown(); - postEvent(" finished."); - } - } - - private class SlowNonModalTask extends Task { - - public SlowNonModalTask() { - super("Slow Non-modal Task", true, true, false); - } - - @Override - public void run(TaskMonitor monitor) { - postEvent(" started..."); - sleep(DELAY_SLOW); - threadsFinished.countDown(); - postEvent(" finished."); - } - } - - private class TDEvent { - - private String threadName = Thread.currentThread().getName(); - private String message; - - TDEvent(String message) { - this.message = message; - - // Msg.out(message + " from " + threadName); - } - - String getThreadName() { - return threadName; - } - - @Override - public String toString() { - return message + " - thread [" + threadName + ']'; - } - } - } diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskLauncherTest.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskLauncherTest.java new file mode 100644 index 0000000000..e128558e8d --- /dev/null +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/TaskLauncherTest.java @@ -0,0 +1,177 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.task; + +import static org.junit.Assert.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.*; + +public class TaskLauncherTest extends AbstractTaskTest { + + private Thread swingThread; + + @Before + public void setUp() { + runSwing(() -> swingThread = Thread.currentThread()); + } + + @After + public void tearDown() { + // release any blockers + swingThread.interrupt(); + } + + @Test + public void testLaunchFromSwing() throws Exception { + + FastModalTask task = new FastModalTask(); + launchTaskFromSwing(task); + waitForTask(); + assertSwingThreadBlockedForTask(); + } + + @Test + public void testLaunchFromBackground() throws Exception { + + FastModalTask task = new FastModalTask(); + launchTaskFromBackground(task); + waitForTask(); + assertRanInSwingThread(); + } + + @Test + public void testLaunchFromBackgroundWithBusySwing() throws Exception { + + SwingBlocker blocker = new SwingBlocker(); + runSwing(blocker, false); + blocker.waitForStart(); + + FastModalTask task = new FastModalTask(); + launchTaskFromBackground(task); + waitForTask(); + + assertDidNotRunInSwing(); + } + + @Test + public void testLaunchFromInsideOfAnotherTaskThread() throws Exception { + + SwingBlocker blocker = new SwingBlocker(); + runSwing(blocker, false); + blocker.waitForStart(); + + // 4 - 2 per task + threadsFinished = new CountDownLatch(4); + launchTaskFromTask(); + waitForTask(); + assertDidNotRunInSwing(); + } + + @Test + public void testLaunchFromInsideOfAnotherTaskThreadWithBusySwingThread() throws Exception { + + // 4 - 2 per task + threadsFinished = new CountDownLatch(4); + launchTaskFromTask(); + waitForTask(); + assertDidNotRunInSwing(); + } + + private int getWaitTimeoutInSeconds() { + return (int) TimeUnit.SECONDS.convert(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS) * 2; + } + + protected void launchTaskFromBackground(Task task) throws InterruptedException { + + CountDownLatch start = new CountDownLatch(1); + new Thread("Test Task Launcher Background Client") { + @Override + public void run() { + taskLauncherSpy = new TaskLauncherSpy(task); + start.countDown(); + postEvent("After task launcher"); + threadsFinished.countDown(); + } + }.start(); + + assertTrue("Background thread did not start in " + getWaitTimeoutInSeconds() + " seconds", + start.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS)); + } + + protected void launchTaskFromTask() throws InterruptedException { + + TaskLaunchingTask task = new TaskLaunchingTask(); + + CountDownLatch start = new CountDownLatch(1); + new Thread("Nested Test Task Launcher Background Client") { + @Override + public void run() { + taskLauncherSpy = new TaskLauncherSpy(task); + start.countDown(); + postEvent("After task launcher"); + threadsFinished.countDown(); + } + }.start(); + + assertTrue("Background thread did not start in " + getWaitTimeoutInSeconds() + " seconds", + start.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS)); + } + + private class TaskLaunchingTask extends Task { + + public TaskLaunchingTask() { + super("Slow Modal Task", true, true, true); + } + + @Override + public void run(TaskMonitor monitor) { + postEvent(getName() + " started..."); + + sleep(DELAY_FAST); + try { + launchTaskFromBackground(new FastModalTask()); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + + threadsFinished.countDown(); + postEvent(getName() + " finished."); + } + } + + private class SwingBlocker implements Runnable { + + private static final long REALLY_LONG_SLEEP_THAT_DOESNT_FINISH_MS = 20000; + + private CountDownLatch started = new CountDownLatch(1); + + @Override + public void run() { + started.countDown(); + sleep(REALLY_LONG_SLEEP_THAT_DOESNT_FINISH_MS); + } + + void waitForStart() throws InterruptedException { + assertTrue("Swing blocker did not start", + started.await(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS)); + } + + } +} diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java index 3fd6829942..b9fcb579ee 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java @@ -22,12 +22,16 @@ import java.net.URL; import java.net.URLDecoder; import java.text.SimpleDateFormat; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import javax.swing.SwingUtilities; import ghidra.util.exception.AssertException; +import ghidra.util.exception.UnableToSwingException; import utilities.util.reflection.ReflectionUtilities; /** @@ -257,7 +261,80 @@ public class SystemUtilities { * @see #runSwingNow(Supplier) if you need to return a value from the Swing thread. */ public static void runSwingNow(Runnable r) { - runSwing(r, true, SWING_RUN_ERROR_MSG); + + try { + // not sure what a reasonable wait is for a background thread; we can make this larger + // if we find that a really slow system UI causes this to fail + int maxWait = 10; + runSwingNow(r, maxWait, TimeUnit.SECONDS); + } + catch (UnableToSwingException e) { + throw new RuntimeException("Timed-out waiting to run a Swing task--potential deadlock!", + e); + } + } + + /** + * Calls the given runnable on the Swing thread. + * + *

This method will throw an exception if the Swing thread is not available within the + * given timeout. This method is useful for preventing deadlocks. + * + * @param r the runnable + * @param timeout the timeout value + * @param unit the time unit of the timeout value + * @throws UnableToSwingException if the timeout was reach waiting for the Swing thead + * @see #runSwingNow(Supplier) if you need to return a value from the Swing thread. + */ + public static void runSwingNow(Runnable r, long timeout, TimeUnit unit) + throws UnableToSwingException { + + if (isInHeadlessMode() || SystemUtilities.isEventDispatchThread()) { + doRunSwing(r, true, SWING_RUN_ERROR_MSG); + return; + } + + CountDownLatch start = new CountDownLatch(1); + CountDownLatch end = new CountDownLatch(1); + AtomicBoolean timedOut = new AtomicBoolean(); + + doRunSwing(() -> { + + start.countDown(); + + try { + if (timedOut.get()) { + // timed-out waiting for Swing lock, but eventually did get the lock; too late now + return; + } + + r.run(); + } + finally { + end.countDown(); + } + + }, false, SWING_RUN_ERROR_MSG); + + try { + timedOut.set(!start.await(timeout, unit)); + } + catch (InterruptedException e) { + // handled below + } + + if (timedOut.get()) { + throw new UnableToSwingException( + "Timed-out waiting for Swing thread lock in " + timeout + " " + unit); + } + + // we've started! + try { + end.await(); // wait FOREVER! + } + catch (InterruptedException e) { + // we sometimes interrupt our tasks intentionally, so don't report it + } } /** @@ -267,7 +344,7 @@ public class SystemUtilities { * @param r the runnable */ public static void runSwingLater(Runnable r) { - runSwing(r, false, SWING_RUN_ERROR_MSG); + doRunSwing(r, false, SWING_RUN_ERROR_MSG); } public static void runIfSwingOrPostSwingLater(Runnable r) { @@ -284,7 +361,7 @@ public class SystemUtilities { } } - private static void runSwing(Runnable r, boolean wait, String errorMessage) { + private static void doRunSwing(Runnable r, boolean wait, String errorMessage) { if (isInHeadlessMode()) { r.run(); return; diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/UnableToSwingException.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/UnableToSwingException.java new file mode 100644 index 0000000000..5b6dd1dd98 --- /dev/null +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/UnableToSwingException.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util.exception; + +import javax.swing.SwingUtilities; + +/** + * Signals that a background thread attempted to {@link SwingUtilities#invokeAndWait(Runnable)} + * operation that timed-out because the Swing thread was busy. This can be a sign of + * a deadlock. + */ +public class UnableToSwingException extends Exception { + + public UnableToSwingException(String message) { + super(message); + } +} From 07f0371a50f81d9e9bf5c511f8d89a0a940e633d Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Mon, 20 May 2019 15:47:41 -0400 Subject: [PATCH 2/6] GT-2875 - Unswingable - review fixes --- .../app/cmd/data/CreateDataBackgroundCmd.java | 4 +- .../ghidra/app/cmd/memory/MoveBlockTask.java | 22 +- .../app/plugin/core/clear/ClearCmd.java | 8 +- .../plugin/core/memory/MoveBlockDialog.java | 45 ++- .../core/memory/MoveBlockModelTest.java | 97 +++---- .../docking/test/AbstractDockingTest.java | 5 +- .../DataLoadingConstraintEditor.java | 3 +- .../task/BackgroundThreadTaskLauncher.java | 26 +- .../util/task/NonSwingTaskLauncher.java | 32 --- .../java/ghidra/util/task/TaskLauncher.java | 49 ++-- ...SwingTaskLauncher.java => TaskRunner.java} | 54 ++-- .../ghidra/util/task/AbstractTaskTest.java | 4 +- .../java/generic/concurrent/GThreadPool.java | 2 +- .../main/java/ghidra/util/worker/Worker.java | 5 +- .../src/main/java/ghidra/util/Swing.java | 264 ++++++++++++++++++ .../java/ghidra/util/SystemUtilities.java | 149 +--------- 16 files changed, 395 insertions(+), 374 deletions(-) delete mode 100644 Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java rename Ghidra/Framework/Docking/src/main/java/ghidra/util/task/{SwingTaskLauncher.java => TaskRunner.java} (74%) create mode 100644 Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java index 156f81dae7..505a9a7abc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java @@ -21,7 +21,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.task.TaskMonitor; /** @@ -149,7 +149,7 @@ public class CreateDataBackgroundCmd extends BackgroundCommand { // Allow the Swing thread a chance to paint components that may require // a DB lock. - SystemUtilities.allowSwingToProcessEvents(); + Swing.allowSwingToProcessEvents(); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java index fe35d642ba..d3ea2b928d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java @@ -58,7 +58,6 @@ public class MoveBlockTask extends ProgramTask { @Override protected void doRun(TaskMonitor monitor) { - // TODO Auto-generated method stub Memory mem = program.getMemory(); MemoryBlock block = mem.getBlock(currentStart); @@ -77,23 +76,23 @@ public class MoveBlockTask extends ProgramTask { } } catch (OutOfMemoryError e) { - monitor.setMessage(msg = "Insufficient memory to complete operation"); + msg = "Insufficient memory to complete operation"; cause = e; } catch (NotFoundException exc) { - monitor.setMessage(msg = "Memory block not found"); + msg = "Memory block not found"; cause = exc; } catch (MemoryConflictException exc) { - monitor.setMessage(msg = exc.getMessage()); + msg = exc.getMessage(); cause = exc; } catch (MemoryBlockException exc) { - monitor.setMessage(msg = exc.getMessage()); + msg = exc.getMessage(); cause = exc; } catch (IllegalArgumentException e) { - monitor.setMessage(msg = e.getMessage()); + msg = e.getMessage(); cause = e; } catch (Throwable t) { @@ -102,25 +101,18 @@ public class MoveBlockTask extends ProgramTask { if (msg == null) { msg = t.toString(); } - monitor.setMessage(msg); cause = t; } + + monitor.setMessage(msg); listener.moveBlockCompleted(this); throw new RollbackException(msg, cause); } - /** - * Return true if the user cancelled the move command. - */ public boolean isCancelled() { return wasCancelled; } - /** - * Return whether the block was successfully moved. - * - * @return true if the block was moved - */ public boolean getStatus() { return status; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java index 71209ce8ca..f37d116456 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java @@ -25,7 +25,7 @@ import ghidra.program.model.lang.Register; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitorAdapter; @@ -184,7 +184,7 @@ public class ClearCmd extends BackgroundCommand { monitor.setProgress(progress); // Allow Swing a chance to paint components that may require a DB lock - SystemUtilities.allowSwingToProcessEvents(); + Swing.allowSwingToProcessEvents(); } } previousRangeAddrCnt += range.getLength(); @@ -295,7 +295,7 @@ public class ClearCmd extends BackgroundCommand { AddressRangeIterator it = clearView.getAddressRanges(); while (it.hasNext()) { - AddressRange currentRange = it.next(); + AddressRange currentRange = it.next(); Address start = currentRange.getMinAddress(); Address end = currentRange.getMaxAddress(); clearAddresses(monitor, listing, start, end); @@ -320,7 +320,7 @@ public class ClearCmd extends BackgroundCommand { monitor.incrementProgress(numDone); // Allow the Swing thread a chance to paint components that may require a DB lock - SystemUtilities.allowSwingToProcessEvents(); + Swing.allowSwingToProcessEvents(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java index 1ac158afa6..5053f21bd5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java @@ -18,8 +18,6 @@ package ghidra.app.plugin.core.memory; import java.awt.Cursor; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import docking.DialogComponentProvider; import docking.widgets.label.GDLabel; @@ -32,7 +30,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; import ghidra.util.HelpLocation; import ghidra.util.layout.PairLayout; -import ghidra.util.task.TaskLauncher; +import ghidra.util.task.BackgroundThreadTaskLauncher; import ghidra.util.task.TaskMonitorAdapter; /** @@ -77,22 +75,19 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc */ @Override public void moveBlockCompleted(final MoveBlockTask cmd) { - Runnable r = new Runnable() { - @Override - public void run() { - if (cmd.getStatus()) { + Runnable r = () -> { + if (cmd.getStatus()) { + close(); + model.dispose(); + } + else { + setCursor(Cursor.getDefaultCursor()); + setOkEnabled(false); + if (cmd.isCancelled()) { + tool.setStatusInfo(getStatusText()); close(); model.dispose(); } - else { - setCursor(Cursor.getDefaultCursor()); - setOkEnabled(false); - if (cmd.isCancelled()) { - tool.setStatusInfo(getStatusText()); - close(); - model.dispose(); - } - } } }; SwingUtilities.invokeLater(r); @@ -142,7 +137,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc protected void okCallback() { setOkEnabled(false); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new TaskLauncher(model.makeTask(), new TaskMonitorAdapter() { + + BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(model.makeTask()); + launcher.run(new TaskMonitorAdapter() { @Override public void setMessage(String message) { setStatusText(message); @@ -176,18 +173,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc newEndField = new AddressInput(); newEndField.setName("newEnd"); - newStartField.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - startChanged(); - } - }); - newEndField.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - endChanged(); - } - }); + newStartField.addChangeListener(e -> startChanged()); + newEndField.addChangeListener(e -> endChanged()); panel.add(new GLabel("Name:", SwingConstants.RIGHT)); panel.add(blockNameLabel); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java index 7d579a7255..72721fd9a5 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java @@ -21,7 +21,6 @@ import org.junit.*; import ghidra.app.cmd.memory.MoveBlockListener; import ghidra.app.cmd.memory.MoveBlockTask; -import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.database.data.DataTypeManagerDB; @@ -33,32 +32,18 @@ import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.task.*; -/** - * Test the model that moves a block of memory. - * - * - */ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest implements MoveBlockListener { + private Program notepad; private Program x8051; - private PluginTool tool; private TestEnv env; private MoveBlockModel model; private MemoryBlock block; - private boolean expectedStatus; - private boolean moveCompleted; - private boolean status; - private String errMsg; - /** - * Constructor for MoveBlockModelTest. - * - * @param name - */ - public MoveBlockModelTest() { - super(); - } + private volatile boolean moveCompleted; + private volatile boolean status; + private volatile String errMsg; private Program buildProgram1(String programName) throws Exception { ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY); @@ -84,13 +69,10 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest return builder.getProgram(); } - /* - * @see TestCase#setUp() - */ @Before public void setUp() throws Exception { env = new TestEnv(); - tool = env.getTool(); + notepad = buildProgram1("notepad"); x8051 = buildProgram2("x08"); block = notepad.getMemory().getBlock(getNotepadAddr(0x1001000)); @@ -109,13 +91,8 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest x8051.endTransaction(transactionID, true); } - /* - * @see TestCase#tearDown() - */ @After public void tearDown() { - env.release(x8051); - env.release(notepad); env.dispose(); } @@ -152,25 +129,23 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest @Test public void testMoveBlockStart() throws Exception { model.setNewStartAddress(getNotepadAddr(0x2000000)); - expectedStatus = true; + launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !notepad.canLock()) { - Thread.sleep(1000); - } - assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); + waitForCondition(() -> moveCompleted && notepad.canLock()); + assertTrue("Error message= [" + errMsg + "], ", status); } @Test public void testMoveBlockEnd() throws Exception { model.setNewEndAddress(getNotepadAddr(0x2007500)); - expectedStatus = true; + launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !notepad.canLock()) { - Thread.sleep(1000); - } - assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); + waitForCondition(() -> moveCompleted && notepad.canLock()); + assertTrue("Error message= [" + errMsg + "], ", status); } @Test @@ -194,13 +169,15 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest start = getAddr(x8051, "INTMEM", 0x50); model.setNewStartAddress(start); assertEquals(getAddr(x8051, "INTMEM", 0xcf), model.getNewEndAddress()); - expectedStatus = false; + + setErrorsExpected(true); launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !x8051.canLock()) { - Thread.sleep(1000); - } - assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); + waitForCondition(() -> moveCompleted && x8051.canLock()); + setErrorsExpected(false); + + assertFalse("Error message= [" + errMsg + "], ", status); } @Test @@ -213,13 +190,12 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest model.initialize(block); start = getAddr(x8051, "CODE", 0x2000); model.setNewStartAddress(start); - expectedStatus = true; - moveCompleted = false; + launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !x8051.canLock()) { - Thread.sleep(1000); - } + waitForCondition(() -> moveCompleted && x8051.canLock()); + // make sure settings on data got moved DataTypeManagerDB dtm = ((ProgramDB) x8051).getDataManager(); @@ -257,19 +233,19 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest Address newStart = memBlock.getStart().getNewAddress(0x01002000); model.setNewStartAddress(newStart); - expectedStatus = false; - errMsg = null; - + setErrorsExpected(true); launch(model.makeTask()); - while (!moveCompleted || !notepad.canLock()) { - Thread.sleep(1000); - } - assertTrue(!expectedStatus); + + waitForCondition(() -> moveCompleted && notepad.canLock()); + setErrorsExpected(false); + assertNotNull(errMsg); } private void launch(Task task) { - new TaskLauncher(task, new TaskMonitorAdapter() { + + BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task); + launcher.run(new TaskMonitorAdapter() { @Override public void setMessage(String message) { errMsg = message; @@ -286,21 +262,14 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest return space.getAddress(offset); } - /** - * @see ghidra.app.plugin.contrib.memory.MoveBlockListener#moveBlockCompleted(boolean, - * java.lang.String) - */ @Override public void moveBlockCompleted(MoveBlockTask cmd) { moveCompleted = true; this.status = cmd.getStatus(); } - /** - * @see ghidra.app.plugin.contrib.memory.MoveBlockListener#stateChanged() - */ @Override public void stateChanged() { + // stub } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 43c788dce1..66ade1485d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -15,8 +15,7 @@ */ package docking.test; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.awt.*; import java.awt.datatransfer.*; @@ -1760,9 +1759,11 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { */ public static void setErrorsExpected(boolean expected) { if (expected) { + Msg.error(AbstractDockingTest.class, ">>>>>>>>>>>>>>>> Expected Exception"); ConcurrentTestExceptionHandler.disable(); } else { + Msg.error(AbstractDockingTest.class, "<<<<<<<<<<<<<<<< End Expected Exception"); ConcurrentTestExceptionHandler.enable(); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java index ec6aa4d02d..3c2c9c1529 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java @@ -221,7 +221,8 @@ public abstract class DataLoadingConstraintEditor extends AbstractColumnConst reloadDataButton.setVisible(false); Task task = new LoadDataTask(); task.addTaskListener(this); - new TaskLauncher(task, (TaskMonitor) taskMonitorComponent); + BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task); + launcher.run(taskMonitorComponent); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java index 679524d418..4ca176904b 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java @@ -15,29 +15,39 @@ */ package ghidra.util.task; +import java.util.concurrent.Executor; + +import generic.concurrent.GThreadPool; +import ghidra.util.Swing; import ghidra.util.TaskUtilities; /** * Helper class to launch the given task in a background thread This helper will not - * show a task dialog. See {@link TaskLauncher}. + * show a task dialog. + * + *

This class is useful when you want to run the task and use a monitor that is embedded + * in some other component. + * + *

See {@link TaskLauncher}. */ -class BackgroundThreadTaskLauncher { +public class BackgroundThreadTaskLauncher { private Task task; - BackgroundThreadTaskLauncher(Task task) { + public BackgroundThreadTaskLauncher(Task task) { this.task = task; } - void run(TaskMonitor monitor) { + public void run(TaskMonitor monitor) { // add the task here, so we can track it before it is actually started by the thread TaskUtilities.addTrackedTask(task, monitor); String name = "Task - " + task.getTaskTitle(); - Thread taskThread = new Thread(() -> { + GThreadPool pool = GThreadPool.getSharedThreadPool(Swing.GSWING_THREAD_POOL_NAME); + Executor executor = pool.getExecutor(); + executor.execute(() -> { + Thread.currentThread().setName(name); task.monitoredRun(monitor); - }, name); - taskThread.setPriority(Thread.MIN_PRIORITY); - taskThread.start(); + }); } } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java deleted file mode 100644 index c0896e55c0..0000000000 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/NonSwingTaskLauncher.java +++ /dev/null @@ -1,32 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.util.task; - -/** - * Helper class to launch the given task in the current thread. See {@link TaskLauncher}. - */ -class CurrentThreadTaskLauncher { - - private Task task; - - CurrentThreadTaskLauncher(Task task) { - this.task = task; - } - - void run() { - task.monitoredRun(TaskMonitor.DUMMY); - } -} diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java index e7698601e7..9dc04cccc6 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java @@ -18,9 +18,7 @@ package ghidra.util.task; import java.awt.Component; import java.util.concurrent.TimeUnit; -import javax.swing.SwingUtilities; - -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.exception.UnableToSwingException; /** @@ -232,34 +230,19 @@ public class TaskLauncher { public TaskLauncher(Task task, Component parent, int delay, int dialogWidth) { try { - runSwing(task, parent, delay, dialogWidth); + scheduleFromSwingThread(task, parent, delay, dialogWidth); } catch (UnableToSwingException e) { runInThisBackgroundThread(task); } } - /** - * Constructor where an external taskMonitor is used. Normally, this class will provide - * the {@link TaskDialog} as the monitor. This constructor is useful when you want to run - * the task and use a monitor that is embedded in some other component. - * - *

See notes on modal usage - * - * @param task task to run in another thread (other than the Swing Thread) - * @param monitor the monitor to use while running the task. - */ - public TaskLauncher(Task task, TaskMonitor monitor) { - BackgroundThreadTaskLauncher runner = new BackgroundThreadTaskLauncher(task); - runner.run(monitor); - } - - private void runSwing(Task task, Component parent, int delay, int dialogWidth) + private void scheduleFromSwingThread(Task task, Component parent, int delay, int dialogWidth) throws UnableToSwingException { - SwingTaskLauncher swinger = buildSwingLauncher(task, parent, delay, dialogWidth); - if (SwingUtilities.isEventDispatchThread()) { - swinger.run(); + TaskRunner runner = createTaskRunner(task, parent, delay, dialogWidth); + if (Swing.isEventDispatchThread()) { + runner.run(); return; } @@ -271,26 +254,28 @@ public class TaskLauncher { // This will throw an exception if we could not get the Swing lock. When that happens, // the task was NOT run. int timeout = getSwingTimeoutInSeconds(); - SystemUtilities.runSwingNow(() -> { - swinger.run(); - }, timeout, TimeUnit.SECONDS); + Swing.runNow(() -> runner.run(), timeout, TimeUnit.SECONDS); } protected int getSwingTimeoutInSeconds() { return 2; } - protected SwingTaskLauncher buildSwingLauncher(Task task, Component parent, int delay, - int dialogWidth) { - return new SwingTaskLauncher(task, parent, delay, dialogWidth); + protected TaskRunner createTaskRunner(Task task, Component parent, int delay, int dialogWidth) { + return new TaskRunner(task, parent, delay, dialogWidth); } + /** + * Runs the given task in the current thread, which cannot be the Swing thread + * + * @param task the task to run + * @throws IllegalStateException if the given thread is the Swing thread + */ protected void runInThisBackgroundThread(Task task) { - if (SwingUtilities.isEventDispatchThread()) { + if (Swing.isEventDispatchThread()) { throw new IllegalStateException("Must not call this method from the Swing thread"); } - CurrentThreadTaskLauncher runner = new CurrentThreadTaskLauncher(task); - runner.run(); + task.monitoredRun(TaskMonitor.DUMMY); } } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java similarity index 74% rename from Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java rename to Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java index cbc65ddb45..4fd0866850 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/SwingTaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java @@ -16,17 +16,18 @@ package ghidra.util.task; import java.awt.Component; +import java.util.concurrent.Executor; -import javax.swing.SwingUtilities; - +import generic.concurrent.GThreadPool; import generic.util.WindowUtilities; -import ghidra.util.*; +import ghidra.util.Swing; +import ghidra.util.TaskUtilities; /** * Helper class to launch the given task in a background thread, showing a task dialog if * this task takes to long. See {@link TaskLauncher}. */ -class SwingTaskLauncher { +class TaskRunner { protected Task task; private Component parent; @@ -43,7 +44,7 @@ class SwingTaskLauncher { } }; - SwingTaskLauncher(Task task, Component parent, int delay, int dialogWidth) { + TaskRunner(Task task, Component parent, int delay, int dialogWidth) { this.task = task; this.parent = parent; this.delay = delay; @@ -51,26 +52,16 @@ class SwingTaskLauncher { } void run() { + + // note: we need to be on the Swing thread to create our UI widgets + Swing.assertThisIsTheSwingThread( + "The Task runner is required to be run from the Swing thread"); + this.taskDialog = buildTaskDialog(parent); startBackgroundThread(taskDialog); taskDialog.show(Math.max(delay, 0)); - - waitIfNotSwing(); - } - - private void waitIfNotSwing() { - if (SwingUtilities.isEventDispatchThread() || !task.isModal()) { - return; - } - - try { - taskThread.join(); - } - catch (InterruptedException e) { - Msg.debug(this, "Task Launcher unexpectedly interrupted waiting for task thread", e); - } } protected TaskDialog buildTaskDialog(Component comp) { @@ -80,10 +71,8 @@ class SwingTaskLauncher { // on the Swing thread to prevent exceptions while painting (as seen when using the // Nimbus Look and Feel). // - SystemUtilities.runSwingNow(() -> { - taskDialog = createTaskDialog(comp); - taskDialog.setMinimumSize(dialogWidth, 0); - }); + taskDialog = createTaskDialog(comp); + taskDialog.setMinimumSize(dialogWidth, 0); if (task.isInterruptible() || task.isForgettable()) { taskDialog.addCancelledListener(monitorChangeListener); @@ -100,21 +89,16 @@ class SwingTaskLauncher { TaskUtilities.addTrackedTask(task, monitor); String name = "Task - " + task.getTaskTitle(); - taskThread = new Thread(() -> { + GThreadPool pool = GThreadPool.getSharedThreadPool(Swing.GSWING_THREAD_POOL_NAME); + Executor executor = pool.getExecutor(); + executor.execute(() -> { + Thread.currentThread().setName(name); task.monitoredRun(monitor); - taskProcessed(); - }, name); - taskThread.setPriority(Thread.MIN_PRIORITY); - taskThread.start(); - } - - private void taskProcessed() { - if (taskDialog != null) { taskDialog.taskProcessed(); - } + }); } - protected TaskDialog createTaskDialog(Component comp) { + private TaskDialog createTaskDialog(Component comp) { Component currentParent = comp; if (currentParent != null) { currentParent = WindowUtilities.windowForComponent(comp); diff --git a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java index 32a77bb55e..1f9a9c7e2a 100644 --- a/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java +++ b/Ghidra/Framework/Docking/src/test/java/ghidra/util/task/AbstractTaskTest.java @@ -92,10 +92,10 @@ public class AbstractTaskTest extends AbstractDockingTest { } @Override - protected SwingTaskLauncher buildSwingLauncher(Task task, Component parent, int delay, + protected TaskRunner createTaskRunner(Task task, Component parent, int delay, int dialogWidth) { - return new SwingTaskLauncher(task, parent, delay, dialogWidth) { + return new TaskRunner(task, parent, delay, dialogWidth) { @Override protected TaskDialog buildTaskDialog(Component comp) { dialogSpy = new TaskDialogSpy(task); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/GThreadPool.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/GThreadPool.java index 2e8328f029..eefb4f0276 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/GThreadPool.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/GThreadPool.java @@ -174,7 +174,7 @@ public class GThreadPool { * * @return the executor */ - public GThreadPoolExecutor getExecutor() { + public Executor getExecutor() { return executor; } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Worker.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Worker.java index f7821b75ab..a7546c2fc4 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Worker.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/worker/Worker.java @@ -17,6 +17,7 @@ package ghidra.util.worker; import java.util.concurrent.LinkedBlockingQueue; +import ghidra.util.Swing; import ghidra.util.SystemUtilities; import ghidra.util.task.TaskMonitor; @@ -27,8 +28,6 @@ import ghidra.util.task.TaskMonitor; */ public class Worker extends AbstractWorker { - public static final String GSWING_THREAD_POOL_NAME = "GSwing Worker"; - /** * A convenience method to create a Worker that uses a shared thread pool for performing * operations for GUI clients in a background thread @@ -42,7 +41,7 @@ public class Worker extends AbstractWorker { * @return the new worker */ public static Worker createGuiWorker() { - return new Worker(GSWING_THREAD_POOL_NAME); + return new Worker(Swing.GSWING_THREAD_POOL_NAME); } /** diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java new file mode 100644 index 0000000000..6380b727bf --- /dev/null +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java @@ -0,0 +1,264 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.util; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import javax.swing.SwingUtilities; + +import ghidra.util.exception.AssertException; +import ghidra.util.exception.UnableToSwingException; +import utilities.util.reflection.ReflectionUtilities; + +/** + * A utility class to handle running code on the AWT Event Dispatch Thread + */ +public class Swing { + + private static final String SWING_TIMEOUT_SECONDS_PROPERTY = + Swing.class.getName().toLowerCase() + ".timeout.seconds"; + private static final int SWING_TIMEOUT_SECONDS_DEFAULT_VALUE = 10; + + private static int loadTimeout() { + String timeoutString = System.getProperty(SWING_TIMEOUT_SECONDS_PROPERTY, + Integer.toString(SWING_TIMEOUT_SECONDS_DEFAULT_VALUE)); + + try { + return Integer.parseInt(timeoutString); + } + catch (NumberFormatException e) { + return SWING_TIMEOUT_SECONDS_DEFAULT_VALUE; + } + } + + private static final int SWING_TIMEOUT_SECONDS_VALUE = loadTimeout(); + + private static final String SWING_RUN_ERROR_MSG = + "Unexpected exception running a task in the Swing Thread: "; + + public static final String GSWING_THREAD_POOL_NAME = "GSwing Worker"; + + /** + * Returns true if this is the event dispatch thread. Note that this method returns true in + * headless mode because any thread in headless mode can dispatch its own events. In swing + * environments, the swing thread is usually used to dispatch events. + * + * @return true if this is the event dispatch thread -OR- is in headless mode. + */ + public static boolean isEventDispatchThread() { + if (isInHeadlessMode()) { + return true; + } + + // Note: just calling this method may trigger the AWT thread to get created + return SwingUtilities.isEventDispatchThread(); + } + + /** + * Wait until AWT event queue (Swing) has been flushed and no more (to a point) events + * are pending. + */ + public static void allowSwingToProcessEvents() { + Runnable r = () -> { + // do nothing...this is just a placeholder runnable that gets put onto the stack + }; + runNow(r); + runNow(r); + runNow(r); + } + + /** + * A development/testing time method to make sure the current thread is the swing thread. + * @param errorMessage The message to display when the assert fails + */ + public static void assertThisIsTheSwingThread(String errorMessage) { + boolean isProductionMode = + !SystemUtilities.isInTestingMode() && !SystemUtilities.isInDevelopmentMode(); + if (isProductionMode) { + return; // squash during production mode + } + + if (!isEventDispatchThread()) { + Throwable t = + ReflectionUtilities.filterJavaThrowable(new AssertException(errorMessage)); + Msg.error(SystemUtilities.class, errorMessage, t); + } + } + + /** + * Calls the given suppler on the Swing thread, blocking with a + * {@link SwingUtilities#invokeAndWait(Runnable)}. Use this method when you need to get + * a value while being on the Swing thread. + * + *

+	 * 		String value = runNow(() -> label.getText());
+	 * 
+ * + * @param s the supplier that will be called on the Swing thread + * @return the result of the supplier + * @see #runNow(Runnable) + */ + public static T runNow(Supplier s) { + AtomicReference ref = new AtomicReference<>(); + runNow(() -> ref.set(s.get())); + return ref.get(); + } + + /** + * Calls the given runnable on the Swing thread + * + * @param r the runnable + * @see #runNow(Supplier) if you need to return a value from the Swing thread. + */ + public static void runNow(Runnable r) { + + try { + // not sure what a reasonable wait is for a background thread; we can make this larger + // if we find that a really slow system UI causes this to fail + runNow(r, SWING_TIMEOUT_SECONDS_VALUE, TimeUnit.SECONDS); + } + catch (UnableToSwingException e) { + throw new RuntimeException("Timed-out waiting to run a Swing task--potential deadlock!", + e); + } + } + + /** + * Calls the given runnable on the Swing thread + * + *

This method will throw an exception if the Swing thread is not available within the + * given timeout. This method is useful for preventing deadlocks. + * + * @param r the runnable + * @param timeout the timeout value + * @param unit the time unit of the timeout value + * @throws UnableToSwingException if the timeout was reach waiting for the Swing thread + * @see #runNow(Supplier) if you need to return a value from the Swing thread. + */ + public static void runNow(Runnable r, long timeout, TimeUnit unit) + throws UnableToSwingException { + + if (isInHeadlessMode() || SystemUtilities.isEventDispatchThread()) { + doRunSwing(r, true, SWING_RUN_ERROR_MSG); + return; + } + + CountDownLatch start = new CountDownLatch(1); + CountDownLatch end = new CountDownLatch(1); + AtomicBoolean timedOut = new AtomicBoolean(); + + doRunSwing(() -> { + + start.countDown(); + + try { + if (timedOut.get()) { + // timed-out waiting for Swing lock, but eventually did get the lock; too late now + return; + } + + r.run(); + } + finally { + end.countDown(); + } + + }, false, SWING_RUN_ERROR_MSG); + + try { + timedOut.set(!start.await(timeout, unit)); + } + catch (InterruptedException e) { + // handled below + } + + if (timedOut.get()) { + throw new UnableToSwingException( + "Timed-out waiting for Swing thread lock in " + timeout + " " + unit); + } + + // we've started! + try { + end.await(); // wait FOREVER! + } + catch (InterruptedException e) { + // we sometimes interrupt our tasks intentionally, so don't report it + } + } + + /** + * Calls the given runnable on the Swing thread in the future by putting the request on + * the back of the event queue. + * + * @param r the runnable + */ + public static void runLater(Runnable r) { + doRunSwing(r, false, SWING_RUN_ERROR_MSG); + } + + public static void runIfSwingOrRunLater(Runnable r) { + if (isInHeadlessMode()) { + r.run(); + return; + } + + if (SwingUtilities.isEventDispatchThread()) { + r.run(); + } + else { + SwingUtilities.invokeLater(r); + } + } + + private static boolean isInHeadlessMode() { + return SystemUtilities.isInHeadlessMode(); + } + + private static void doRunSwing(Runnable r, boolean wait, String errorMessage) { + if (isInHeadlessMode()) { + r.run(); + return; + } + + if (wait) { + if (SwingUtilities.isEventDispatchThread()) { + r.run(); + return; + } + try { + SwingUtilities.invokeAndWait(r); + } + catch (InterruptedException e) { + // we sometimes interrupt our tasks intentionally, so don't report it + } + catch (InvocationTargetException e) { + Msg.error(Swing.class, errorMessage + "\nException Message: " + e.getMessage(), e); + } + } + else { + SwingUtilities.invokeLater(r); + } + } + + private Swing() { + // utility class + } +} diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java index b9fcb579ee..2698065ab9 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/SystemUtilities.java @@ -17,21 +17,15 @@ package ghidra.util; import java.awt.Font; import java.io.*; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLDecoder; import java.text.SimpleDateFormat; import java.util.*; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import javax.swing.SwingUtilities; import ghidra.util.exception.AssertException; -import ghidra.util.exception.UnableToSwingException; import utilities.util.reflection.ReflectionUtilities; /** @@ -41,9 +35,6 @@ import utilities.util.reflection.ReflectionUtilities; */ public class SystemUtilities { - private static final String SWING_RUN_ERROR_MSG = - "Unexpected exception running a task in the Swing Thread: "; - private final static String DATE_TIME_FORMAT = "MMM d yyyy HH:mm:ss"; private static String userName; @@ -249,9 +240,7 @@ public class SystemUtilities { * @see #runSwingNow(Runnable) */ public static T runSwingNow(Supplier s) { - AtomicReference ref = new AtomicReference<>(); - runSwingNow(() -> ref.set(s.get())); - return ref.get(); + return Swing.runNow(s); } /** @@ -261,80 +250,7 @@ public class SystemUtilities { * @see #runSwingNow(Supplier) if you need to return a value from the Swing thread. */ public static void runSwingNow(Runnable r) { - - try { - // not sure what a reasonable wait is for a background thread; we can make this larger - // if we find that a really slow system UI causes this to fail - int maxWait = 10; - runSwingNow(r, maxWait, TimeUnit.SECONDS); - } - catch (UnableToSwingException e) { - throw new RuntimeException("Timed-out waiting to run a Swing task--potential deadlock!", - e); - } - } - - /** - * Calls the given runnable on the Swing thread. - * - *

This method will throw an exception if the Swing thread is not available within the - * given timeout. This method is useful for preventing deadlocks. - * - * @param r the runnable - * @param timeout the timeout value - * @param unit the time unit of the timeout value - * @throws UnableToSwingException if the timeout was reach waiting for the Swing thead - * @see #runSwingNow(Supplier) if you need to return a value from the Swing thread. - */ - public static void runSwingNow(Runnable r, long timeout, TimeUnit unit) - throws UnableToSwingException { - - if (isInHeadlessMode() || SystemUtilities.isEventDispatchThread()) { - doRunSwing(r, true, SWING_RUN_ERROR_MSG); - return; - } - - CountDownLatch start = new CountDownLatch(1); - CountDownLatch end = new CountDownLatch(1); - AtomicBoolean timedOut = new AtomicBoolean(); - - doRunSwing(() -> { - - start.countDown(); - - try { - if (timedOut.get()) { - // timed-out waiting for Swing lock, but eventually did get the lock; too late now - return; - } - - r.run(); - } - finally { - end.countDown(); - } - - }, false, SWING_RUN_ERROR_MSG); - - try { - timedOut.set(!start.await(timeout, unit)); - } - catch (InterruptedException e) { - // handled below - } - - if (timedOut.get()) { - throw new UnableToSwingException( - "Timed-out waiting for Swing thread lock in " + timeout + " " + unit); - } - - // we've started! - try { - end.await(); // wait FOREVER! - } - catch (InterruptedException e) { - // we sometimes interrupt our tasks intentionally, so don't report it - } + Swing.runNow(r); } /** @@ -344,48 +260,11 @@ public class SystemUtilities { * @param r the runnable */ public static void runSwingLater(Runnable r) { - doRunSwing(r, false, SWING_RUN_ERROR_MSG); + Swing.runLater(r); } public static void runIfSwingOrPostSwingLater(Runnable r) { - if (isInHeadlessMode()) { - r.run(); - return; - } - - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - } - else { - SwingUtilities.invokeLater(r); - } - } - - private static void doRunSwing(Runnable r, boolean wait, String errorMessage) { - if (isInHeadlessMode()) { - r.run(); - return; - } - - if (wait) { - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - return; - } - try { - SwingUtilities.invokeAndWait(r); - } - catch (InterruptedException e) { - // we sometimes interrupt our tasks intentionally, so don't report it - } - catch (InvocationTargetException e) { - Msg.error(SystemUtilities.class, - errorMessage + "\nException Message: " + e.getMessage(), e); - } - } - else { - SwingUtilities.invokeLater(r); - } + Swing.runIfSwingOrRunLater(r); } /** @@ -543,25 +422,7 @@ public class SystemUtilities { * @return true if this is the event dispatch thread -OR- is in headless mode. */ public static boolean isEventDispatchThread() { - if (isInHeadlessMode()) { - return true; - } - - // Note: just calling this method may trigger the AWT thread to get created - return SwingUtilities.isEventDispatchThread(); - } - - /** - * Wait until AWT event queue (Swing) has been flushed and no more (to a point) events - * are pending. - */ - public static void allowSwingToProcessEvents() { - Runnable r = () -> { - // do nothing...this is just a placeholder runnable that gets put onto the stack - }; - runSwingNow(r); - runSwingNow(r); - runSwingNow(r); + return Swing.isEventDispatchThread(); } /** From bc2fb09ec577aeedaca238eca57709c9099059f2 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Mon, 20 May 2019 16:22:49 -0400 Subject: [PATCH 3/6] GT-2875 - Unswingable - test fixes --- .../core/datamgr/actions/CreateTypeDefAction.java | 5 +++++ .../ghidra/framework/plugintool/PluginManager.java | 10 ---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateTypeDefAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateTypeDefAction.java index 8e9437504d..1960d89cb5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateTypeDefAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateTypeDefAction.java @@ -64,6 +64,11 @@ public class CreateTypeDefAction extends AbstractTypeDefAction { } ArchiveNode archiveNode = node.getArchiveNode(); + if (archiveNode == null) { + // this can happen as the tree is changing + return false; + } + boolean enabled = archiveNode.isModifiable(); if (archiveNode instanceof BuiltInArchiveNode) { // these will be put into the program archive diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java index c2f6080b11..d5e0c5ed98 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginManager.java @@ -82,10 +82,6 @@ class PluginManager { return set.toArray(cl); } - void addPlugin(String className) throws PluginException { - addPlugins(new String[] { className }); - } - void addPlugin(Plugin plugin) throws PluginException { addPlugins(new Plugin[] { plugin }); } @@ -369,12 +365,6 @@ class PluginManager { } } - /** - * Save the data state for all plugins in the tool to an XML element. - * @param isTransactionState true if saving the toolstate is for a potential undo/redo - * (database transaction) - * @return XML element containing data state for all plugins. - */ Element saveDataStateToXml(boolean savingProject) { Element root = new Element("DATA_STATE"); for (int i = 0; i < pluginList.size(); i++) { From 5e8340b7f824c753c6c9b3d94645a03fe3c76645 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 21 May 2019 10:10:13 -0400 Subject: [PATCH 4/6] GT-2875 - Unswingable - review fixes --- .../datamgr/actions/DisassociateAction.java | 3 +- .../core/datamgr/actions/SyncAction.java | 8 ++- .../plugin/core/module/ModuleSortPlugin.java | 44 +++++++------ .../java/ghidra/util/task/TaskBuilder.java | 32 +++++++++- .../java/ghidra/util/task/TaskLauncher.java | 63 ++++++++++--------- .../java/ghidra/util/task/TaskRunner.java | 8 +-- .../src/main/java/ghidra/util/Swing.java | 10 +-- 7 files changed, 106 insertions(+), 62 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java index ee15686c72..bfb450674e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/DisassociateAction.java @@ -100,7 +100,8 @@ public class DisassociateAction extends DockingAction { monitor -> doDisassociate(synchronizer, typesToDisassociate, allAssociatedTypes, monitor); new TaskBuilder("Disassociate From Archive", r) .setStatusTextAlignment(SwingConstants.LEADING) - .launchModal(); + .launchModal() + ; //@formatter:on } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SyncAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SyncAction.java index b0a72f825c..41951c3d66 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SyncAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/SyncAction.java @@ -103,8 +103,12 @@ public abstract class SyncAction extends DockingAction implements Comparable * + * Or, + * + *

+ *	    TaskBuilder.withTask(new AwesomeTask(awesomeStuff)).launchModal();		
+ * 
+ * + * Or, + * + *
+ *	    {@link TaskLauncher#launch(Task) TaskLauncher.launch}(new AwesomeTask(awesomeStuff));		
+ * 
+ * + * *

Note: this class will check to see if it is in a headless environment before launching * its task. This makes it safe to use this class in headed or headless environments. */ @@ -84,6 +97,21 @@ public class TaskBuilder { return new TaskBuilder(r); } + /** + * A convenience method to start a builder using the given task. The + * {@link #setTitle(String) title} of the task will be the value of + * {@link Task#getTaskTitle()}. + * + *

This method allows for a more attractive fluent API usage than does the constructor + * (see the javadoc header). + * + * @param t the task + * @return this builder + */ + public static TaskBuilder withTask(Task t) { + return new TaskBuilder(t.getTaskTitle(), t); + } + private TaskBuilder(MonitoredRunnable r) { this.runnable = Objects.requireNonNull(r); } @@ -239,9 +267,9 @@ public class TaskBuilder { } if (isModal) { - return TaskLauncher.INITIAL_MODAL_DELAY; + return TaskLauncher.INITIAL_MODAL_DELAY_MS; } - return TaskLauncher.INITIAL_DELAY; + return TaskLauncher.INITIAL_DELAY_MS; } private class TaskBuilderTask extends Task { diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java index 9dc04cccc6..7a8b600ddc 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java @@ -31,7 +31,8 @@ import ghidra.util.exception.UnableToSwingException; * see one of the many static convenience methods. * * - *

Most clients of this class should not be concerned with where + *

Modal Usage
+ * 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 active window, which is the desired * behavior for most uses. If you should need a dialog to appear over a non-active window, @@ -91,7 +92,7 @@ public class TaskLauncher { public void run(TaskMonitor monitor) { runnable.monitoredRun(monitor); } - }, null, INITIAL_DELAY); + }, null, INITIAL_DELAY_MS); } /** @@ -120,7 +121,7 @@ public class TaskLauncher { public void run(TaskMonitor monitor) { runnable.monitoredRun(monitor); } - }, null, INITIAL_MODAL_DELAY); + }, null, INITIAL_MODAL_DELAY_MS); } /** @@ -161,21 +162,13 @@ public class TaskLauncher { // End Static Launcher Methods //================================================================================================== - static final int INITIAL_DELAY = 1000;// 1 second + static final int INITIAL_DELAY_MS = 1000; /** The time, for modal tasks, to try and run before blocking and showing a dialog */ - public static final int INITIAL_MODAL_DELAY = 500; - - private static Component getParent(Component parent) { - if (parent == null) { - return null; - } - - return (parent.isVisible() ? parent : null); - } + static final int INITIAL_MODAL_DELAY_MS = 500; /** - * Constructor for TaskLauncher. + * Constructor for TaskLauncher * *

This constructor assumes that if a progress dialog is needed, then it should appear * over the active window. If you should need a dialog to appear over a non-active window, @@ -186,11 +179,11 @@ public class TaskLauncher { * */ public TaskLauncher(Task task) { - this(task, null, task.isModal() ? INITIAL_MODAL_DELAY : INITIAL_DELAY); + this(task, null, task.isModal() ? INITIAL_MODAL_DELAY_MS : INITIAL_DELAY_MS); } /** - * Constructor for TaskLauncher. + * Constructor for TaskLauncher * *

See notes on modal usage * @@ -198,49 +191,49 @@ public class TaskLauncher { * @param parent component whose window to use to parent the dialog. */ public TaskLauncher(Task task, Component parent) { - this(task, getParent(parent), task.isModal() ? INITIAL_MODAL_DELAY : INITIAL_DELAY); + this(task, getParent(parent), task.isModal() ? INITIAL_MODAL_DELAY_MS : INITIAL_DELAY_MS); } /** - * Construct a new TaskLauncher. + * Construct a new TaskLauncher * *

See notes on modal usage * * @param task task to run in another thread (other than the Swing Thread) * @param parent component whose window to use to parent the dialog; null centers the task * dialog over the current window - * @param delay number of milliseconds to delay until the task monitor is displayed + * @param delayMs number of milliseconds to delay until the task monitor is displayed */ - public TaskLauncher(Task task, Component parent, int delay) { - this(task, parent, delay, TaskDialog.DEFAULT_WIDTH); + public TaskLauncher(Task task, Component parent, int delayMs) { + this(task, parent, delayMs, TaskDialog.DEFAULT_WIDTH); } /** - * Construct a new TaskLauncher. + * Construct a new TaskLauncher * *

See notes on modal usage * * @param task task to run in another thread (other than the Swing Thread) * @param parent component whose window to use to parent the dialog; null centers the task * dialog over the current window - * @param delay number of milliseconds to delay until the task monitor is displayed + * @param delayMs number of milliseconds to delay until the task monitor is displayed * @param dialogWidth The preferred width of the dialog (this allows clients to make a wider * dialog, which better shows long messages). */ - public TaskLauncher(Task task, Component parent, int delay, int dialogWidth) { + public TaskLauncher(Task task, Component parent, int delayMs, int dialogWidth) { try { - scheduleFromSwingThread(task, parent, delay, dialogWidth); + scheduleFromSwingThread(task, parent, delayMs, dialogWidth); } catch (UnableToSwingException e) { runInThisBackgroundThread(task); } } - private void scheduleFromSwingThread(Task task, Component parent, int delay, int dialogWidth) + private void scheduleFromSwingThread(Task task, Component parent, int delayMs, int dialogWidth) throws UnableToSwingException { - TaskRunner runner = createTaskRunner(task, parent, delay, dialogWidth); + TaskRunner runner = createTaskRunner(task, parent, delayMs, dialogWidth); if (Swing.isEventDispatchThread()) { runner.run(); return; @@ -257,12 +250,15 @@ public class TaskLauncher { Swing.runNow(() -> runner.run(), timeout, TimeUnit.SECONDS); } + // template method to allow timeout change protected int getSwingTimeoutInSeconds() { return 2; } - protected TaskRunner createTaskRunner(Task task, Component parent, int delay, int dialogWidth) { - return new TaskRunner(task, parent, delay, dialogWidth); + // template method to allow task runner change + protected TaskRunner createTaskRunner(Task task, Component parent, int delayMs, + int dialogWidth) { + return new TaskRunner(task, parent, delayMs, dialogWidth); } /** @@ -278,4 +274,13 @@ public class TaskLauncher { task.monitoredRun(TaskMonitor.DUMMY); } + + private static Component getParent(Component parent) { + if (parent == null) { + return null; + } + + return (parent.isVisible() ? parent : null); + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java index 4fd0866850..a4437a4635 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskRunner.java @@ -31,7 +31,7 @@ class TaskRunner { protected Task task; private Component parent; - private int delay; + private int delayMs; private int dialogWidth; private TaskDialog taskDialog; private Thread taskThread; @@ -44,10 +44,10 @@ class TaskRunner { } }; - TaskRunner(Task task, Component parent, int delay, int dialogWidth) { + TaskRunner(Task task, Component parent, int delayMs, int dialogWidth) { this.task = task; this.parent = parent; - this.delay = delay; + this.delayMs = delayMs; this.dialogWidth = dialogWidth; } @@ -61,7 +61,7 @@ class TaskRunner { startBackgroundThread(taskDialog); - taskDialog.show(Math.max(delay, 0)); + taskDialog.show(Math.max(delayMs, 0)); } protected TaskDialog buildTaskDialog(Component comp) { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java index 6380b727bf..8a405522dd 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java @@ -105,8 +105,10 @@ public class Swing { /** * Calls the given suppler on the Swing thread, blocking with a - * {@link SwingUtilities#invokeAndWait(Runnable)}. Use this method when you need to get - * a value while being on the Swing thread. + * {@link SwingUtilities#invokeAndWait(Runnable)} if not on the Swing thread. + * + *

Use this method when you are not on the Swing thread and you need to get a value + * that is managed/synchronized by the Swing thread. * *

 	 * 		String value = runNow(() -> label.getText());
@@ -195,9 +197,9 @@ public class Swing {
 				"Timed-out waiting for Swing thread lock in " + timeout + " " + unit);
 		}
 
-		// we've started!
+		// we've started; wait for the runnable with no timeout
 		try {
-			end.await(); // wait FOREVER!
+			end.await();
 		}
 		catch (InterruptedException e) {
 			// we sometimes interrupt our tasks intentionally, so don't report it

From 31f3cca1a5c89399c1f4579528a4e46138654abf Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Tue, 21 May 2019 14:51:32 -0400
Subject: [PATCH 5/6] GT-2875 - Unswingable - review fixes

---
 .../InterpreterComponentProvider.java         |   2 +-
 .../core/interpreter/InterpreterConsole.java  |   2 +-
 .../app/tablechooser/TableChooserDialog.java  |   2 +-
 .../ghidra/app/util/bean/SetEquateDialog.java |   2 +-
 ...AbstractGhidraHeadlessIntegrationTest.java |   1 +
 .../ghidra/test/AbstractProgramBasedTest.java |   4 +-
 .../AutoTableDisassemblerTest.java            |   2 +-
 .../java/docking/DialogComponentProvider.java |   2 +-
 .../widgets/filter/FilterTextField.java       |   2 +-
 .../pathmanager/PathnameTablePanel.java       |   2 +-
 .../widgets/table/GTableFilterPanel.java      |   2 +-
 .../constraint/dialog/ColumnFilterDialog.java |   2 +-
 .../java/ghidra/util/task/TaskBuilder.java    |   4 +-
 .../java/ghidra/util/task/TaskLauncher.java   |  15 ++-
 .../widgets/filter/FilterTextFieldTest.java   |   2 +-
 .../main/java/ghidra/util/TaskUtilities.java  |   2 +-
 .../ghidra/util/task/TimeoutTaskMonitor.java  |   2 +-
 .../graph/graphs/FilteringVisualGraph.java    |   2 +-
 .../java/ghidra/graph/job/GraphJobRunner.java |   2 +-
 .../graph/viewer/VisualGraphViewUpdater.java  |   2 +-
 .../edge/VisualGraphPathHighlighter.java      |   2 +-
 .../viewer/AlgorithmSteppingTaskMonitor.java  |   2 +-
 .../src/main/java/ghidra/util/Swing.java      | 111 ++++++++++--------
 .../main/java/utility}/function/Callback.java |   2 +-
 .../function/ExceptionalCallback.java         |   2 +-
 .../function/ExceptionalConsumer.java         |   2 +-
 .../function/ExceptionalFunction.java         |   2 +-
 27 files changed, 100 insertions(+), 79 deletions(-)
 rename Ghidra/Framework/{Generic/src/main/java/ghidra/generic => Utility/src/main/java/utility}/function/Callback.java (97%)
 rename Ghidra/Framework/{Generic/src/main/java/ghidra/generic => Utility/src/main/java/utility}/function/ExceptionalCallback.java (96%)
 rename Ghidra/Framework/{Generic/src/main/java/ghidra/generic => Utility/src/main/java/utility}/function/ExceptionalConsumer.java (96%)
 rename Ghidra/Framework/{Generic/src/main/java/ghidra/generic => Utility/src/main/java/utility}/function/ExceptionalFunction.java (97%)

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java
index 73acde6e92..c41690114c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java
@@ -26,10 +26,10 @@ import docking.action.DockingAction;
 import docking.action.ToolBarData;
 import docking.widgets.OptionDialog;
 import ghidra.framework.plugintool.ComponentProviderAdapter;
-import ghidra.generic.function.Callback;
 import ghidra.util.HelpLocation;
 import resources.Icons;
 import resources.ResourceManager;
+import utility.function.Callback;
 
 public class InterpreterComponentProvider extends ComponentProviderAdapter
 		implements InterpreterConsole {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterConsole.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterConsole.java
index 2b3420cbe7..258ad1a9dd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterConsole.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterConsole.java
@@ -18,8 +18,8 @@ package ghidra.app.plugin.core.interpreter;
 import java.io.*;
 
 import docking.action.DockingAction;
-import ghidra.generic.function.Callback;
 import ghidra.util.Disposable;
+import utility.function.Callback;
 
 /**
  * Interactive interpreter console.
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java
index f47db591e0..93c43a69fd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java
@@ -33,7 +33,6 @@ import ghidra.app.nav.NavigatableRemovalListener;
 import ghidra.app.services.GoToService;
 import ghidra.app.util.HelpTopics;
 import ghidra.framework.plugintool.PluginTool;
-import ghidra.generic.function.Callback;
 import ghidra.program.model.listing.Program;
 import ghidra.program.util.ProgramLocation;
 import ghidra.program.util.ProgramSelection;
@@ -44,6 +43,7 @@ import ghidra.util.datastruct.WeakSet;
 import ghidra.util.table.*;
 import ghidra.util.task.TaskMonitor;
 import resources.ResourceManager;
+import utility.function.Callback;
 
 /**
  * Dialog to show a table of items.  If the dialog is constructed with a non-null 
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java
index 6ddb9fd2b5..320e5aeb92 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java
@@ -49,7 +49,6 @@ import ghidra.app.context.ListingActionContext;
  */
 import ghidra.app.services.DataTypeManagerService;
 import ghidra.framework.plugintool.PluginTool;
-import ghidra.generic.function.Callback;
 import ghidra.program.database.symbol.EquateManager;
 import ghidra.program.model.data.DataTypeManager;
 import ghidra.program.model.data.Enum;
@@ -62,6 +61,7 @@ import ghidra.util.UniversalID;
 import ghidra.util.layout.HorizontalLayout;
 import ghidra.util.layout.VerticalLayout;
 import ghidra.util.table.*;
+import utility.function.Callback;
 
 public class SetEquateDialog extends DialogComponentProvider {
 	public static final int CANCELED = 0;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java
index 5a5889df68..bf2c8458a4 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java
@@ -47,6 +47,7 @@ import ghidra.util.exception.AssertException;
 import ghidra.util.exception.RollbackException;
 import junit.framework.AssertionFailedError;
 import utility.application.ApplicationLayout;
+import utility.function.*;
 
 public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDockingTest {
 
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java
index e7bd073dd3..8c267a3a36 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractProgramBasedTest.java
@@ -25,13 +25,13 @@ import org.junit.After;
 
 import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
 import ghidra.framework.plugintool.PluginTool;
-import ghidra.generic.function.ExceptionalConsumer;
-import ghidra.generic.function.ExceptionalFunction;
 import ghidra.program.model.address.*;
 import ghidra.program.model.listing.*;
 import ghidra.program.util.ProgramLocation;
 import ghidra.util.exception.AssertException;
 import util.CollectionUtils;
+import utility.function.ExceptionalConsumer;
+import utility.function.ExceptionalFunction;
 
 /**
  * A convenience base class for creating tests that use the default tool and open a program.
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerTest.java
index b0d01b0d5b..ea62d98814 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerTest.java
@@ -35,7 +35,6 @@ import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
 import ghidra.app.services.ProgramManager;
 import ghidra.framework.plugintool.Plugin;
 import ghidra.framework.plugintool.PluginTool;
-import ghidra.generic.function.Callback;
 import ghidra.program.database.ProgramBuilder;
 import ghidra.program.model.address.*;
 import ghidra.program.model.data.DataType;
@@ -51,6 +50,7 @@ import ghidra.util.TrackedTaskListener;
 import ghidra.util.table.GhidraTable;
 import ghidra.util.table.field.AddressBasedLocation;
 import ghidra.util.task.Task;
+import utility.function.Callback;
 
 public class AutoTableDisassemblerTest extends AbstractGhidraHeadedIntegrationTest {
 
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
index 86b38e15e7..672186881a 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProvider.java
@@ -32,10 +32,10 @@ import docking.event.mouse.GMouseListenerAdapter;
 import docking.menu.DockingToolbarButton;
 import docking.util.*;
 import docking.widgets.label.GDHtmlLabel;
-import ghidra.generic.function.Callback;
 import ghidra.util.*;
 import ghidra.util.exception.AssertException;
 import ghidra.util.task.*;
+import utility.function.Callback;
 
 /**
  * Base class used for creating dialogs in Ghidra. Subclass this to create a dialog provider that has
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java
index a5cc15a54f..6850c054d8 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filter/FilterTextField.java
@@ -22,10 +22,10 @@ import javax.swing.*;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 
-import ghidra.generic.function.Callback;
 import ghidra.util.SystemUtilities;
 import ghidra.util.datastruct.WeakDataStructureFactory;
 import ghidra.util.datastruct.WeakSet;
+import utility.function.Callback;
 
 /**
  * A text field that is meant to be used in conjunction with tables that allow filter text.  This
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/pathmanager/PathnameTablePanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/pathmanager/PathnameTablePanel.java
index f34b200e8b..97ad8a24f5 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/pathmanager/PathnameTablePanel.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/pathmanager/PathnameTablePanel.java
@@ -28,10 +28,10 @@ import docking.widgets.filechooser.GhidraFileChooser;
 import docking.widgets.filechooser.GhidraFileChooserMode;
 import docking.widgets.table.*;
 import ghidra.framework.preferences.Preferences;
-import ghidra.generic.function.Callback;
 import ghidra.util.filechooser.GhidraFileChooserModel;
 import ghidra.util.filechooser.GhidraFileFilter;
 import resources.ResourceManager;
+import utility.function.Callback;
 
 /**
  * Component that has a table to show pathnames; the panel includes buttons to control
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java
index 5d116af614..653b4ec57a 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java
@@ -40,7 +40,6 @@ import docking.widgets.table.columnfilter.ColumnBasedTableFilter;
 import docking.widgets.table.columnfilter.ColumnFilterSaveManager;
 import docking.widgets.table.constraint.dialog.ColumnFilterDialog;
 import ghidra.framework.options.PreferenceState;
-import ghidra.generic.function.Callback;
 import ghidra.util.HelpLocation;
 import ghidra.util.Msg;
 import ghidra.util.datastruct.WeakDataStructureFactory;
@@ -50,6 +49,7 @@ import ghidra.util.task.SwingUpdateManager;
 import resources.Icons;
 import resources.ResourceManager;
 import utilities.util.reflection.ReflectionUtilities;
+import utility.function.Callback;
 
 /**
  * This class is a panel that provides a label and text field that allows users to input text that
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java
index e55cd0cad9..16cc6ebc55 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constraint/dialog/ColumnFilterDialog.java
@@ -33,11 +33,11 @@ import docking.widgets.table.GTableFilterPanel;
 import docking.widgets.table.RowObjectFilterModel;
 import docking.widgets.table.columnfilter.*;
 import docking.widgets.table.constrainteditor.ColumnConstraintEditor;
-import ghidra.generic.function.Callback;
 import ghidra.util.HelpLocation;
 import ghidra.util.Msg;
 import ghidra.util.layout.VerticalLayout;
 import resources.ResourceManager;
+import utility.function.Callback;
 
 /**
  * Dialog for creating and editing column table filters.
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskBuilder.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskBuilder.java
index 963f5242e8..e0b44d6abe 100644
--- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskBuilder.java
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskBuilder.java
@@ -176,8 +176,8 @@ public class TaskBuilder {
 
 	/**
 	 * Sets the amount of time that will pass before showing the dialog.  The default is
-	 * {@link TaskLauncher#INITIAL_DELAY} for non-modal tasks and 
-	 * {@link TaskLauncher#INITIAL_MODAL_DELAY} for modal tasks.
+	 * {@link TaskLauncher#INITIAL_DELAY_MS} for non-modal tasks and 
+	 * {@link TaskLauncher#INITIAL_MODAL_DELAY_MS} for modal tasks.
 	 *  
 	 * @param delay the delay time
 	 * @return this builder
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java
index 7a8b600ddc..4fed9da64d 100644
--- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/TaskLauncher.java
@@ -23,12 +23,19 @@ import ghidra.util.exception.UnableToSwingException;
 
 /**
  * Class to initiate a Task in a new Thread, and to show a progress dialog that indicates
- * activity.  The progress dialog will show an animation in the event that the task of this class
- * cannot show progress.
+ * activity if the task takes too long.  The progress dialog will show an 
+ * animation in the event that the task of this class cannot show progress.
  *
  * 

For complete control of how this class functions, use * {@link #TaskLauncher(Task, Component, int, int)}. Alternatively, for simpler uses, * see one of the many static convenience methods. + * + *

Important Usage Note:
+ * For clients that are not on the Swing thread the behavior of this class is designed to + * prevent deadlocks. When called from a non-Swing thread, this class will attempt to show a + * modal dialog. However, if more than {@link #getSwingTimeoutInSeconds()} elapses while waiting + * for the Swing thread, then this class will give up on using the Swing thread and will not + * create a background thread. Instead, the client code will be run in the client thread. * * *

Modal Usage
@@ -250,12 +257,12 @@ public class TaskLauncher { Swing.runNow(() -> runner.run(), timeout, TimeUnit.SECONDS); } - // template method to allow timeout change + // template method to allow timeout change; used by tests protected int getSwingTimeoutInSeconds() { return 2; } - // template method to allow task runner change + // template method to allow task runner change; used by tests protected TaskRunner createTaskRunner(Task task, Component parent, int delayMs, int dialogWidth) { return new TaskRunner(task, parent, delayMs, dialogWidth); diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filter/FilterTextFieldTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filter/FilterTextFieldTest.java index 7d42f09388..dac917f9d2 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filter/FilterTextFieldTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/filter/FilterTextFieldTest.java @@ -33,7 +33,7 @@ import org.junit.Before; import org.junit.Test; import generic.test.AbstractGenericTest; -import ghidra.generic.function.Callback; +import utility.function.Callback; public class FilterTextFieldTest { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/TaskUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/TaskUtilities.java index a7cccb926d..d3a9c5bd18 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/TaskUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/TaskUtilities.java @@ -37,7 +37,7 @@ public class TaskUtilities { } /** - * Removes the given listener added via {@link #addTrackedTask(Task)}. + * Removes the given listener added via {@link #addTrackedTask(Task,TaskMonitor)}. * @param listener The listener that needs to be removed. */ public static void removeTrackedTaskListener(TrackedTaskListener listener) { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java index d2598da787..b6ccb030f7 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/task/TimeoutTaskMonitor.java @@ -19,12 +19,12 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import ghidra.generic.function.Callback; import ghidra.util.SystemUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.exception.TimeoutException; import ghidra.util.timer.GTimer; import ghidra.util.timer.GTimerMonitor; +import utility.function.Callback; /** * A task monitor that allows clients the ability to specify a timeout after which this monitor diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java index a062e03ffe..d35087c79b 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/graphs/FilteringVisualGraph.java @@ -24,12 +24,12 @@ import com.google.common.collect.Iterators; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.graph.util.Pair; -import ghidra.generic.function.Callback; import ghidra.graph.GraphAlgorithms; import ghidra.graph.viewer.VisualEdge; import ghidra.graph.viewer.VisualVertex; import ghidra.graph.viewer.layout.VisualGraphLayout; import util.CollectionUtils; +import utility.function.Callback; /** * A graph implementation that allows clients to mark vertices and edges as filtered. When diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/GraphJobRunner.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/GraphJobRunner.java index 21466a425c..df6a6a4914 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/GraphJobRunner.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/GraphJobRunner.java @@ -17,11 +17,11 @@ package ghidra.graph.job; import java.util.*; -import ghidra.generic.function.Callback; import ghidra.util.Msg; import ghidra.util.SystemUtilities; import ghidra.util.datastruct.QueueStub; import ghidra.util.exception.AssertException; +import utility.function.Callback; /** * A class to run {@link GraphJob}s. This class will queue jobs and will run them diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualGraphViewUpdater.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualGraphViewUpdater.java index e25a25b3f0..a2dc43f5ac 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualGraphViewUpdater.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualGraphViewUpdater.java @@ -25,7 +25,6 @@ import java.util.Objects; import com.google.common.base.Function; import edu.uci.ics.jung.visualization.*; -import ghidra.generic.function.Callback; import ghidra.graph.VisualGraph; import ghidra.graph.job.*; import ghidra.graph.viewer.edge.routing.BasicEdgeRouter; @@ -34,6 +33,7 @@ import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import ghidra.util.exception.AssertException; import ghidra.util.task.BusyListener; +import utility.function.Callback; /** * This is the class through which operations travel that manipulate the view and graph while diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java index c80465fb0d..c14a645086 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/edge/VisualGraphPathHighlighter.java @@ -23,7 +23,6 @@ import java.util.function.Supplier; import docking.DockingWindowManager; import generic.concurrent.GThreadPool; -import ghidra.generic.function.Callback; import ghidra.graph.*; import ghidra.graph.algo.ChkDominanceAlgorithm; import ghidra.graph.algo.ChkPostDominanceAlgorithm; @@ -34,6 +33,7 @@ import ghidra.util.SystemUtilities; import ghidra.util.datastruct.CallbackAccumulator; import ghidra.util.exception.CancelledException; import ghidra.util.task.*; +import utility.function.Callback; /** * A class that calculates flow between vertices and then triggers that flow to be painted diff --git a/Ghidra/Framework/Graph/src/test/java/ghidra/graph/algo/viewer/AlgorithmSteppingTaskMonitor.java b/Ghidra/Framework/Graph/src/test/java/ghidra/graph/algo/viewer/AlgorithmSteppingTaskMonitor.java index e660c44c1c..14ce330f86 100644 --- a/Ghidra/Framework/Graph/src/test/java/ghidra/graph/algo/viewer/AlgorithmSteppingTaskMonitor.java +++ b/Ghidra/Framework/Graph/src/test/java/ghidra/graph/algo/viewer/AlgorithmSteppingTaskMonitor.java @@ -18,10 +18,10 @@ package ghidra.graph.algo.viewer; import java.util.HashSet; import java.util.Set; -import ghidra.generic.function.Callback; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitorAdapter; +import utility.function.Callback; /** * Task monitor that will trigger a {@link #wait()} when {@link #checkCanceled()} is called. This diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java index 8a405522dd..9f20d106b8 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java @@ -16,9 +16,7 @@ package ghidra.util; import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -159,51 +157,39 @@ public class Swing { throws UnableToSwingException { if (isInHeadlessMode() || SystemUtilities.isEventDispatchThread()) { - doRunSwing(r, true, SWING_RUN_ERROR_MSG); + doRun(r, true, SWING_RUN_ERROR_MSG); return; } - CountDownLatch start = new CountDownLatch(1); - CountDownLatch end = new CountDownLatch(1); - AtomicBoolean timedOut = new AtomicBoolean(); + /* + We use the CyclicBarrier to force this thread and the Swing thread to wait for each + other. This allows the calling thread to know if/when the Swing thread starts and + the Swing thread to know if the calling thread timed-out. + */ + CyclicBarrier start = new CyclicBarrier(2); + CyclicBarrier end = new CyclicBarrier(2); - doRunSwing(() -> { + runLater(() -> { - start.countDown(); + if (!waitFor(start)) { + return; // must have timed-out + } try { - if (timedOut.get()) { - // timed-out waiting for Swing lock, but eventually did get the lock; too late now - return; - } - r.run(); } finally { - end.countDown(); + waitFor(end); } - }, false, SWING_RUN_ERROR_MSG); + }); - try { - timedOut.set(!start.await(timeout, unit)); - } - catch (InterruptedException e) { - // handled below - } - - if (timedOut.get()) { + if (!waitFor(start, timeout, unit)) { throw new UnableToSwingException( "Timed-out waiting for Swing thread lock in " + timeout + " " + unit); } - // we've started; wait for the runnable with no timeout - try { - end.await(); - } - catch (InterruptedException e) { - // we sometimes interrupt our tasks intentionally, so don't report it - } + waitFor(end); } /** @@ -213,7 +199,7 @@ public class Swing { * @param r the runnable */ public static void runLater(Runnable r) { - doRunSwing(r, false, SWING_RUN_ERROR_MSG); + doRun(r, false, SWING_RUN_ERROR_MSG); } public static void runIfSwingOrRunLater(Runnable r) { @@ -230,33 +216,60 @@ public class Swing { } } + private static boolean waitFor(CyclicBarrier barrier, long timeout, TimeUnit unit) { + + try { + barrier.await(timeout, unit); + return true; + } + catch (InterruptedException | BrokenBarrierException | TimeoutException e) { + // our Swing tasks may be interrupted from the framework + } + + // timed-out or was interrupted + return false; + } + + private static boolean waitFor(CyclicBarrier barrier) { + + try { + barrier.await(); + return true; + } + catch (InterruptedException | BrokenBarrierException e) { + // our Swing tasks may be interrupted from the framework + } + return false; + } + private static boolean isInHeadlessMode() { return SystemUtilities.isInHeadlessMode(); } - private static void doRunSwing(Runnable r, boolean wait, String errorMessage) { + private static void doRun(Runnable r, boolean wait, String errorMessage) { if (isInHeadlessMode()) { r.run(); return; } - if (wait) { - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - return; - } - try { - SwingUtilities.invokeAndWait(r); - } - catch (InterruptedException e) { - // we sometimes interrupt our tasks intentionally, so don't report it - } - catch (InvocationTargetException e) { - Msg.error(Swing.class, errorMessage + "\nException Message: " + e.getMessage(), e); - } - } - else { + if (!wait) { SwingUtilities.invokeLater(r); + return; + } + + if (SwingUtilities.isEventDispatchThread()) { + r.run(); + return; + } + + try { + SwingUtilities.invokeAndWait(r); + } + catch (InterruptedException e) { + // we sometimes interrupt our tasks intentionally, so don't report it + } + catch (InvocationTargetException e) { + Msg.error(Swing.class, errorMessage + "\nException Message: " + e.getMessage(), e); } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/Callback.java b/Ghidra/Framework/Utility/src/main/java/utility/function/Callback.java similarity index 97% rename from Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/Callback.java rename to Ghidra/Framework/Utility/src/main/java/utility/function/Callback.java index 0d31bcb328..bf1ac4bbd7 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/Callback.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/function/Callback.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.generic.function; +package utility.function; /** * A generic functional interface that is more semantically sound than {@link Runnable}. Use diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalCallback.java b/Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalCallback.java similarity index 96% rename from Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalCallback.java rename to Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalCallback.java index 36741294d9..d7325ba417 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalCallback.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalCallback.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.generic.function; +package utility.function; /** * A generic functional interface that is more semantically sound than {@link Runnable}. Use diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalConsumer.java b/Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalConsumer.java similarity index 96% rename from Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalConsumer.java rename to Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalConsumer.java index f39088f0a9..5c75c3a1ac 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalConsumer.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.generic.function; +package utility.function; /** * A generic functional interface that allows you to consume an item and potentially throw diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalFunction.java b/Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalFunction.java similarity index 97% rename from Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalFunction.java rename to Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalFunction.java index 82a0f2324d..2c6545ce99 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/generic/function/ExceptionalFunction.java +++ b/Ghidra/Framework/Utility/src/main/java/utility/function/ExceptionalFunction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.generic.function; +package utility.function; /** * A generic functional interface that allows you to consume an item, return a result, From 7c30299876e6bf8ab4b48c1d5937b8b8710c417e Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 21 May 2019 15:55:17 -0400 Subject: [PATCH 6/6] GT-2875 - Unswingable - compile error --- .../ghidra/test/AbstractGhidraHeadlessIntegrationTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index bf2c8458a4..8ecf478a86 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -15,7 +15,7 @@ */ package ghidra.test; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; import java.io.File; import java.io.IOException; @@ -32,7 +32,6 @@ import ghidra.framework.cmd.Command; import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; -import ghidra.generic.function.*; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.*;