mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GT-2875 - Unswingable - review fixes
This commit is contained in:
parent
8dffd377fb
commit
07f0371a50
16 changed files with 395 additions and 374 deletions
264
Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java
Normal file
264
Ghidra/Framework/Utility/src/main/java/ghidra/util/Swing.java
Normal 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
|
||||
}
|
||||
}
|
|
@ -17,21 +17,15 @@ package ghidra.util;
|
|||
|
||||
import java.awt.Font;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.UnableToSwingException;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
/**
|
||||
|
@ -41,9 +35,6 @@ import utilities.util.reflection.ReflectionUtilities;
|
|||
*/
|
||||
public class SystemUtilities {
|
||||
|
||||
private static final String SWING_RUN_ERROR_MSG =
|
||||
"Unexpected exception running a task in the Swing Thread: ";
|
||||
|
||||
private final static String DATE_TIME_FORMAT = "MMM d yyyy HH:mm:ss";
|
||||
|
||||
private static String userName;
|
||||
|
@ -249,9 +240,7 @@ public class SystemUtilities {
|
|||
* @see #runSwingNow(Runnable)
|
||||
*/
|
||||
public static <T> T runSwingNow(Supplier<T> s) {
|
||||
AtomicReference<T> ref = new AtomicReference<>();
|
||||
runSwingNow(() -> ref.set(s.get()));
|
||||
return ref.get();
|
||||
return Swing.runNow(s);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,80 +250,7 @@ public class SystemUtilities {
|
|||
* @see #runSwingNow(Supplier) if you need to return a value from the Swing thread.
|
||||
*/
|
||||
public static void runSwingNow(Runnable r) {
|
||||
|
||||
try {
|
||||
// not sure what a reasonable wait is for a background thread; we can make this larger
|
||||
// if we find that a really slow system UI causes this to fail
|
||||
int maxWait = 10;
|
||||
runSwingNow(r, maxWait, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (UnableToSwingException e) {
|
||||
throw new RuntimeException("Timed-out waiting to run a Swing task--potential deadlock!",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given runnable on the Swing thread.
|
||||
*
|
||||
* <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
|
||||
}
|
||||
Swing.runNow(r);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -344,48 +260,11 @@ public class SystemUtilities {
|
|||
* @param r the runnable
|
||||
*/
|
||||
public static void runSwingLater(Runnable r) {
|
||||
doRunSwing(r, false, SWING_RUN_ERROR_MSG);
|
||||
Swing.runLater(r);
|
||||
}
|
||||
|
||||
public static void runIfSwingOrPostSwingLater(Runnable r) {
|
||||
if (isInHeadlessMode()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
r.run();
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(r);
|
||||
}
|
||||
}
|
||||
|
||||
private static void doRunSwing(Runnable r, boolean wait, String errorMessage) {
|
||||
if (isInHeadlessMode()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(r);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// we sometimes interrupt our tasks intentionally, so don't report it
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
Msg.error(SystemUtilities.class,
|
||||
errorMessage + "\nException Message: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(r);
|
||||
}
|
||||
Swing.runIfSwingOrRunLater(r);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -543,25 +422,7 @@ public class SystemUtilities {
|
|||
* @return true if this is the event dispatch thread -OR- is in headless mode.
|
||||
*/
|
||||
public static boolean isEventDispatchThread() {
|
||||
if (isInHeadlessMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: just calling this method may trigger the AWT thread to get created
|
||||
return SwingUtilities.isEventDispatchThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until AWT event queue (Swing) has been flushed and no more (to a point) events
|
||||
* are pending.
|
||||
*/
|
||||
public static void allowSwingToProcessEvents() {
|
||||
Runnable r = () -> {
|
||||
// do nothing...this is just a placeholder runnable that gets put onto the stack
|
||||
};
|
||||
runSwingNow(r);
|
||||
runSwingNow(r);
|
||||
runSwingNow(r);
|
||||
return Swing.isEventDispatchThread();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue