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

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