GT-2875 - Unswingable - Code to allow task launching to give-up on the

Swing thread to prevent deadlocks
This commit is contained in:
dragonmacher 2019-05-16 15:42:24 -04:00
parent 88cf9c0f80
commit 8dffd377fb
10 changed files with 776 additions and 253 deletions

View file

@ -22,12 +22,16 @@ 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;
/**
@ -257,7 +261,80 @@ 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);
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
}
}
/**
@ -267,7 +344,7 @@ public class SystemUtilities {
* @param r the runnable
*/
public static void runSwingLater(Runnable r) {
runSwing(r, false, SWING_RUN_ERROR_MSG);
doRunSwing(r, false, SWING_RUN_ERROR_MSG);
}
public static void runIfSwingOrPostSwingLater(Runnable r) {
@ -284,7 +361,7 @@ public class SystemUtilities {
}
}
private static void runSwing(Runnable r, boolean wait, String errorMessage) {
private static void doRunSwing(Runnable r, boolean wait, String errorMessage) {
if (isInHeadlessMode()) {
r.run();
return;

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