Merge remote-tracking branch 'origin/GT-2875-dragonmacher-task-launcher-deadlock'

This commit is contained in:
ghidravore 2019-05-21 16:42:32 -04:00
commit 1340268ffa
49 changed files with 1145 additions and 542 deletions

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -103,8 +103,12 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
return;
}
new TaskLauncher(new SyncTask(synchronizer), null, TaskLauncher.INITIAL_MODAL_DELAY,
SwingConstants.LEFT);
//@formatter:off
TaskBuilder.withTask(new SyncTask(synchronizer))
.setStatusTextAlignment(SwingConstants.LEADING)
.launchModal()
;
//@formatter:on
}
private void doSync(DataTypeSynchronizer synchronizer, TaskMonitor monitor) {

View File

@ -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 {

View File

@ -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.

View File

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

View File

@ -21,6 +21,13 @@
package ghidra.app.plugin.core.module;
import java.util.*;
import javax.swing.SwingConstants;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
@ -36,14 +43,6 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.*;
import java.util.*;
import javax.swing.SwingConstants;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
/**
* Plugin to sort Modules and Fragments within a selected Module.
* Child Module folders are always name-sorted and placed
@ -97,10 +96,12 @@ public class ModuleSortPlugin extends ProgramPlugin {
return;
}
SortTask sortTask = new SortTask(module, sortType);
// this blocks until the task is finished
new TaskLauncher(sortTask, null, TaskLauncher.INITIAL_MODAL_DELAY, SwingConstants.LEADING);
//@formatter:off
TaskBuilder.withTask(new SortTask(module, sortType))
.setStatusTextAlignment(SwingConstants.LEADING)
.launchModal()
;
//@formatter:on
}
private void doSort(ProgramModule parent, GroupComparator comparator, TaskMonitor monitor)
@ -110,11 +111,11 @@ public class ModuleSortPlugin extends ProgramPlugin {
monitor.initialize(kids.length);
for (int i = 0; i < kids.length; i++) {
for (Group kid : kids) {
monitor.checkCanceled();
list.add(kids[i]);
if (kids[i] instanceof ProgramModule) {
doSort((ProgramModule) kids[i], comparator, monitor);
list.add(kid);
if (kid instanceof ProgramModule) {
doSort((ProgramModule) kid, comparator, monitor);
}
monitor.incrementProgress(1);
}
@ -152,8 +153,9 @@ public class ModuleSortPlugin extends ProgramPlugin {
private ProgramModule getSelectedModule(Object contextObj) {
if (contextObj instanceof ProgramNode) {
ProgramNode node = (ProgramNode) contextObj;
if (node.isModule() && node.getTree().getSelectionCount() == 1)
if (node.isModule() && node.getTree().getSelectionCount() == 1) {
return node.getModule();
}
}
return null;
}
@ -244,12 +246,14 @@ public class ModuleSortPlugin extends ProgramPlugin {
this.sortType = sortType;
if (sortType == SORT_BY_ADDRESS) {
setPopupMenuData(new MenuData(SORT_BY_ADDR_MENUPATH, null, "module"));
setDescription("Perform a minimum address sort of all fragments contained within a selected folder");
setDescription(
"Perform a minimum address sort of all fragments contained within a selected folder");
}
else {
setPopupMenuData(new MenuData(SORT_BY_NAME_MENUPATH, null, "module"));
setDescription("Perform a name sort of all fragments contained within a selected folder");
setDescription(
"Perform a name sort of all fragments contained within a selected folder");
}
setEnabled(true); // always enabled
setHelpLocation(new HelpLocation("ProgramTreePlugin", "SortByAddressOrName"));
@ -258,7 +262,7 @@ public class ModuleSortPlugin extends ProgramPlugin {
/**
* Determine if the Module Sort action should be visible within
* the popup menu for the specified active object.
* @param activeObj the object under the mouse location for the popup.
* @param context the context
* @return true if action should be made visible in popup menu.
*/
@Override

View File

@ -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

View File

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

View File

@ -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.*;
@ -47,6 +46,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 {

View File

@ -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.

View File

@ -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 {

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -221,7 +221,8 @@ public abstract class DataLoadingConstraintEditor<T> 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

View File

@ -0,0 +1,53 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.task;
import java.util.concurrent.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.
*
* <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}.
*/
public class BackgroundThreadTaskLauncher {
private Task task;
public BackgroundThreadTaskLauncher(Task task) {
this.task = task;
}
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();
GThreadPool pool = GThreadPool.getSharedThreadPool(Swing.GSWING_THREAD_POOL_NAME);
Executor executor = pool.getExecutor();
executor.execute(() -> {
Thread.currentThread().setName(name);
task.monitoredRun(monitor);
});
}
}

View File

@ -54,6 +54,19 @@ import util.CollectionUtils;
* .launchModal();
* </pre>
*
* Or,
*
* <pre>
* TaskBuilder.withTask(new AwesomeTask(awesomeStuff)).launchModal();
* </pre>
*
* Or,
*
* <pre>
* {@link TaskLauncher#launch(Task) TaskLauncher.launch}(new AwesomeTask(awesomeStuff));
* </pre>
*
*
* <p>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()}.
*
* <p>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);
}
@ -148,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
@ -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 {

View File

@ -16,24 +16,30 @@
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.Swing;
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 <b>if the task takes too long</b>. The progress dialog will show an
* animation in the event that the task of this class cannot show progress.
*
* <p>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.
*
* <p><b>Important Usage Note:</b><br>
* 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 <b>give up on using the Swing thread and will not
* create a background thread</b>. Instead, the client code will be run in the client thread.
*
* <a name="modal_usage"></a>
* <p><a name="modal_usage"></a>Most clients of this class should not be concerned with where
* <p><b><a name="modal_usage">Modal Usage</a></b><br>
* 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,
@ -93,7 +99,7 @@ public class TaskLauncher {
public void run(TaskMonitor monitor) {
runnable.monitoredRun(monitor);
}
}, null, INITIAL_DELAY);
}, null, INITIAL_DELAY_MS);
}
/**
@ -122,7 +128,7 @@ public class TaskLauncher {
public void run(TaskMonitor monitor) {
runnable.monitoredRun(monitor);
}
}, null, INITIAL_MODAL_DELAY);
}, null, INITIAL_MODAL_DELAY_MS);
}
/**
@ -163,33 +169,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;
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;
}
return (parent.isVisible() ? parent : null);
}
static final int INITIAL_MODAL_DELAY_MS = 500;
/**
* Constructor for TaskLauncher.
* Constructor for TaskLauncher
*
* <p>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,
@ -200,11 +186,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
*
* <p>See <a href="#modal_usage">notes on modal usage</a>
*
@ -212,121 +198,96 @@ 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
*
* <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 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
*
* <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 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) {
this.task = task;
this.taskDialog = buildTaskDialog(parent, dialogWidth);
startBackgroundThread(taskDialog);
taskDialog.show(Math.max(delay, 0));
waitForModalIfNotSwing();
try {
scheduleFromSwingThread(task, parent, delayMs, dialogWidth);
}
catch (UnableToSwingException e) {
runInThisBackgroundThread(task);
}
}
private void waitForModalIfNotSwing() {
if (SwingUtilities.isEventDispatchThread() || !task.isModal()) {
private void scheduleFromSwingThread(Task task, Component parent, int delayMs, int dialogWidth)
throws UnableToSwingException {
TaskRunner runner = createTaskRunner(task, parent, delayMs, dialogWidth);
if (Swing.isEventDispatchThread()) {
runner.run();
return;
}
try {
taskThread.join();
}
catch (InterruptedException e) {
Msg.debug(this, "Task Launcher unexpectedly interrupted waiting for task thread", e);
}
//
// Not on the Swing thread. Try to execute on the Swing thread, timing-out if it takes
// too long (this prevents deadlocks).
//
// This will throw an exception if we could not get the Swing lock. When that happens,
// the task was NOT run.
int timeout = getSwingTimeoutInSeconds();
Swing.runNow(() -> runner.run(), timeout, TimeUnit.SECONDS);
}
// template method to allow timeout change; used by tests
protected int getSwingTimeoutInSeconds() {
return 2;
}
// 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);
}
/**
* 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 taskMonitor the monitor to use while running the task.
* 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
*/
public TaskLauncher(Task task, TaskMonitor taskMonitor) {
this.task = task;
startBackgroundThread(taskMonitor);
}
private TaskDialog buildTaskDialog(Component comp, int dialogWidth) {
taskDialog = createTaskDialog(comp);
taskDialog.setMinimumSize(dialogWidth, 0);
if (task.isInterruptible() || task.isForgettable()) {
taskDialog.addCancelledListener(monitorChangeListener);
protected void runInThisBackgroundThread(Task task) {
if (Swing.isEventDispatchThread()) {
throw new IllegalStateException("Must not call this method from the Swing thread");
}
taskDialog.setStatusJustification(task.getStatusTextAlignment());
return taskDialog;
task.monitoredRun(TaskMonitor.DUMMY);
}
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 parent = comp;
if (parent != null) {
parent = WindowUtilities.windowForComponent(comp);
}
private static Component getParent(Component parent) {
if (parent == null) {
return new TaskDialog(task);
return null;
}
return new TaskDialog(comp, task);
return (parent.isVisible() ? parent : null);
}
}

View File

@ -0,0 +1,112 @@
/* ###
* 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 java.util.concurrent.Executor;
import generic.concurrent.GThreadPool;
import generic.util.WindowUtilities;
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 TaskRunner {
protected Task task;
private Component parent;
private int delayMs;
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
}
};
TaskRunner(Task task, Component parent, int delayMs, int dialogWidth) {
this.task = task;
this.parent = parent;
this.delayMs = delayMs;
this.dialogWidth = dialogWidth;
}
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(delayMs, 0));
}
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).
//
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();
GThreadPool pool = GThreadPool.getSharedThreadPool(Swing.GSWING_THREAD_POOL_NAME);
Executor executor = pool.getExecutor();
executor.execute(() -> {
Thread.currentThread().setName(name);
task.monitoredRun(monitor);
taskDialog.taskProcessed();
});
}
private 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);
}
}

View File

@ -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 {

View File

@ -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<TDEvent> eventQueue = new LinkedBlockingDeque<>();
protected volatile TaskLauncherSpy taskLauncherSpy;
protected volatile TaskDialogSpy dialogSpy;
protected AtomicBoolean didRunInBackground = new AtomicBoolean();
protected void assertDidNotRunInSwing() {
for (TDEvent e : eventQueue) {
assertFalse(e.getThreadName().contains("AWT"));
}
}
protected void assertRanInSwingThread() {
assertFalse("Task was not run in the Swing thread", didRunInBackground.get());
}
protected void assertSwingThreadBlockedForTask() {
TDEvent lastEvent = eventQueue.peekLast();
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
if (!swingIsLast) {
fail("The Swing thread did not block until the task finished");
}
}
protected void assertSwingThreadFinishedBeforeTask() {
TDEvent lastEvent = eventQueue.peekLast();
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
if (swingIsLast) {
fail("The Swing thread blocked until the task finished");
}
}
protected void waitForTask() throws Exception {
threadsFinished.await(2, TimeUnit.SECONDS);
}
protected void launchTask(Task task) {
launchTaskFromSwing(task);
}
protected void launchTaskFromSwing(Task task) {
runSwing(() -> {
taskLauncherSpy = new TaskLauncherSpy(task);
postEvent("After task launcher");
threadsFinished.countDown();
});
}
protected void postEvent(String message) {
eventQueue.add(new TDEvent(message));
}
protected class TaskLauncherSpy extends TaskLauncher {
public TaskLauncherSpy(Task task) {
super(task, null, DELAY_LAUNCHER);
}
@Override
protected TaskRunner createTaskRunner(Task task, Component parent, int delay,
int dialogWidth) {
return new TaskRunner(task, parent, delay, dialogWidth) {
@Override
protected TaskDialog buildTaskDialog(Component comp) {
dialogSpy = new TaskDialogSpy(task);
return dialogSpy;
}
};
}
@Override
protected int getSwingTimeoutInSeconds() {
return 1; // speed-up for tests
}
@Override
protected void runInThisBackgroundThread(Task task) {
didRunInBackground.set(true);
super.runInThisBackgroundThread(task);
}
TaskDialogSpy getDialogSpy() {
return dialogSpy;
}
boolean didRunInBackground() {
return didRunInBackground.get();
}
}
protected class FastModalTask extends Task {
public FastModalTask() {
super("Fast Modal Task", true, true, true);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(getName() + " started...");
sleep(DELAY_FAST);
threadsFinished.countDown();
postEvent(getName() + " finished.");
}
}
protected class FastNonModalTask extends Task {
public FastNonModalTask() {
super("Fast Non-modal Task", true, true, false);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(getName() + " started...");
sleep(DELAY_FAST);
postEvent(getName() + " finished.");
threadsFinished.countDown();
}
}
protected class SlowModalTask extends Task {
public SlowModalTask() {
super("Slow Modal Task", true, true, true);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(getName() + " started...");
sleep(DELAY_SLOW);
threadsFinished.countDown();
postEvent(getName() + " finished.");
}
}
protected class SlowNonModalTask extends Task {
public SlowNonModalTask() {
super("Slow Non-modal Task", true, true, false);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(getName() + " started...");
sleep(DELAY_SLOW);
threadsFinished.countDown();
postEvent(getName() + " finished.");
}
}
protected class TDEvent {
protected String threadName = Thread.currentThread().getName();
protected String message;
TDEvent(String message) {
this.message = message;
// Msg.out(message + " from " + threadName);
}
String getThreadName() {
return threadName;
}
@Override
public String toString() {
return message + " - thread [" + threadName + ']';
}
}
}

View File

@ -0,0 +1,36 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.task;
import java.util.concurrent.atomic.AtomicBoolean;
public class TaskDialogSpy extends TaskDialog {
private AtomicBoolean shown = new AtomicBoolean();
public TaskDialogSpy(Task task) {
super(task);
}
@Override
protected void doShow() {
shown.set(true);
super.doShow();
}
boolean wasShown() {
return shown.get();
}
}

View File

@ -17,26 +17,10 @@ package ghidra.util.task;
import static org.junit.Assert.*;
import java.awt.Component;
import java.util.Deque;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Test;
import docking.test.AbstractDockingTest;
public class TaskDialogTest extends AbstractDockingTest {
private static final int DELAY_FAST = 10;
private static final int DELAY_SLOW = 100;
private static final int DELAY_LAUNCHER = DELAY_FAST * 2;
private CountDownLatch threadsFinished = new CountDownLatch(2);
private Deque<TDEvent> eventQueue = new LinkedBlockingDeque<>();
public class TaskDialogTest extends AbstractTaskTest {
@After
public void tearDown() {
@ -48,7 +32,7 @@ public class TaskDialogTest extends AbstractDockingTest {
FastModalTask task = new FastModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
launchTask(task);
waitForTask();
@ -60,7 +44,7 @@ public class TaskDialogTest extends AbstractDockingTest {
public void testModalDialog_SlowTask_Dialog() throws Exception {
SlowModalTask task = new SlowModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
launchTask(task);
waitForTask();
@ -73,7 +57,7 @@ public class TaskDialogTest extends AbstractDockingTest {
FastNonModalTask task = new FastNonModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
launchTask(task);
waitForTask();
@ -86,7 +70,7 @@ public class TaskDialogTest extends AbstractDockingTest {
SlowNonModalTask task = new SlowNonModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
launchTask(task);
waitForTask();
@ -100,7 +84,7 @@ public class TaskDialogTest extends AbstractDockingTest {
@Test
public void testTaskCancel() throws Exception {
SlowModalTask task = new SlowModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
launchTask(task);
dialogSpy.doShow();
@ -118,7 +102,7 @@ public class TaskDialogTest extends AbstractDockingTest {
@Test
public void testTaskNoCancel() throws Exception {
SlowModalTask task = new SlowModalTask();
TaskDialogSpy dialogSpy = launchTask(task);
launchTask(task);
dialogSpy.doShow();
dialogSpy.setCancelEnabled(false);
@ -128,159 +112,4 @@ public class TaskDialogTest extends AbstractDockingTest {
assertFalse(dialogSpy.isCancelEnabled());
}
private void assertSwingThreadBlockedForTask() {
TDEvent lastEvent = eventQueue.peekLast();
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
if (!swingIsLast) {
fail("The Swing thread did not block until the task finished");
}
}
private void assertSwingThreadFinishedBeforeTask() {
TDEvent lastEvent = eventQueue.peekLast();
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
if (swingIsLast) {
fail("The Swing thread blocked until the task finished");
}
}
private void waitForTask() throws Exception {
threadsFinished.await(2, TimeUnit.SECONDS);
}
private TaskDialogSpy launchTask(Task task) {
AtomicReference<TaskDialogSpy> ref = new AtomicReference<>();
runSwing(() -> {
TaskLauncherSpy launcherSpy = new TaskLauncherSpy(task);
postEvent("After task launcher");
TaskDialogSpy dialogSpy = launcherSpy.getDialogSpy();
ref.set(dialogSpy);
threadsFinished.countDown();
});
return ref.get();
}
private void postEvent(String message) {
eventQueue.add(new TDEvent(message));
}
private class TaskLauncherSpy extends TaskLauncher {
private TaskDialogSpy dialogSpy;
public TaskLauncherSpy(Task task) {
super(task, null, DELAY_LAUNCHER);
}
@Override
protected TaskDialog createTaskDialog(Component comp) {
dialogSpy = new TaskDialogSpy(task);
return dialogSpy;
}
TaskDialogSpy getDialogSpy() {
return dialogSpy;
}
}
private class TaskDialogSpy extends TaskDialog {
private AtomicBoolean shown = new AtomicBoolean();
public TaskDialogSpy(Task task) {
super(task);
}
@Override
protected void doShow() {
shown.set(true);
super.doShow();
}
boolean wasShown() {
return shown.get();
}
}
private class FastModalTask extends Task {
public FastModalTask() {
super("Fast Modal Task", true, true, true);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(" started...");
sleep(DELAY_FAST);
threadsFinished.countDown();
postEvent(" finished.");
}
}
private class FastNonModalTask extends Task {
public FastNonModalTask() {
super("Fast Non-modal Task", true, true, false);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(" started...");
sleep(DELAY_FAST);
postEvent(" finished.");
threadsFinished.countDown();
}
}
private class SlowModalTask extends Task {
public SlowModalTask() {
super("Slow Modal Task", true, true, true);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(" started...");
sleep(DELAY_SLOW);
threadsFinished.countDown();
postEvent(" finished.");
}
}
private class SlowNonModalTask extends Task {
public SlowNonModalTask() {
super("Slow Non-modal Task", true, true, false);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(" started...");
sleep(DELAY_SLOW);
threadsFinished.countDown();
postEvent(" finished.");
}
}
private class TDEvent {
private String threadName = Thread.currentThread().getName();
private String message;
TDEvent(String message) {
this.message = message;
// Msg.out(message + " from " + threadName);
}
String getThreadName() {
return threadName;
}
@Override
public String toString() {
return message + " - thread [" + threadName + ']';
}
}
}

View File

@ -0,0 +1,177 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.task;
import static org.junit.Assert.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.*;
public class TaskLauncherTest extends AbstractTaskTest {
private Thread swingThread;
@Before
public void setUp() {
runSwing(() -> swingThread = Thread.currentThread());
}
@After
public void tearDown() {
// release any blockers
swingThread.interrupt();
}
@Test
public void testLaunchFromSwing() throws Exception {
FastModalTask task = new FastModalTask();
launchTaskFromSwing(task);
waitForTask();
assertSwingThreadBlockedForTask();
}
@Test
public void testLaunchFromBackground() throws Exception {
FastModalTask task = new FastModalTask();
launchTaskFromBackground(task);
waitForTask();
assertRanInSwingThread();
}
@Test
public void testLaunchFromBackgroundWithBusySwing() throws Exception {
SwingBlocker blocker = new SwingBlocker();
runSwing(blocker, false);
blocker.waitForStart();
FastModalTask task = new FastModalTask();
launchTaskFromBackground(task);
waitForTask();
assertDidNotRunInSwing();
}
@Test
public void testLaunchFromInsideOfAnotherTaskThread() throws Exception {
SwingBlocker blocker = new SwingBlocker();
runSwing(blocker, false);
blocker.waitForStart();
// 4 - 2 per task
threadsFinished = new CountDownLatch(4);
launchTaskFromTask();
waitForTask();
assertDidNotRunInSwing();
}
@Test
public void testLaunchFromInsideOfAnotherTaskThreadWithBusySwingThread() throws Exception {
// 4 - 2 per task
threadsFinished = new CountDownLatch(4);
launchTaskFromTask();
waitForTask();
assertDidNotRunInSwing();
}
private int getWaitTimeoutInSeconds() {
return (int) TimeUnit.SECONDS.convert(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS) * 2;
}
protected void launchTaskFromBackground(Task task) throws InterruptedException {
CountDownLatch start = new CountDownLatch(1);
new Thread("Test Task Launcher Background Client") {
@Override
public void run() {
taskLauncherSpy = new TaskLauncherSpy(task);
start.countDown();
postEvent("After task launcher");
threadsFinished.countDown();
}
}.start();
assertTrue("Background thread did not start in " + getWaitTimeoutInSeconds() + " seconds",
start.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS));
}
protected void launchTaskFromTask() throws InterruptedException {
TaskLaunchingTask task = new TaskLaunchingTask();
CountDownLatch start = new CountDownLatch(1);
new Thread("Nested Test Task Launcher Background Client") {
@Override
public void run() {
taskLauncherSpy = new TaskLauncherSpy(task);
start.countDown();
postEvent("After task launcher");
threadsFinished.countDown();
}
}.start();
assertTrue("Background thread did not start in " + getWaitTimeoutInSeconds() + " seconds",
start.await(getWaitTimeoutInSeconds(), TimeUnit.SECONDS));
}
private class TaskLaunchingTask extends Task {
public TaskLaunchingTask() {
super("Slow Modal Task", true, true, true);
}
@Override
public void run(TaskMonitor monitor) {
postEvent(getName() + " started...");
sleep(DELAY_FAST);
try {
launchTaskFromBackground(new FastModalTask());
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
threadsFinished.countDown();
postEvent(getName() + " finished.");
}
}
private class SwingBlocker implements Runnable {
private static final long REALLY_LONG_SLEEP_THAT_DOESNT_FINISH_MS = 20000;
private CountDownLatch started = new CountDownLatch(1);
@Override
public void run() {
started.countDown();
sleep(REALLY_LONG_SLEEP_THAT_DOESNT_FINISH_MS);
}
void waitForStart() throws InterruptedException {
assertTrue("Swing blocker did not start",
started.await(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS));
}
}
}

View File

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

View File

@ -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) {

View File

@ -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

View File

@ -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<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
* operations for GUI clients in a background thread
@ -42,7 +41,7 @@ public class Worker extends AbstractWorker<Job> {
* @return the new worker
*/
public static Worker createGuiWorker() {
return new Worker(GSWING_THREAD_POOL_NAME);
return new Worker(Swing.GSWING_THREAD_POOL_NAME);
}
/**

View File

@ -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

View File

@ -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

View File

@ -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 <b>while

View File

@ -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

View File

@ -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

View File

@ -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++) {

View File

@ -0,0 +1,279 @@
/* ###
* 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.*;
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)} if not on the Swing thread.
*
* <p>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.
*
* <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()) {
doRun(r, true, SWING_RUN_ERROR_MSG);
return;
}
/*
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);
runLater(() -> {
if (!waitFor(start)) {
return; // must have timed-out
}
try {
r.run();
}
finally {
waitFor(end);
}
});
if (!waitFor(start, timeout, unit)) {
throw new UnableToSwingException(
"Timed-out waiting for Swing thread lock in " + timeout + " " + unit);
}
waitFor(end);
}
/**
* 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) {
doRun(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 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 doRun(Runnable r, boolean wait, String errorMessage) {
if (isInHeadlessMode()) {
r.run();
return;
}
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);
}
}
private Swing() {
// utility class
}
}

View File

@ -17,12 +17,10 @@ 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.atomic.AtomicReference;
import java.util.function.Supplier;
import javax.swing.SwingUtilities;
@ -37,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;
@ -245,9 +240,7 @@ public class SystemUtilities {
* @see #runSwingNow(Runnable)
*/
public static <T> T runSwingNow(Supplier<T> s) {
AtomicReference<T> ref = new AtomicReference<>();
runSwingNow(() -> ref.set(s.get()));
return ref.get();
return Swing.runNow(s);
}
/**
@ -257,7 +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) {
runSwing(r, true, SWING_RUN_ERROR_MSG);
Swing.runNow(r);
}
/**
@ -267,48 +260,11 @@ public class SystemUtilities {
* @param r the runnable
*/
public static void runSwingLater(Runnable r) {
runSwing(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 runSwing(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);
}
/**
@ -466,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();
}
/**

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,