diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java index 156f81dae7..505a9a7abc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/data/CreateDataBackgroundCmd.java @@ -21,7 +21,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.util.CodeUnitInsertionException; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.task.TaskMonitor; /** @@ -149,7 +149,7 @@ public class CreateDataBackgroundCmd extends BackgroundCommand { // Allow the Swing thread a chance to paint components that may require // a DB lock. - SystemUtilities.allowSwingToProcessEvents(); + Swing.allowSwingToProcessEvents(); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java index fe35d642ba..d3ea2b928d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/memory/MoveBlockTask.java @@ -58,7 +58,6 @@ public class MoveBlockTask extends ProgramTask { @Override protected void doRun(TaskMonitor monitor) { - // TODO Auto-generated method stub Memory mem = program.getMemory(); MemoryBlock block = mem.getBlock(currentStart); @@ -77,23 +76,23 @@ public class MoveBlockTask extends ProgramTask { } } catch (OutOfMemoryError e) { - monitor.setMessage(msg = "Insufficient memory to complete operation"); + msg = "Insufficient memory to complete operation"; cause = e; } catch (NotFoundException exc) { - monitor.setMessage(msg = "Memory block not found"); + msg = "Memory block not found"; cause = exc; } catch (MemoryConflictException exc) { - monitor.setMessage(msg = exc.getMessage()); + msg = exc.getMessage(); cause = exc; } catch (MemoryBlockException exc) { - monitor.setMessage(msg = exc.getMessage()); + msg = exc.getMessage(); cause = exc; } catch (IllegalArgumentException e) { - monitor.setMessage(msg = e.getMessage()); + msg = e.getMessage(); cause = e; } catch (Throwable t) { @@ -102,25 +101,18 @@ public class MoveBlockTask extends ProgramTask { if (msg == null) { msg = t.toString(); } - monitor.setMessage(msg); cause = t; } + + monitor.setMessage(msg); listener.moveBlockCompleted(this); throw new RollbackException(msg, cause); } - /** - * Return true if the user cancelled the move command. - */ public boolean isCancelled() { return wasCancelled; } - /** - * Return whether the block was successfully moved. - * - * @return true if the block was moved - */ public boolean getStatus() { return status; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java index 71209ce8ca..f37d116456 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clear/ClearCmd.java @@ -25,7 +25,7 @@ import ghidra.program.model.lang.Register; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitorAdapter; @@ -184,7 +184,7 @@ public class ClearCmd extends BackgroundCommand { monitor.setProgress(progress); // Allow Swing a chance to paint components that may require a DB lock - SystemUtilities.allowSwingToProcessEvents(); + Swing.allowSwingToProcessEvents(); } } previousRangeAddrCnt += range.getLength(); @@ -295,7 +295,7 @@ public class ClearCmd extends BackgroundCommand { AddressRangeIterator it = clearView.getAddressRanges(); while (it.hasNext()) { - AddressRange currentRange = it.next(); + AddressRange currentRange = it.next(); Address start = currentRange.getMinAddress(); Address end = currentRange.getMaxAddress(); clearAddresses(monitor, listing, start, end); @@ -320,7 +320,7 @@ public class ClearCmd extends BackgroundCommand { monitor.incrementProgress(numDone); // Allow the Swing thread a chance to paint components that may require a DB lock - SystemUtilities.allowSwingToProcessEvents(); + Swing.allowSwingToProcessEvents(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java index 1ac158afa6..5053f21bd5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MoveBlockDialog.java @@ -18,8 +18,6 @@ package ghidra.app.plugin.core.memory; import java.awt.Cursor; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import docking.DialogComponentProvider; import docking.widgets.label.GDLabel; @@ -32,7 +30,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; import ghidra.util.HelpLocation; import ghidra.util.layout.PairLayout; -import ghidra.util.task.TaskLauncher; +import ghidra.util.task.BackgroundThreadTaskLauncher; import ghidra.util.task.TaskMonitorAdapter; /** @@ -77,22 +75,19 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc */ @Override public void moveBlockCompleted(final MoveBlockTask cmd) { - Runnable r = new Runnable() { - @Override - public void run() { - if (cmd.getStatus()) { + Runnable r = () -> { + if (cmd.getStatus()) { + close(); + model.dispose(); + } + else { + setCursor(Cursor.getDefaultCursor()); + setOkEnabled(false); + if (cmd.isCancelled()) { + tool.setStatusInfo(getStatusText()); close(); model.dispose(); } - else { - setCursor(Cursor.getDefaultCursor()); - setOkEnabled(false); - if (cmd.isCancelled()) { - tool.setStatusInfo(getStatusText()); - close(); - model.dispose(); - } - } } }; SwingUtilities.invokeLater(r); @@ -142,7 +137,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc protected void okCallback() { setOkEnabled(false); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - new TaskLauncher(model.makeTask(), new TaskMonitorAdapter() { + + BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(model.makeTask()); + launcher.run(new TaskMonitorAdapter() { @Override public void setMessage(String message) { setStatusText(message); @@ -176,18 +173,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc newEndField = new AddressInput(); newEndField.setName("newEnd"); - newStartField.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - startChanged(); - } - }); - newEndField.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - endChanged(); - } - }); + newStartField.addChangeListener(e -> startChanged()); + newEndField.addChangeListener(e -> endChanged()); panel.add(new GLabel("Name:", SwingConstants.RIGHT)); panel.add(blockNameLabel); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java index 7d579a7255..72721fd9a5 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java @@ -21,7 +21,6 @@ import org.junit.*; import ghidra.app.cmd.memory.MoveBlockListener; import ghidra.app.cmd.memory.MoveBlockTask; -import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.database.data.DataTypeManagerDB; @@ -33,32 +32,18 @@ import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.task.*; -/** - * Test the model that moves a block of memory. - * - * - */ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest implements MoveBlockListener { + private Program notepad; private Program x8051; - private PluginTool tool; private TestEnv env; private MoveBlockModel model; private MemoryBlock block; - private boolean expectedStatus; - private boolean moveCompleted; - private boolean status; - private String errMsg; - /** - * Constructor for MoveBlockModelTest. - * - * @param name - */ - public MoveBlockModelTest() { - super(); - } + private volatile boolean moveCompleted; + private volatile boolean status; + private volatile String errMsg; private Program buildProgram1(String programName) throws Exception { ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY); @@ -84,13 +69,10 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest return builder.getProgram(); } - /* - * @see TestCase#setUp() - */ @Before public void setUp() throws Exception { env = new TestEnv(); - tool = env.getTool(); + notepad = buildProgram1("notepad"); x8051 = buildProgram2("x08"); block = notepad.getMemory().getBlock(getNotepadAddr(0x1001000)); @@ -109,13 +91,8 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest x8051.endTransaction(transactionID, true); } - /* - * @see TestCase#tearDown() - */ @After public void tearDown() { - env.release(x8051); - env.release(notepad); env.dispose(); } @@ -152,25 +129,23 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest @Test public void testMoveBlockStart() throws Exception { model.setNewStartAddress(getNotepadAddr(0x2000000)); - expectedStatus = true; + launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !notepad.canLock()) { - Thread.sleep(1000); - } - assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); + waitForCondition(() -> moveCompleted && notepad.canLock()); + assertTrue("Error message= [" + errMsg + "], ", status); } @Test public void testMoveBlockEnd() throws Exception { model.setNewEndAddress(getNotepadAddr(0x2007500)); - expectedStatus = true; + launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !notepad.canLock()) { - Thread.sleep(1000); - } - assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); + waitForCondition(() -> moveCompleted && notepad.canLock()); + assertTrue("Error message= [" + errMsg + "], ", status); } @Test @@ -194,13 +169,15 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest start = getAddr(x8051, "INTMEM", 0x50); model.setNewStartAddress(start); assertEquals(getAddr(x8051, "INTMEM", 0xcf), model.getNewEndAddress()); - expectedStatus = false; + + setErrorsExpected(true); launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !x8051.canLock()) { - Thread.sleep(1000); - } - assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); + waitForCondition(() -> moveCompleted && x8051.canLock()); + setErrorsExpected(false); + + assertFalse("Error message= [" + errMsg + "], ", status); } @Test @@ -213,13 +190,12 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest model.initialize(block); start = getAddr(x8051, "CODE", 0x2000); model.setNewStartAddress(start); - expectedStatus = true; - moveCompleted = false; + launch(model.makeTask()); + // wait until the we get the move complete notification - while (!moveCompleted || !x8051.canLock()) { - Thread.sleep(1000); - } + waitForCondition(() -> moveCompleted && x8051.canLock()); + // make sure settings on data got moved DataTypeManagerDB dtm = ((ProgramDB) x8051).getDataManager(); @@ -257,19 +233,19 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest Address newStart = memBlock.getStart().getNewAddress(0x01002000); model.setNewStartAddress(newStart); - expectedStatus = false; - errMsg = null; - + setErrorsExpected(true); launch(model.makeTask()); - while (!moveCompleted || !notepad.canLock()) { - Thread.sleep(1000); - } - assertTrue(!expectedStatus); + + waitForCondition(() -> moveCompleted && notepad.canLock()); + setErrorsExpected(false); + assertNotNull(errMsg); } private void launch(Task task) { - new TaskLauncher(task, new TaskMonitorAdapter() { + + BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task); + launcher.run(new TaskMonitorAdapter() { @Override public void setMessage(String message) { errMsg = message; @@ -286,21 +262,14 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest return space.getAddress(offset); } - /** - * @see ghidra.app.plugin.contrib.memory.MoveBlockListener#moveBlockCompleted(boolean, - * java.lang.String) - */ @Override public void moveBlockCompleted(MoveBlockTask cmd) { moveCompleted = true; this.status = cmd.getStatus(); } - /** - * @see ghidra.app.plugin.contrib.memory.MoveBlockListener#stateChanged() - */ @Override public void stateChanged() { + // stub } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 43c788dce1..66ade1485d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -15,8 +15,7 @@ */ package docking.test; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.awt.*; import java.awt.datatransfer.*; @@ -1760,9 +1759,11 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { */ public static void setErrorsExpected(boolean expected) { if (expected) { + Msg.error(AbstractDockingTest.class, ">>>>>>>>>>>>>>>> Expected Exception"); ConcurrentTestExceptionHandler.disable(); } else { + Msg.error(AbstractDockingTest.class, "<<<<<<<<<<<<<<<< End Expected Exception"); ConcurrentTestExceptionHandler.enable(); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java index ec6aa4d02d..3c2c9c1529 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/constrainteditor/DataLoadingConstraintEditor.java @@ -221,7 +221,8 @@ public abstract class DataLoadingConstraintEditor extends AbstractColumnConst reloadDataButton.setVisible(false); Task task = new LoadDataTask(); task.addTaskListener(this); - new TaskLauncher(task, (TaskMonitor) taskMonitorComponent); + BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task); + launcher.run(taskMonitorComponent); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java index 679524d418..4ca176904b 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/task/BackgroundThreadTaskLauncher.java @@ -15,29 +15,39 @@ */ package ghidra.util.task; +import java.util.concurrent.Executor; + +import generic.concurrent.GThreadPool; +import ghidra.util.Swing; import ghidra.util.TaskUtilities; /** * Helper class to launch the given task in a background thread This helper will not - * show a task dialog. See {@link TaskLauncher}. + * show a task dialog. + * + *

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

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

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

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

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

This method will throw an exception if the Swing thread is not available within the - * given timeout. This method is useful for preventing deadlocks. - * - * @param r the runnable - * @param timeout the timeout value - * @param unit the time unit of the timeout value - * @throws UnableToSwingException if the timeout was reach waiting for the Swing thead - * @see #runSwingNow(Supplier) if you need to return a value from the Swing thread. - */ - public static void runSwingNow(Runnable r, long timeout, TimeUnit unit) - throws UnableToSwingException { - - if (isInHeadlessMode() || SystemUtilities.isEventDispatchThread()) { - doRunSwing(r, true, SWING_RUN_ERROR_MSG); - return; - } - - CountDownLatch start = new CountDownLatch(1); - CountDownLatch end = new CountDownLatch(1); - AtomicBoolean timedOut = new AtomicBoolean(); - - doRunSwing(() -> { - - start.countDown(); - - try { - if (timedOut.get()) { - // timed-out waiting for Swing lock, but eventually did get the lock; too late now - return; - } - - r.run(); - } - finally { - end.countDown(); - } - - }, false, SWING_RUN_ERROR_MSG); - - try { - timedOut.set(!start.await(timeout, unit)); - } - catch (InterruptedException e) { - // handled below - } - - if (timedOut.get()) { - throw new UnableToSwingException( - "Timed-out waiting for Swing thread lock in " + timeout + " " + unit); - } - - // we've started! - try { - end.await(); // wait FOREVER! - } - catch (InterruptedException e) { - // we sometimes interrupt our tasks intentionally, so don't report it - } + Swing.runNow(r); } /** @@ -344,48 +260,11 @@ public class SystemUtilities { * @param r the runnable */ public static void runSwingLater(Runnable r) { - doRunSwing(r, false, SWING_RUN_ERROR_MSG); + Swing.runLater(r); } public static void runIfSwingOrPostSwingLater(Runnable r) { - if (isInHeadlessMode()) { - r.run(); - return; - } - - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - } - else { - SwingUtilities.invokeLater(r); - } - } - - private static void doRunSwing(Runnable r, boolean wait, String errorMessage) { - if (isInHeadlessMode()) { - r.run(); - return; - } - - if (wait) { - if (SwingUtilities.isEventDispatchThread()) { - r.run(); - return; - } - try { - SwingUtilities.invokeAndWait(r); - } - catch (InterruptedException e) { - // we sometimes interrupt our tasks intentionally, so don't report it - } - catch (InvocationTargetException e) { - Msg.error(SystemUtilities.class, - errorMessage + "\nException Message: " + e.getMessage(), e); - } - } - else { - SwingUtilities.invokeLater(r); - } + Swing.runIfSwingOrRunLater(r); } /** @@ -543,25 +422,7 @@ public class SystemUtilities { * @return true if this is the event dispatch thread -OR- is in headless mode. */ public static boolean isEventDispatchThread() { - if (isInHeadlessMode()) { - return true; - } - - // Note: just calling this method may trigger the AWT thread to get created - return SwingUtilities.isEventDispatchThread(); - } - - /** - * Wait until AWT event queue (Swing) has been flushed and no more (to a point) events - * are pending. - */ - public static void allowSwingToProcessEvents() { - Runnable r = () -> { - // do nothing...this is just a placeholder runnable that gets put onto the stack - }; - runSwingNow(r); - runSwingNow(r); - runSwingNow(r); + return Swing.isEventDispatchThread(); } /**