mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-739,741,742,666,681,823: combine listener interfaces, remove attribute-specific callbacks, update-mode schema, recorder refactor, model testing, and double-launch fix
This commit is contained in:
@@ -18,8 +18,7 @@ package ghidra.async;
|
||||
import java.lang.ref.Cleaner.Cleanable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -168,7 +167,7 @@ public class AsyncReference<T, C> {
|
||||
listener.accept(oldVal, newVal, cause);
|
||||
}
|
||||
catch (RejectedExecutionException exc) {
|
||||
Msg.trace(this, "Ignoring rejection", exc);
|
||||
Msg.trace(this, "Ignoring rejection: " + exc);
|
||||
}
|
||||
catch (Throwable exc) {
|
||||
Msg.error(this, "Ignoring exception on async reference listener: ", exc);
|
||||
@@ -376,7 +375,7 @@ public class AsyncReference<T, C> {
|
||||
}
|
||||
}
|
||||
|
||||
IllegalStateException ex = new IllegalStateException(reason);
|
||||
ExecutionException ex = new ExecutionException("Disposed", reason);
|
||||
for (CompletableFuture<?> future : toExcept) {
|
||||
future.completeExceptionally(ex);
|
||||
}
|
||||
|
||||
@@ -830,7 +830,11 @@ public interface AsyncUtils<T> {
|
||||
}
|
||||
|
||||
public interface TemperamentalRunnable {
|
||||
public void run() throws Exception;
|
||||
public void run() throws Throwable;
|
||||
}
|
||||
|
||||
public interface TemperamentalSupplier<T> {
|
||||
public T get() throws Throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,24 +15,107 @@
|
||||
*/
|
||||
package ghidra.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import ghidra.async.AsyncUtils.TemperamentalRunnable;
|
||||
import ghidra.async.AsyncUtils.TemperamentalSupplier;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
public interface AsyncTestUtils {
|
||||
static final long TIMEOUT_MILLISECONDS =
|
||||
static final long TIMEOUT_MS =
|
||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||
static final long RETRY_INTERVAL_MS = 100;
|
||||
|
||||
default <T> T waitOnNoValidate(CompletableFuture<T> future) {
|
||||
// Do this instead of plain ol' .get(time), to ease debugging
|
||||
// When suspended in .get(time), you can't introspect much, otherwise
|
||||
long started = System.currentTimeMillis();
|
||||
while (true) {
|
||||
try {
|
||||
return future.get(100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
if (Long.compareUnsigned(System.currentTimeMillis() - started, TIMEOUT_MS) >= 0) {
|
||||
throw new RuntimeException(AsyncUtils.unwrapThrowable(e));
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Throwable unwrapped = AsyncUtils.unwrapThrowable(e);
|
||||
if (unwrapped instanceof RuntimeException) {
|
||||
throw (RuntimeException) unwrapped;
|
||||
}
|
||||
return ExceptionUtils.rethrow(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default void validateCompletionThread() {
|
||||
}
|
||||
|
||||
default <T> T waitOn(CompletableFuture<T> future) throws Throwable {
|
||||
try {
|
||||
return future.get(TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw AsyncUtils.unwrapThrowable(e);
|
||||
}
|
||||
/**
|
||||
* NB. CF's may issue dependent callbacks either on the thread completing the dependency, or
|
||||
* on the thread chaining the dependent. If the CF completes before the chain, then the
|
||||
* callback comes to me, and so currentThread will not be the model's callback thread. Thus,
|
||||
* I should not validate the currentThread at callback if it is the currentThread now.
|
||||
*/
|
||||
Thread waitingThread = Thread.currentThread();
|
||||
CompletableFuture<T> validated = future.whenComplete((t, ex) -> {
|
||||
if (Thread.currentThread() != waitingThread) {
|
||||
validateCompletionThread();
|
||||
}
|
||||
});
|
||||
return waitOnNoValidate(validated);
|
||||
}
|
||||
|
||||
default void waitOn(Executor executor) throws Throwable {
|
||||
waitOn(CompletableFuture.supplyAsync(() -> null, executor));
|
||||
}
|
||||
|
||||
default void retryVoid(TemperamentalRunnable runnable,
|
||||
Collection<Class<? extends Throwable>> retriable) throws Throwable {
|
||||
retry(() -> {
|
||||
runnable.run();
|
||||
return null;
|
||||
}, retriable);
|
||||
}
|
||||
|
||||
default <T> T retry(TemperamentalSupplier<T> supplier,
|
||||
Collection<Class<? extends Throwable>> retriable) throws Throwable {
|
||||
return retry(TIMEOUT_MS, supplier, retriable);
|
||||
}
|
||||
|
||||
default <T> T retry(long timeoutMs, TemperamentalSupplier<T> supplier,
|
||||
Collection<Class<? extends Throwable>> retriable) throws Throwable {
|
||||
long retryAttempts = timeoutMs / RETRY_INTERVAL_MS;
|
||||
Throwable lastExc = null;
|
||||
for (int i = 0; i < retryAttempts; i++) {
|
||||
if (i != 0) {
|
||||
Thread.sleep(RETRY_INTERVAL_MS);
|
||||
}
|
||||
try {
|
||||
return supplier.get();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (i < 10) {
|
||||
Msg.debug(this, "Retrying after " + e);
|
||||
}
|
||||
lastExc = e;
|
||||
for (Class<? extends Throwable> et : retriable) {
|
||||
if (et.isAssignableFrom(e.getClass())) {
|
||||
e = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (e != null) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastExc;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user