GT-2875 - Unswingable - review fixes

This commit is contained in:
dragonmacher 2019-05-20 15:47:41 -04:00
parent 8dffd377fb
commit 07f0371a50
16 changed files with 395 additions and 374 deletions

View file

@ -21,7 +21,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.SystemUtilities; import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor; 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 // Allow the Swing thread a chance to paint components that may require
// a DB lock. // a DB lock.
SystemUtilities.allowSwingToProcessEvents(); Swing.allowSwingToProcessEvents();
} }
} }
} }

View file

@ -58,7 +58,6 @@ public class MoveBlockTask extends ProgramTask {
@Override @Override
protected void doRun(TaskMonitor monitor) { protected void doRun(TaskMonitor monitor) {
// TODO Auto-generated method stub
Memory mem = program.getMemory(); Memory mem = program.getMemory();
MemoryBlock block = mem.getBlock(currentStart); MemoryBlock block = mem.getBlock(currentStart);
@ -77,23 +76,23 @@ public class MoveBlockTask extends ProgramTask {
} }
} }
catch (OutOfMemoryError e) { catch (OutOfMemoryError e) {
monitor.setMessage(msg = "Insufficient memory to complete operation"); msg = "Insufficient memory to complete operation";
cause = e; cause = e;
} }
catch (NotFoundException exc) { catch (NotFoundException exc) {
monitor.setMessage(msg = "Memory block not found"); msg = "Memory block not found";
cause = exc; cause = exc;
} }
catch (MemoryConflictException exc) { catch (MemoryConflictException exc) {
monitor.setMessage(msg = exc.getMessage()); msg = exc.getMessage();
cause = exc; cause = exc;
} }
catch (MemoryBlockException exc) { catch (MemoryBlockException exc) {
monitor.setMessage(msg = exc.getMessage()); msg = exc.getMessage();
cause = exc; cause = exc;
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
monitor.setMessage(msg = e.getMessage()); msg = e.getMessage();
cause = e; cause = e;
} }
catch (Throwable t) { catch (Throwable t) {
@ -102,25 +101,18 @@ public class MoveBlockTask extends ProgramTask {
if (msg == null) { if (msg == null) {
msg = t.toString(); msg = t.toString();
} }
monitor.setMessage(msg);
cause = t; cause = t;
} }
monitor.setMessage(msg);
listener.moveBlockCompleted(this); listener.moveBlockCompleted(this);
throw new RollbackException(msg, cause); throw new RollbackException(msg, cause);
} }
/**
* Return true if the user cancelled the move command.
*/
public boolean isCancelled() { public boolean isCancelled() {
return wasCancelled; return wasCancelled;
} }
/**
* Return whether the block was successfully moved.
*
* @return true if the block was moved
*/
public boolean getStatus() { public boolean getStatus() {
return status; return status;
} }

View file

@ -25,7 +25,7 @@ import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.SystemUtilities; import ghidra.util.Swing;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter; import ghidra.util.task.TaskMonitorAdapter;
@ -184,7 +184,7 @@ public class ClearCmd extends BackgroundCommand {
monitor.setProgress(progress); monitor.setProgress(progress);
// Allow Swing a chance to paint components that may require a DB lock // Allow Swing a chance to paint components that may require a DB lock
SystemUtilities.allowSwingToProcessEvents(); Swing.allowSwingToProcessEvents();
} }
} }
previousRangeAddrCnt += range.getLength(); previousRangeAddrCnt += range.getLength();
@ -320,7 +320,7 @@ public class ClearCmd extends BackgroundCommand {
monitor.incrementProgress(numDone); monitor.incrementProgress(numDone);
// Allow the Swing thread a chance to paint components that may require a DB lock // Allow the Swing thread a chance to paint components that may require a DB lock
SystemUtilities.allowSwingToProcessEvents(); Swing.allowSwingToProcessEvents();
} }
} }

View file

@ -18,8 +18,6 @@ package ghidra.app.plugin.core.memory;
import java.awt.Cursor; import java.awt.Cursor;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
@ -32,7 +30,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressFactory;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.BackgroundThreadTaskLauncher;
import ghidra.util.task.TaskMonitorAdapter; import ghidra.util.task.TaskMonitorAdapter;
/** /**
@ -77,9 +75,7 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
*/ */
@Override @Override
public void moveBlockCompleted(final MoveBlockTask cmd) { public void moveBlockCompleted(final MoveBlockTask cmd) {
Runnable r = new Runnable() { Runnable r = () -> {
@Override
public void run() {
if (cmd.getStatus()) { if (cmd.getStatus()) {
close(); close();
model.dispose(); model.dispose();
@ -93,7 +89,6 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
model.dispose(); model.dispose();
} }
} }
}
}; };
SwingUtilities.invokeLater(r); SwingUtilities.invokeLater(r);
} }
@ -142,7 +137,9 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
protected void okCallback() { protected void okCallback() {
setOkEnabled(false); setOkEnabled(false);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
new TaskLauncher(model.makeTask(), new TaskMonitorAdapter() {
BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(model.makeTask());
launcher.run(new TaskMonitorAdapter() {
@Override @Override
public void setMessage(String message) { public void setMessage(String message) {
setStatusText(message); setStatusText(message);
@ -176,18 +173,8 @@ public class MoveBlockDialog extends DialogComponentProvider implements MoveBloc
newEndField = new AddressInput(); newEndField = new AddressInput();
newEndField.setName("newEnd"); newEndField.setName("newEnd");
newStartField.addChangeListener(new ChangeListener() { newStartField.addChangeListener(e -> startChanged());
@Override newEndField.addChangeListener(e -> endChanged());
public void stateChanged(ChangeEvent e) {
startChanged();
}
});
newEndField.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
endChanged();
}
});
panel.add(new GLabel("Name:", SwingConstants.RIGHT)); panel.add(new GLabel("Name:", SwingConstants.RIGHT));
panel.add(blockNameLabel); panel.add(blockNameLabel);

View file

@ -21,7 +21,6 @@ import org.junit.*;
import ghidra.app.cmd.memory.MoveBlockListener; import ghidra.app.cmd.memory.MoveBlockListener;
import ghidra.app.cmd.memory.MoveBlockTask; import ghidra.app.cmd.memory.MoveBlockTask;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.database.data.DataTypeManagerDB; import ghidra.program.database.data.DataTypeManagerDB;
@ -33,32 +32,18 @@ import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv; import ghidra.test.TestEnv;
import ghidra.util.task.*; import ghidra.util.task.*;
/**
* Test the model that moves a block of memory.
*
*
*/
public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
implements MoveBlockListener { implements MoveBlockListener {
private Program notepad; private Program notepad;
private Program x8051; private Program x8051;
private PluginTool tool;
private TestEnv env; private TestEnv env;
private MoveBlockModel model; private MoveBlockModel model;
private MemoryBlock block; private MemoryBlock block;
private boolean expectedStatus;
private boolean moveCompleted;
private boolean status;
private String errMsg;
/** private volatile boolean moveCompleted;
* Constructor for MoveBlockModelTest. private volatile boolean status;
* private volatile String errMsg;
* @param name
*/
public MoveBlockModelTest() {
super();
}
private Program buildProgram1(String programName) throws Exception { private Program buildProgram1(String programName) throws Exception {
ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY); ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY);
@ -84,13 +69,10 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
return builder.getProgram(); return builder.getProgram();
} }
/*
* @see TestCase#setUp()
*/
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
env = new TestEnv(); env = new TestEnv();
tool = env.getTool();
notepad = buildProgram1("notepad"); notepad = buildProgram1("notepad");
x8051 = buildProgram2("x08"); x8051 = buildProgram2("x08");
block = notepad.getMemory().getBlock(getNotepadAddr(0x1001000)); block = notepad.getMemory().getBlock(getNotepadAddr(0x1001000));
@ -109,13 +91,8 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
x8051.endTransaction(transactionID, true); x8051.endTransaction(transactionID, true);
} }
/*
* @see TestCase#tearDown()
*/
@After @After
public void tearDown() { public void tearDown() {
env.release(x8051);
env.release(notepad);
env.dispose(); env.dispose();
} }
@ -152,25 +129,23 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
@Test @Test
public void testMoveBlockStart() throws Exception { public void testMoveBlockStart() throws Exception {
model.setNewStartAddress(getNotepadAddr(0x2000000)); model.setNewStartAddress(getNotepadAddr(0x2000000));
expectedStatus = true;
launch(model.makeTask()); launch(model.makeTask());
// wait until the we get the move complete notification // wait until the we get the move complete notification
while (!moveCompleted || !notepad.canLock()) { waitForCondition(() -> moveCompleted && notepad.canLock());
Thread.sleep(1000); assertTrue("Error message= [" + errMsg + "], ", status);
}
assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status);
} }
@Test @Test
public void testMoveBlockEnd() throws Exception { public void testMoveBlockEnd() throws Exception {
model.setNewEndAddress(getNotepadAddr(0x2007500)); model.setNewEndAddress(getNotepadAddr(0x2007500));
expectedStatus = true;
launch(model.makeTask()); launch(model.makeTask());
// wait until the we get the move complete notification // wait until the we get the move complete notification
while (!moveCompleted || !notepad.canLock()) { waitForCondition(() -> moveCompleted && notepad.canLock());
Thread.sleep(1000); assertTrue("Error message= [" + errMsg + "], ", status);
}
assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status);
} }
@Test @Test
@ -194,13 +169,15 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
start = getAddr(x8051, "INTMEM", 0x50); start = getAddr(x8051, "INTMEM", 0x50);
model.setNewStartAddress(start); model.setNewStartAddress(start);
assertEquals(getAddr(x8051, "INTMEM", 0xcf), model.getNewEndAddress()); assertEquals(getAddr(x8051, "INTMEM", 0xcf), model.getNewEndAddress());
expectedStatus = false;
setErrorsExpected(true);
launch(model.makeTask()); launch(model.makeTask());
// wait until the we get the move complete notification // wait until the we get the move complete notification
while (!moveCompleted || !x8051.canLock()) { waitForCondition(() -> moveCompleted && x8051.canLock());
Thread.sleep(1000); setErrorsExpected(false);
}
assertEquals("Error message= [" + errMsg + "], ", expectedStatus, status); assertFalse("Error message= [" + errMsg + "], ", status);
} }
@Test @Test
@ -213,13 +190,12 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
model.initialize(block); model.initialize(block);
start = getAddr(x8051, "CODE", 0x2000); start = getAddr(x8051, "CODE", 0x2000);
model.setNewStartAddress(start); model.setNewStartAddress(start);
expectedStatus = true;
moveCompleted = false;
launch(model.makeTask()); launch(model.makeTask());
// wait until the we get the move complete notification // wait until the we get the move complete notification
while (!moveCompleted || !x8051.canLock()) { waitForCondition(() -> moveCompleted && x8051.canLock());
Thread.sleep(1000);
}
// make sure settings on data got moved // make sure settings on data got moved
DataTypeManagerDB dtm = ((ProgramDB) x8051).getDataManager(); DataTypeManagerDB dtm = ((ProgramDB) x8051).getDataManager();
@ -257,19 +233,19 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
Address newStart = memBlock.getStart().getNewAddress(0x01002000); Address newStart = memBlock.getStart().getNewAddress(0x01002000);
model.setNewStartAddress(newStart); model.setNewStartAddress(newStart);
expectedStatus = false; setErrorsExpected(true);
errMsg = null;
launch(model.makeTask()); launch(model.makeTask());
while (!moveCompleted || !notepad.canLock()) {
Thread.sleep(1000); waitForCondition(() -> moveCompleted && notepad.canLock());
} setErrorsExpected(false);
assertTrue(!expectedStatus);
assertNotNull(errMsg); assertNotNull(errMsg);
} }
private void launch(Task task) { private void launch(Task task) {
new TaskLauncher(task, new TaskMonitorAdapter() {
BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task);
launcher.run(new TaskMonitorAdapter() {
@Override @Override
public void setMessage(String message) { public void setMessage(String message) {
errMsg = message; errMsg = message;
@ -286,21 +262,14 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest
return space.getAddress(offset); return space.getAddress(offset);
} }
/**
* @see ghidra.app.plugin.contrib.memory.MoveBlockListener#moveBlockCompleted(boolean,
* java.lang.String)
*/
@Override @Override
public void moveBlockCompleted(MoveBlockTask cmd) { public void moveBlockCompleted(MoveBlockTask cmd) {
moveCompleted = true; moveCompleted = true;
this.status = cmd.getStatus(); this.status = cmd.getStatus();
} }
/**
* @see ghidra.app.plugin.contrib.memory.MoveBlockListener#stateChanged()
*/
@Override @Override
public void stateChanged() { public void stateChanged() {
// stub
} }
} }

View file

@ -15,8 +15,7 @@
*/ */
package docking.test; package docking.test;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.*;
import static org.junit.Assert.fail;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.*; import java.awt.datatransfer.*;
@ -1760,9 +1759,11 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
*/ */
public static void setErrorsExpected(boolean expected) { public static void setErrorsExpected(boolean expected) {
if (expected) { if (expected) {
Msg.error(AbstractDockingTest.class, ">>>>>>>>>>>>>>>> Expected Exception");
ConcurrentTestExceptionHandler.disable(); ConcurrentTestExceptionHandler.disable();
} }
else { else {
Msg.error(AbstractDockingTest.class, "<<<<<<<<<<<<<<<< End Expected Exception");
ConcurrentTestExceptionHandler.enable(); ConcurrentTestExceptionHandler.enable();
} }
} }

View file

@ -221,7 +221,8 @@ public abstract class DataLoadingConstraintEditor<T> extends AbstractColumnConst
reloadDataButton.setVisible(false); reloadDataButton.setVisible(false);
Task task = new LoadDataTask(); Task task = new LoadDataTask();
task.addTaskListener(this); task.addTaskListener(this);
new TaskLauncher(task, (TaskMonitor) taskMonitorComponent); BackgroundThreadTaskLauncher launcher = new BackgroundThreadTaskLauncher(task);
launcher.run(taskMonitorComponent);
} }
@Override @Override

View file

@ -15,29 +15,39 @@
*/ */
package ghidra.util.task; package ghidra.util.task;
import java.util.concurrent.Executor;
import generic.concurrent.GThreadPool;
import ghidra.util.Swing;
import ghidra.util.TaskUtilities; import ghidra.util.TaskUtilities;
/** /**
* Helper class to launch the given task in a background thread This helper will not * 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.
*
* <p>This class is useful when you want to run the task and use a monitor that is embedded
* in some other component.
*
* <p>See {@link TaskLauncher}.
*/ */
class BackgroundThreadTaskLauncher { public class BackgroundThreadTaskLauncher {
private Task task; private Task task;
BackgroundThreadTaskLauncher(Task task) { public BackgroundThreadTaskLauncher(Task task) {
this.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 // add the task here, so we can track it before it is actually started by the thread
TaskUtilities.addTrackedTask(task, monitor); TaskUtilities.addTrackedTask(task, monitor);
String name = "Task - " + task.getTaskTitle(); 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); task.monitoredRun(monitor);
}, name); });
taskThread.setPriority(Thread.MIN_PRIORITY);
taskThread.start();
} }
} }

View file

@ -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);
}
}

View file

@ -18,9 +18,7 @@ package ghidra.util.task;
import java.awt.Component; import java.awt.Component;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.swing.SwingUtilities; import ghidra.util.Swing;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.UnableToSwingException; import ghidra.util.exception.UnableToSwingException;
/** /**
@ -232,34 +230,19 @@ public class TaskLauncher {
public TaskLauncher(Task task, Component parent, int delay, int dialogWidth) { public TaskLauncher(Task task, Component parent, int delay, int dialogWidth) {
try { try {
runSwing(task, parent, delay, dialogWidth); scheduleFromSwingThread(task, parent, delay, dialogWidth);
} }
catch (UnableToSwingException e) { catch (UnableToSwingException e) {
runInThisBackgroundThread(task); runInThisBackgroundThread(task);
} }
} }
/** private void scheduleFromSwingThread(Task task, Component parent, int delay, int dialogWidth)
* 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.
*
* <p>See <a href="#modal_usage">notes on modal usage</a>
*
* @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)
throws UnableToSwingException { throws UnableToSwingException {
SwingTaskLauncher swinger = buildSwingLauncher(task, parent, delay, dialogWidth); TaskRunner runner = createTaskRunner(task, parent, delay, dialogWidth);
if (SwingUtilities.isEventDispatchThread()) { if (Swing.isEventDispatchThread()) {
swinger.run(); runner.run();
return; return;
} }
@ -271,26 +254,28 @@ public class TaskLauncher {
// This will throw an exception if we could not get the Swing lock. When that happens, // This will throw an exception if we could not get the Swing lock. When that happens,
// the task was NOT run. // the task was NOT run.
int timeout = getSwingTimeoutInSeconds(); int timeout = getSwingTimeoutInSeconds();
SystemUtilities.runSwingNow(() -> { Swing.runNow(() -> runner.run(), timeout, TimeUnit.SECONDS);
swinger.run();
}, timeout, TimeUnit.SECONDS);
} }
protected int getSwingTimeoutInSeconds() { protected int getSwingTimeoutInSeconds() {
return 2; return 2;
} }
protected SwingTaskLauncher buildSwingLauncher(Task task, Component parent, int delay, protected TaskRunner createTaskRunner(Task task, Component parent, int delay, int dialogWidth) {
int dialogWidth) { return new TaskRunner(task, parent, delay, dialogWidth);
return new SwingTaskLauncher(task, parent, delay, dialogWidth);
} }
/**
* Runs the given task in the current thread, which <b>cannot be the Swing thread</b>
*
* @param task the task to run
* @throws IllegalStateException if the given thread is the Swing thread
*/
protected void runInThisBackgroundThread(Task task) { protected void runInThisBackgroundThread(Task task) {
if (SwingUtilities.isEventDispatchThread()) { if (Swing.isEventDispatchThread()) {
throw new IllegalStateException("Must not call this method from the Swing thread"); throw new IllegalStateException("Must not call this method from the Swing thread");
} }
CurrentThreadTaskLauncher runner = new CurrentThreadTaskLauncher(task); task.monitoredRun(TaskMonitor.DUMMY);
runner.run();
} }
} }

View file

@ -16,17 +16,18 @@
package ghidra.util.task; package ghidra.util.task;
import java.awt.Component; import java.awt.Component;
import java.util.concurrent.Executor;
import javax.swing.SwingUtilities; import generic.concurrent.GThreadPool;
import generic.util.WindowUtilities; 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 * Helper class to launch the given task in a background thread, showing a task dialog if
* this task takes to long. See {@link TaskLauncher}. * this task takes to long. See {@link TaskLauncher}.
*/ */
class SwingTaskLauncher { class TaskRunner {
protected Task task; protected Task task;
private Component parent; 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.task = task;
this.parent = parent; this.parent = parent;
this.delay = delay; this.delay = delay;
@ -51,26 +52,16 @@ class SwingTaskLauncher {
} }
void run() { 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); this.taskDialog = buildTaskDialog(parent);
startBackgroundThread(taskDialog); startBackgroundThread(taskDialog);
taskDialog.show(Math.max(delay, 0)); 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) { 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 // on the Swing thread to prevent exceptions while painting (as seen when using the
// Nimbus Look and Feel). // Nimbus Look and Feel).
// //
SystemUtilities.runSwingNow(() -> {
taskDialog = createTaskDialog(comp); taskDialog = createTaskDialog(comp);
taskDialog.setMinimumSize(dialogWidth, 0); taskDialog.setMinimumSize(dialogWidth, 0);
});
if (task.isInterruptible() || task.isForgettable()) { if (task.isInterruptible() || task.isForgettable()) {
taskDialog.addCancelledListener(monitorChangeListener); taskDialog.addCancelledListener(monitorChangeListener);
@ -100,21 +89,16 @@ class SwingTaskLauncher {
TaskUtilities.addTrackedTask(task, monitor); TaskUtilities.addTrackedTask(task, monitor);
String name = "Task - " + task.getTaskTitle(); 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); task.monitoredRun(monitor);
taskProcessed();
}, name);
taskThread.setPriority(Thread.MIN_PRIORITY);
taskThread.start();
}
private void taskProcessed() {
if (taskDialog != null) {
taskDialog.taskProcessed(); taskDialog.taskProcessed();
} });
} }
protected TaskDialog createTaskDialog(Component comp) { private TaskDialog createTaskDialog(Component comp) {
Component currentParent = comp; Component currentParent = comp;
if (currentParent != null) { if (currentParent != null) {
currentParent = WindowUtilities.windowForComponent(comp); currentParent = WindowUtilities.windowForComponent(comp);

View file

@ -92,10 +92,10 @@ public class AbstractTaskTest extends AbstractDockingTest {
} }
@Override @Override
protected SwingTaskLauncher buildSwingLauncher(Task task, Component parent, int delay, protected TaskRunner createTaskRunner(Task task, Component parent, int delay,
int dialogWidth) { int dialogWidth) {
return new SwingTaskLauncher(task, parent, delay, dialogWidth) { return new TaskRunner(task, parent, delay, dialogWidth) {
@Override @Override
protected TaskDialog buildTaskDialog(Component comp) { protected TaskDialog buildTaskDialog(Component comp) {
dialogSpy = new TaskDialogSpy(task); dialogSpy = new TaskDialogSpy(task);

View file

@ -174,7 +174,7 @@ public class GThreadPool {
* *
* @return the executor * @return the executor
*/ */
public GThreadPoolExecutor getExecutor() { public Executor getExecutor() {
return executor; return executor;
} }

View file

@ -17,6 +17,7 @@ package ghidra.util.worker;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import ghidra.util.Swing;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -27,8 +28,6 @@ import ghidra.util.task.TaskMonitor;
*/ */
public class Worker extends AbstractWorker<Job> { public class Worker extends AbstractWorker<Job> {
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 * A convenience method to create a Worker that uses a shared thread pool for performing
* operations for GUI clients in a background thread * operations for GUI clients in a background thread
@ -42,7 +41,7 @@ public class Worker extends AbstractWorker<Job> {
* @return the new worker * @return the new worker
*/ */
public static Worker createGuiWorker() { public static Worker createGuiWorker() {
return new Worker(GSWING_THREAD_POOL_NAME); return new Worker(Swing.GSWING_THREAD_POOL_NAME);
} }
/** /**

View file

@ -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.
*
* <pre>
* String value = runNow(() -> label.getText());
* </pre>
*
* @param s the supplier that will be called on the Swing thread
* @return the result of the supplier
* @see #runNow(Runnable)
*/
public static <T> T runNow(Supplier<T> s) {
AtomicReference<T> 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
*
* <p>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
}
}

View file

@ -17,21 +17,15 @@ package ghidra.util;
import java.awt.Font; import java.awt.Font;
import java.io.*; import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; 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 java.util.function.Supplier;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import ghidra.util.exception.UnableToSwingException;
import utilities.util.reflection.ReflectionUtilities; import utilities.util.reflection.ReflectionUtilities;
/** /**
@ -41,9 +35,6 @@ import utilities.util.reflection.ReflectionUtilities;
*/ */
public class SystemUtilities { 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 final static String DATE_TIME_FORMAT = "MMM d yyyy HH:mm:ss";
private static String userName; private static String userName;
@ -249,9 +240,7 @@ public class SystemUtilities {
* @see #runSwingNow(Runnable) * @see #runSwingNow(Runnable)
*/ */
public static <T> T runSwingNow(Supplier<T> s) { public static <T> T runSwingNow(Supplier<T> s) {
AtomicReference<T> ref = new AtomicReference<>(); return Swing.runNow(s);
runSwingNow(() -> ref.set(s.get()));
return ref.get();
} }
/** /**
@ -261,80 +250,7 @@ public class SystemUtilities {
* @see #runSwingNow(Supplier) if you need to return a value from the Swing thread. * @see #runSwingNow(Supplier) if you need to return a value from the Swing thread.
*/ */
public static void runSwingNow(Runnable r) { public static void runSwingNow(Runnable r) {
Swing.runNow(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.
*
* <p>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
}
} }
/** /**
@ -344,48 +260,11 @@ public class SystemUtilities {
* @param r the runnable * @param r the runnable
*/ */
public static void runSwingLater(Runnable r) { public static void runSwingLater(Runnable r) {
doRunSwing(r, false, SWING_RUN_ERROR_MSG); Swing.runLater(r);
} }
public static void runIfSwingOrPostSwingLater(Runnable r) { public static void runIfSwingOrPostSwingLater(Runnable r) {
if (isInHeadlessMode()) { Swing.runIfSwingOrRunLater(r);
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);
}
} }
/** /**
@ -543,25 +422,7 @@ public class SystemUtilities {
* @return true if this is the event dispatch thread -OR- is in headless mode. * @return true if this is the event dispatch thread -OR- is in headless mode.
*/ */
public static boolean isEventDispatchThread() { public static boolean isEventDispatchThread() {
if (isInHeadlessMode()) { return Swing.isEventDispatchThread();
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);
} }
/** /**