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 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 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 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 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 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 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.
*
* 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: Modal Usage
+ * 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
+ * TaskBuilder.withTask(new AwesomeTask(awesomeStuff)).launchModal();
+ *
+ *
+ * Or,
+ *
+ *
+ * {@link TaskLauncher#launch(Task) TaskLauncher.launch}(new AwesomeTask(awesomeStuff));
+ *
+ *
+ *
*
+ * 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
*
*
* 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 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.
*
*
*
@@ -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.*;