GP-2040: Remove garbage from AsyncUtils.

This commit is contained in:
Dan 2025-03-04 17:09:06 +00:00
parent 3d7089d391
commit 922c4d0186
40 changed files with 271 additions and 5277 deletions

View file

@ -21,6 +21,7 @@ import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.Icon; import javax.swing.Icon;
@ -32,7 +33,8 @@ import ghidra.app.plugin.core.debug.gui.tracermi.RemoteMethodInvocationDialog;
import ghidra.app.plugin.core.debug.service.target.AbstractTarget; import ghidra.app.plugin.core.debug.service.target.AbstractTarget;
import ghidra.app.services.DebuggerConsoleService; import ghidra.app.services.DebuggerConsoleService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.async.*; import ghidra.async.AsyncFence;
import ghidra.async.AsyncUtils;
import ghidra.debug.api.ValStr; import ghidra.debug.api.ValStr;
import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext; import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
@ -1086,9 +1088,11 @@ public class TraceRmiTarget extends AbstractTarget {
@Override @Override
public CompletableFuture<Void> readMemoryAsync(AddressSetView set, TaskMonitor monitor) { public CompletableFuture<Void> readMemoryAsync(AddressSetView set, TaskMonitor monitor) {
// I still separate into blocks, because I want user to be able to cancel /**
// NOTE: I don't intend to warn about the number of requests. * I still separate into blocks, because I want user to be able to cancel. I don't intend to
// They're delivered in serial, and there's a cancel button that works * warn about the number of requests. They're delivered in serial, and there's a cancel
* button that works
*/
MatchedMethod readMem = MatchedMethod readMem =
matches.getBest(ReadMemMatcher.class, null, ActionName.READ_MEM, ReadMemMatcher.ALL); matches.getBest(ReadMemMatcher.class, null, ActionName.READ_MEM, ReadMemMatcher.ALL);
@ -1113,40 +1117,50 @@ public class TraceRmiTarget extends AbstractTarget {
} }
monitor.initialize(total); monitor.initialize(total);
monitor.setMessage("Reading memory"); monitor.setMessage("Reading memory");
// NOTE: Don't read in parallel, lest we overload the connection
return AsyncUtils.each(TypeSpec.VOID, quantized.iterator(), (r, loop) -> { /**
AddressRangeChunker blocks = new AddressRangeChunker(r, BLOCK_SIZE); * NOTE: Don't read in parallel, lest we overload the connection. This does queue them all
* up in a CF chain, though. Still, a request doesn't go out until the preceding one
* completes.
*/
return quantized.stream().flatMap(r -> {
if (r.getAddressSpace().isRegisterSpace()) { if (r.getAddressSpace().isRegisterSpace()) {
Msg.warn(this, "Request to read registers via readMemory: " + r + ". Ignoring."); Msg.warn(this, "Request to read registers via readMemory: " + r + ". Ignoring.");
loop.repeatWhile(!monitor.isCancelled()); return Stream.of();
return;
} }
AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (blk, inner) -> { return new AddressRangeChunker(r, BLOCK_SIZE).stream();
}).reduce(AsyncUtils.nil(), (f, blk) -> {
if (monitor.isCancelled()) {
return f;
}
final Map<String, Object> args;
if (paramProcess != null) {
TraceObject process = procsBySpace.computeIfAbsent(blk.getAddressSpace(),
this::getProcessForSpace);
if (process == null) {
return f;
}
args = Map.ofEntries(
Map.entry(paramProcess.name(), process),
Map.entry(paramRange.name(), blk));
}
else {
args = Map.ofEntries(
Map.entry(paramRange.name(), blk));
}
return f.thenComposeAsync(__ -> {
if (monitor.isCancelled()) {
return AsyncUtils.nil();
}
monitor.incrementProgress(1); monitor.incrementProgress(1);
final Map<String, Object> args; return requestCaches.readBlock(blk.getMinAddress(), readMem.method, args);
if (paramProcess != null) { }, AsyncUtils.FRAMEWORK_EXECUTOR).exceptionally(e -> {
TraceObject process = procsBySpace.computeIfAbsent(blk.getAddressSpace(), Msg.error(this, "Could not read " + blk + ": " + e);
this::getProcessForSpace); return null; // Continue looping on errors
if (process == null) { });
Msg.warn(this, "Cannot find process containing " + blk.getMinAddress()); }, (f1, f2) -> {
inner.repeatWhile(!monitor.isCancelled()); throw new AssertionError("Should be sequential");
return;
}
args = Map.ofEntries(
Map.entry(paramProcess.name(), process),
Map.entry(paramRange.name(), blk));
}
else {
args = Map.ofEntries(
Map.entry(paramRange.name(), blk));
}
CompletableFuture<Void> future =
requestCaches.readBlock(blk.getMinAddress(), readMem.method, args);
future.exceptionally(e -> {
Msg.error(this, "Could not read " + blk + ": " + e);
return null; // Continue looping on errors
}).thenApply(__ -> !monitor.isCancelled()).handle(inner::repeatWhile);
}).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile);
}); });
} }

View file

@ -1,109 +0,0 @@
/* ###
* 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.async;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
/**
* A queue of claims on an associated event
*
* Often a single event handler processes all events of a given type or from a given source. This
* class offers a means of "claiming" an event, so that it can be processed by a specific handler.
* The general handler must call the {@link #satisfy(Object)} method, foregoing its usual handling
* if it returns true.
*
* This permits other threads to claim the next (unclaimed) event from the source. This is
* particularly useful when the claimant knows it will also be the cause of the event, allowing it
* to override the usual event processing with its own. Care must be taken to claim the event before
* performing the action that will cause the event, otherwise a race condition arises wherein the
* caused event may occur before the causer has claimed it.
*
* @param <T> the type of the event
*/
public class AsyncClaimQueue<T> {
private static class Entry<T> {
final CompletableFuture<T> future;
final Predicate<T> predicate;
Entry(CompletableFuture<T> future, Predicate<T> predicate) {
this.future = future;
this.predicate = predicate;
}
}
private final Deque<Entry<T>> queue = new LinkedList<>();
/**
* Claim the next unclaimed occurrence of the event.
*
* @return a future which completes with the claimed occurrence
*/
public CompletableFuture<T> claim() {
return claim(t -> true);
}
/**
* Claim, upon a predicate, the next unclaimed occurrence of the event.
*
* If the occurrence does not satisfy the predicate, the next claim in the queue is tried, if
* present.
*
* NOTE: The predicate should be fast and non-blocking, since it is executed while holding the
* lock to the internal queue.
*
* @param predicate a condition upon which to claim the occurrence
* @return a future which completes with the claimed occurrence
*/
public CompletableFuture<T> claim(Predicate<T> predicate) {
synchronized (queue) {
CompletableFuture<T> future = new CompletableFuture<T>();
queue.add(new Entry<>(future, predicate));
return future;
}
}
/**
* Notify a claimant, if any, of the occurrence of the event.
*
* This method should be called by the usual handler for every occurrence the event of interest.
* If the occurrence is claimed, the claimant is notified, and true is returned. If the
* occurrence is free, false is returned, and the handler should proceed as usual.
*
* @param t the event occurrence
* @return true if claimed, false otherwise
*/
public boolean satisfy(T t) {
Entry<T> entry = null;
synchronized (queue) {
Iterator<Entry<T>> eit = queue.iterator();
while (eit.hasNext()) {
Entry<T> e = eit.next();
if (e.predicate.test(t)) {
entry = e;
eit.remove();
break;
}
}
}
if (entry == null) {
return false;
}
entry.future.complete(t);
return true;
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -59,6 +59,7 @@ public class AsyncFence {
* Calling this method after {@link #ready()} will yield undefined results. * Calling this method after {@link #ready()} will yield undefined results.
* *
* @param future the participant to add * @param future the participant to add
* @return this fence
*/ */
public synchronized AsyncFence include(CompletableFuture<?> future) { public synchronized AsyncFence include(CompletableFuture<?> future) {
if (ready != null) { if (ready != null) {
@ -84,11 +85,9 @@ public class AsyncFence {
} }
/** /**
* TODO * Diagnostic: Get the participants which have not yet completed
* *
* Diagnostic * @return the pending participants
*
* @return
*/ */
public Set<CompletableFuture<?>> getPending() { public Set<CompletableFuture<?>> getPending() {
return participants.stream().filter(f -> !f.isDone()).collect(Collectors.toSet()); return participants.stream().filter(f -> !f.isDone()).collect(Collectors.toSet());

View file

@ -1,67 +0,0 @@
/* ###
* 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.async;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
/**
* Methods for exiting a chain common to all handlers
*
* @param <R> the type of result for the whole construct
*/
public interface AsyncHandlerCanExit<R> {
/**
* Complete the whole loop
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)}. While it can be invoked directly, consider the
* convenience methods {@link #exit(Object)} and {@link #exit(Throwable)} instead.
*
* When the subordinate completes, the whole construct completes and terminates with the same,
* possibly exceptional, result.
*
* @param result the result of completion
* @param exc the exception if completed exceptionally
* @return
*/
public Void exit(R result, Throwable exc);
/**
* Complete the chain with the given result
*
* @param result the result
*/
public default void exit(R result) {
exit(result, null);
}
/**
* Complete the chain with {@code null}
*/
public default void exit() {
exit(null, null);
}
/**
* Complete the chain exceptionally
*
* @param exc the exception
*/
public default void exit(Throwable exc) {
exit(null, exc);
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -171,7 +171,7 @@ public class AsyncLazyMap<K, V> {
* <p> * <p>
* This will replace the behavior of any previous value-testing predicate. * This will replace the behavior of any previous value-testing predicate.
* *
* @param predicate * @param predicate the rule for forgetting entries
* @return this lazy map * @return this lazy map
*/ */
public synchronized AsyncLazyMap<K, V> forgetValues( public synchronized AsyncLazyMap<K, V> forgetValues(
@ -184,7 +184,7 @@ public class AsyncLazyMap<K, V> {
* Sets a predicate to determine which values to remember * Sets a predicate to determine which values to remember
* *
* @see #forgetValues(BiPredicate) * @see #forgetValues(BiPredicate)
* @param predicate * @param predicate the rule for <em>not</em> forgetting entries
* @return this lazy map * @return this lazy map
*/ */
public synchronized AsyncLazyMap<K, V> rememberValues( public synchronized AsyncLazyMap<K, V> rememberValues(
@ -322,6 +322,7 @@ public class AsyncLazyMap<K, V> {
* Remove a key from the map, canceling any pending computation * Remove a key from the map, canceling any pending computation
* *
* @param key the key to remove * @param key the key to remove
* @return the previous value, if completed
*/ */
public V remove(K key) { public V remove(K key) {
KeyedFuture<K, V> f; KeyedFuture<K, V> f;

View file

@ -1,335 +0,0 @@
/* ###
* 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.async;
import static ghidra.async.AsyncUtils.sequence;
import java.lang.ref.Cleaner.Cleanable;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import ghidra.async.seq.AsyncSequenceWithTemp;
import ghidra.async.seq.AsyncSequenceWithoutTemp;
import ghidra.util.Msg;
/**
* An optionally-reentrant lock for managing a contentious resource
*
* <p>
* Typically, a resource has a queue of actions that it executes in order, or it may only permit a
* single pending action at any given time. If a task composed of many actions needs to ensure no
* other actions are queued in between, it must use a lock. This is analogous to thread
* synchronization. This can also be described as a queue to enter other queues.
*
* <p>
* Example:
*
* <pre>
* public CompletableFuture<Void> exampleLock1() {
* AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
* return seq(TypeSpec.VOID).then((seq) -> {
* lock.acquire(null).handle(seq::next);
* }, hold).then((seq) -> {
* doCriticalStuff().handle(seq::next);
* }).then((seq) -> {
* doMoreCriticalStuff().handle(seq::next);
* }).then((seq) -> {
* hold.release();
* seq.exit();
* }).asCompletableFuture().exceptionally((exc) -> {
* hold.release();
* return ExceptionUtils.rethrow(exc);
* });
* }
* </pre>
*
* <p>
* Or more succinctly:
*
* <pre>
* public CompletableFuture<Void> exampleLock2() {
* return lock.with(TypeSpec.VOID, null).then((hold, seq) -> {
* doCriticalStuff().handle(seq::next);
* }).then((seq) -> {
* doMoreCriticalStuff().handle(seq::next);
* }).asCompletableFuture();
* }
* </pre>
*
* <p>
* Re-entry is supported via a {@link Hold}. A method that offers re-enter into a critical section
* must accept a {@link Hold} parameter. Generally, the caller indicates the callee should reenter
* by passing the current hold. It then forwards it to the lock, via {@link #acquire(Hold)} or
* {@link #with(TypeSpec, Hold)}. The hold must be a reference to the current hold or null. A hold
* from another lock cannot be used for re-entry. If it is null, normal behavior is applied and the
* queue is served in FIFO order. If it is a reference to the current hold, the callback is executed
* immediately. Reentrant holds must be released in the reverse order of acquisition.
*
* <pre>
* public CompletableFuture<Void> canReenter(Hold reenter) {
* return lock.with(TypeSpec.VOID, reenter).then((hold, seq) -> {
* doCriticalStuff();
* }).asCompletableFuture();
* }
*
* public CompletableFuture<Void> exampleLock3() {
* return lock.with(TypeSpec.VOID, null).then((hold, seq) -> {
* canReenter(hold).handle(seq::next);
* }).then((seq) -> {
* doMoreCriticalStuff().handle(seq::next);
* }).asCompletableFuture();
* }
* </pre>
*
* <p>
* The implementation is based on a queue a queue of futures. The {@link #acquire(Hold)} task
* completes when the lock is available or if re-entry is possible. {@link Hold#release()} is not a
* task, but it must be called to ensure the next handler is invoked. If a hold is forgotten, e.g.,
* a sequence fails to release it, deadlock will likely occur. In some circumstances, deadlock is
* detected, causing all queued actions (current and future) to complete exceptionally with an
* {@link IllegalStateException}. Deadlock detection is a debugging feature. A programmer cannot
* rely on it for error recovery. If the exception is thrown, it indicates a serious flaw in the
* program which cannot be corrected at runtime.
*
* <p>
* The above examples demonstrate two critical actions, because in general, a single action is
* atomic. This lock <em>does not</em> protect against the usual multi-threaded hazards. Because any
* queue may be served by a multi-threaded executor, shared resources must be protected using
* standard synchronization primitives, e.g., the {@code synchronized} keyword. Resources whose
* methods provide futures are better protected using this lock, because a standard {@link Lock}
* will block the calling thread -- perhaps stalling a queue's executor -- whereas this lock permits
* the thread to execute other actions.
*
* @note This implementation offers little protection against double locking, or gratuitous
* releasing.
* @note As an asynchronous task, {@link #acquire()} returns immediately, but the future does not
* complete until the lock is acquired.
*/
public class AsyncLock {
private class HoldState implements Runnable {
boolean released = false;
@Override
public void run() {
if (!released) {
Msg.error(this, "Some poor soul forgot to release a lock. Now, it's dead!");
dead = true;
List<CompletableFuture<?>> copy;
synchronized (AsyncLock.this) {
copy = List.copyOf(queue);
queue.clear();
}
for (CompletableFuture<?> future : copy) {
future.completeExceptionally(new IllegalStateException("This lock is dead! " +
"I.e., an ownership token became phantom reachable without first being " +
"released"));
}
}
}
}
protected final Deque<CompletableFuture<Hold>> queue = new LinkedList<>();
protected WeakReference<Hold> curHold;
protected int reentries = 0;
protected Throwable disposalReason;
protected boolean dead = false;
protected final String debugName;
/**
* An opaque lock ownership handle
*/
public class Hold {
final HoldState state = new HoldState();
final Cleanable cleanable;
private Hold() {
cleanable = AsyncUtils.CLEANER.register(this, state);
}
/**
* Release ownership of the associated lock
*/
public void release() {
debug(this + ".release()");
CompletableFuture<Hold> next;
Hold oldHold = null;
synchronized (AsyncLock.this) {
oldHold = curHold.get();
if (this != oldHold) {
Msg.error(this, "Invalid ownership handle: " + oldHold + " != " + this);
throw new IllegalStateException("Invalid ownership handle");
}
if (reentries > 0) {
debug(" is from reentry");
reentries--;
return;
}
debug(" is non-reentrant release");
state.released = true;
next = queue.poll();
if (next != null) {
debug(" has queued waiters");
Hold newHold = new Hold();
curHold = new WeakReference<>(newHold);
debug(" launching next, granting " + newHold);
// Use completeAsync, since I'm inside synchronized block
next.completeAsync(() -> newHold);
return;
}
debug(" has no waiters");
curHold = null;
return;
}
}
}
/**
* Construct a new lock
*/
public AsyncLock() {
this(null);
}
/**
* Construct a lock with debug printing
*
* <p>
* This lock will print calls to {@link #acquire(Hold)} and {@link Hold#release()}. It will also
* note when the lock is acquired or re-entered, printing the current hold.
*
* @param debugName a name to prefix to debug messages
*/
public AsyncLock(String debugName) {
this.debugName = debugName;
}
private void debug(String msg) {
if (debugName != null) {
Msg.debug(this, "LOCK: " + debugName + ": " + msg);
}
}
/**
* Queue a future on this lock, possibly re-entering
*
* <p>
* If reentry is {@code null}, then this will acquire the lock without reentry. Otherwise, the
* lock checks the provided hold. If it is valid, the lock is immediately acquired via re-entry.
* If it is not valid, an exception is thrown.
*
* @param reentry a hold to prove current lock ownership for reentry
* @return a future that completes when the lock is held
* @throws IllegalStateException if the given reentry hold is not the current hold on this lock
*/
public CompletableFuture<Hold> acquire(Hold reentry) {
debug(".acquire(" + reentry + ")");
Hold strongHold = null;
synchronized (this) {
if (disposalReason != null) {
return CompletableFuture.failedFuture(disposalReason);
}
if (dead) {
throw new IllegalStateException("This lock is dead! " +
"I.e., an ownership token was finalized without first being released");
}
if (reentry == null && curHold != null) {
debug(" is held: queuing");
CompletableFuture<Hold> future = new CompletableFuture<>();
queue.add(future);
return future;
}
if (reentry == null && curHold == null) {
strongHold = new Hold();
debug(" is available: granting " + strongHold);
curHold = new WeakReference<>(strongHold);
return CompletableFuture.completedFuture(strongHold);
}
if (reentry.state.released) {
throw new IllegalStateException("Reentrant hold is released");
}
if (reentry == curHold.get()) {
debug(" is held by requester: reentering");
reentries++;
return CompletableFuture.completedFuture(reentry);
}
// TODO: This might actually be an internal error.
// I can't think of a situation where this could occur by API misuse.
throw new IllegalStateException("Reentrant hold is not the current hold");
}
}
/**
* Queue a sequence of actions on this lock
*
* <p>
* The lock will be acquired before executing the first action of the sequence, and the hold
* will be automatically released upon completion, whether normal or exceptional. The first
* action receives a reference to the hold, which may be used to re-enter the lock.
*
* <p>
* If the sequence stalls, i.e., an action never completes, it will cause deadlock.
*
* @param type the type "returned" by the sequence
* @param hold an optional handle to prove current ownership for re-entry
* @return a sequence of actions wrapped by lock acquisition and release
*/
public <R> AsyncSequenceWithTemp<R, Hold> with(TypeSpec<R> type, Hold hold) {
AtomicReference<Hold> handle = new AtomicReference<>();
return with(type, hold, handle).then((seq) -> {
seq.next(handle.get(), null);
}, TypeSpec.cls(Hold.class));
}
/**
* Queue a sequence of actions on this lock
*
* <p>
* Identical to {@link #with(TypeSpec, Hold)} except that the acquired hold is stored into an
* atomic reference rather than passed to the first action.
*
* @param type the type "returned" by the sequence
* @param hold an optional hold to prove current ownership for re-entry
* @param handle an atomic reference to store the hold
* @see #with(TypeSpec, Hold)
* @return a sequence of actions wrapped by lock acquisition and release
*/
public <R> AsyncSequenceWithoutTemp<R> with(TypeSpec<R> type, Hold hold,
AtomicReference<Hold> handle) {
return sequence(type).then((seq) -> {
acquire(hold).handle(seq::next);
}, handle).onExit((result, exc) -> {
handle.get().release();
});
}
/**
* Destroy this lock, causing all pending actions to complete exceptionally
*/
public void dispose(Throwable reason) {
List<CompletableFuture<?>> copy;
synchronized (this) {
disposalReason = reason;
copy = List.copyOf(queue);
queue.clear();
}
for (CompletableFuture<?> future : copy) {
future.completeExceptionally(reason);
}
}
}

View file

@ -1,167 +0,0 @@
/* ###
* 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.async;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
/**
* A cache of futures which pairs each to its result by key
*
* <p>
* The cache accepts promises and results, storing unpaired entries for a timeout period. Each
* promise is fulfilled when the cache accepts its corresponding result, determined by key.
* Conversely, a cached result fulfills its corresponding promise, determined by key, when the cache
* accepts that promise. Thus, the cache is two sided. When both the promise and the result for a
* given key enter the cache, they are paired, the promise is fulfilled, and both are removed from
* the cache.
*
* <p>
* If an entry is not paired within the timeout period, it is evicted. An evicted promise is likely
* a recoverable error, e.g., a request timed out. An evicted result is likely a logic or
* synchronization error. Requests, i.e., promises, are usually created before the result is
* obtained. A result may enter the cache first due to variance in execution order, but the promise
* usually enters soon after. If not, more than likely, the developer forgot to enter the request
* into the cache, or if the result was reported out of band, it is gratuitous. Callbacks are
* provided for eviction from either side of the cache.
*
* @param <K> the type of keys
* @param <V> the type of result values
*/
public abstract class AsyncPairingCache<K, V> {
private final Map<K, V> results;
private final Map<K, V> resultsView;
private final Map<K, CompletableFuture<V>> promises;
private final Map<K, CompletableFuture<V>> promisesView;
/**
* Construct a new matching cache
*
* @param maxPending the maximum number of pending promises or results before the eldest is
* evicted. Each is counted independently, e.g., a value of 5 permits 5 pending
* promises and 5 pending results simultaneously.
*/
public AsyncPairingCache(int maxPending) {
results = createResultCache(maxPending);
resultsView = Collections.unmodifiableMap(results);
promises = createPromiseCache(maxPending);
promisesView = Collections.unmodifiableMap(promises);
}
protected abstract Map<K, V> createResultCache(int max);
protected abstract Map<K, CompletableFuture<V>> createPromiseCache(int max);
/**
* Enter a promise for the given key into the cache
*
* <p>
* If the result for the given key is already available, the promise does not enter the cache.
* Instead, the result is removed and the promise is completed.
*
* @param key the key for the expected result
* @param promise the promise to be completed when the result is available
*/
public CompletableFuture<V> waitOn(K key) {
return waitOn(key, k -> new CompletableFuture<>());
}
/**
* Enter a promise for the given key into the cache
*
* <p>
* If the result for the given key is already available, the promise does not enter the cache.
* Instead, the result is removed and the promise is completed.
*
* @param key the key for the expected result
* @param promise the promise to be completed when the result is available
*/
public CompletableFuture<V> waitOn(K key, Function<K, CompletableFuture<V>> futureFactory) {
V value;
synchronized (this) {
value = results.remove(key);
if (value == null) {
return promises.computeIfAbsent(key, futureFactory);
}
}
CompletableFuture<V> future = futureFactory.apply(key);
future.complete(value);
return future;
}
/**
* Enter a result for the given key into the cache
*
* <p>
* If a promise for the key already exists, the result does not enter the cache. Instead, the
* promise is removed and completed.
*
* @param key the key for the provided result
* @param value the result
*/
public void fulfill(K key, V value) {
CompletableFuture<V> promise;
synchronized (this) {
promise = promises.remove(key);
if (promise == null) {
results.put(key, value);
return;
}
}
promise.complete(value);
}
/**
* Flush the cache, completing all pending requests exceptionally
*
* <p>
* Both sides of the cache are cleared.
*
* @param exc the exception for completing the requests
*/
public void flush(Throwable exc) {
Set<CompletableFuture<V>> copy = new HashSet<>();
synchronized (this) {
copy.addAll(promises.values());
promises.clear();
results.clear();
}
for (CompletableFuture<V> p : copy) {
p.completeExceptionally(exc);
}
}
/**
* Get the map view of unpaired promises
*
* @return the map
*/
public Map<K, CompletableFuture<V>> getUnpairedPromises() {
return promisesView;
}
/**
* Get the map view of unpaired results
*
* @return the map
*/
public Map<K, V> getUnpairedResults() {
return resultsView;
}
}

View file

@ -1,78 +0,0 @@
/* ###
* 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.async;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
/**
* A race which orders futures by completion time
*
* This is roughly equivalent to Java's
* {@link CompletableFuture#acceptEither(CompletionStage, Consumer)} or
* {@link CompletableFuture#anyOf(CompletableFuture...)}; however, it is more general. Many futures
* may participate in the race. Each call to {@link #include(CompletableFuture)} adds a participant
* to the race. A call to {@link #next()} returns a future which completes when the first
* participant completes. This subsumes and provides a shorthand for the standard methods. A second
* call to {@link #next()} returns a future which completes when the second participant completes,
* and so on.
*
* This primitive does not provide a means to identify the winning participants. If identification
* is desired, it must be done via the yielded object, or out of band.
*
* @param <T> the type of object yielded by race participants
*/
public class AsyncRace<T> {
private final Deque<T> finishers = new LinkedList<>();
private final Deque<CompletableFuture<? super T>> queue = new LinkedList<>();
/**
* Include a participant in this race
*
* @param future the participant to add
*/
public AsyncRace<T> include(CompletableFuture<? extends T> future) {
future.thenAccept(t -> {
synchronized (this) {
if (queue.isEmpty()) {
finishers.offer(t);
}
else {
queue.poll().complete(t);
}
}
});
return this;
}
/**
* Obtain a future that completes with the result of the first (since last called) finishing
* participant
*
* @return the next "any of" future
*/
public synchronized CompletableFuture<T> next() {
if (finishers.isEmpty()) {
CompletableFuture<T> future = new CompletableFuture<>();
queue.offer(future);
return future;
}
return CompletableFuture.completedFuture(finishers.poll());
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -34,57 +34,31 @@ import java.util.function.Supplier;
* {@link ScheduledThreadPoolExecutor}. * {@link ScheduledThreadPoolExecutor}.
* *
* <p> * <p>
* A delay is achieved using {@link #mark()}, then {@link #after(long)}. For example, within a * A delay is achieved using {@link #mark()}, then {@link Mark#after(long)}.
* {@link AsyncUtils#sequence(TypeSpec)}:
* *
* <pre> * <pre>
* timer.mark().after(1000).handle(seq::next); * future.thenCompose(__ -> timer.mark().after(1000))
* </pre> * </pre>
* *
* <p> * <p>
* {@link #mark()} marks the current system time; all subsequent calls to {@link #after(long)} * {@link #mark()} marks the current system time; all calls to the mark's {@link Mark#after(long)}
* schedule futures relative to this mark. Using {@link #after(long)} before {@link #mark()} gives * schedule futures relative to this mark. Scheduling a timed sequence of actions is best
* undefined behavior. Scheduling a timed sequence of actions is best accomplished using times * accomplished using times relative to a single mark. For example:
* relative to a single mark. For example:
* *
* <pre> * <pre>
* sequence(TypeSpec.VOID).then((seq) -> { * Mark mark = timer.mark();
* timer.mark().after(1000).handle(seq::next); * mark.after(1000).thenCompose(__ -> {
* }).then((seq) -> { * doTaskAtOneSecond();
* doTaskAtOneSecond().handle(seq::next); * return mark.after(2000);
* }).then((seq) -> { * }).thenAccept(__ -> {
* timer.after(2000).handle(seq::next); * doTaskAtTwoSeconds();
* }).then((seq) -> { * });
* doTaskAtTwoSeconds().handle(seq::next);
* }).asCompletableFuture();
* </pre> * </pre>
* *
* <p> * <p>
* This provides slightly more precise scheduling than delaying for a fixed period between tasks. * This provides slightly more precise scheduling than delaying for a fixed period between tasks.
* Consider a second example: * Consider a second example:
* *
* <pre>
* sequence(TypeSpec.VOID).then((seq) -> {
* timer.mark().after(1000).handle(seq::next);
* }).then((seq) -> {
* doTaskAtOneSecond().handle(seq::next);
* }).then((seq) -> {
* timer.mark().after(1000).handle(seq::next);
* }).then((seq) -> {
* doTaskAtTwoSeconds().handle(seq::next);
* }).asCompletableFuture();
* </pre>
*
* <p>
* In the first example, {@code doTaskAtTwoSeconds} executes at 2000ms from the mark + some
* scheduling overhead. In the second example, {@code doTaskAtTwoSeconds} executes at 1000ms + some
* scheduling overhead + the time to execute {@code doTaskAtOneSecond} + 1000ms + some more
* scheduling overhead. Using the second pattern repeatedly can lead to increased inaccuracies as
* overhead accumulates over time. This may be an issue for some applications. Using the first
* pattern, there is no accumulation. The actual scheduled time will always be the specified time
* from the mark + some scheduling overhead. The scheduling overhead is generally bounded to a small
* constant and depends on the accuracy of the host OS and JVM.
*
* <p> * <p>
* Like {@link Timer}, each {@link AsyncTimer} is backed by a single thread which uses * Like {@link Timer}, each {@link AsyncTimer} is backed by a single thread which uses
* {@link Object#wait()} to implement its timing. Thus, this is not suitable for real-time * {@link Object#wait()} to implement its timing. Thus, this is not suitable for real-time
@ -108,8 +82,9 @@ public class AsyncTimer {
/** /**
* Schedule a task to run when the given number of milliseconds has passed since this mark * Schedule a task to run when the given number of milliseconds has passed since this mark
* *
* <p>
* The method returns immediately, giving a future result. The future completes "soon after" * The method returns immediately, giving a future result. The future completes "soon after"
* the requested interval, since the last mark, passes. There is some minimal overhead, but * the requested interval since the last mark passes. There is some minimal overhead, but
* the scheduler endeavors to complete the future as close to the given time as possible. * the scheduler endeavors to complete the future as close to the given time as possible.
* The actual scheduled time will not precede the requested time. * The actual scheduled time will not precede the requested time.
* *

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,396 +16,15 @@
package ghidra.async; package ghidra.async;
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.*; import java.util.function.BiFunction;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import ghidra.async.loop.*;
import ghidra.async.seq.*;
import ghidra.util.Msg;
/** /**
* A wrapper for Java's {@link CompletableFuture} that provides additional fluency * Some conveniences when dealing with Java's {@link CompletableFuture}s.
*
* <p>
* The {@link CompletableFuture} class is very useful for organizing asynchronous tasks into tidy
* sequences. See {@link CompletableFuture#thenCompose(Function)} for more information, though
* better examples are available in other online resources.
*
* <p>
* These utilities seek to ease some operations, e.g., loops, and provide some control of execution
* in sequences not otherwise given by Java's futures. While some verboseness is eliminated, these
* utilities have tradeoffs. They interoperate, permitting use of each according to circumstances
* and taste.
*
* <p>
* Additional conveniences allow Java's non-blocking IO to interoperate more closely with futures.
* See {@link AsynchronousByteChannel}. The "nio" API predates {@link CompletableFuture}, so they
* cannot readily interact. They use {@link CompletionHandler}s instead of futures. This class
* provides API wrappers that return {@link CompletableFuture}s.
*
* <p>
* An asynchronous method is one that returns control immediately, and may promise to provide a
* completed result at some future time. In the case of computation, the method may launch a thread
* or queue the computation on an executor. In the case of non-blocking IO, the method may queue a
* handler on an asynchronous channel. The method is said to "promise" a completed result. It
* instantiates the promise and arranges its fulfillment. It can then return control immediately by
* returning the corresponding "future" object. When the promise is fulfilled, the result is stored
* in the future object, optionally invoking a callback.
*
* <p>
* Java implements this pattern in {@link CompletableFuture}. Java does not provide a separate class
* for the promise. The asynchronous method instantiates the completable future, arranging for its
* completion, and immediately returns it to the caller. Anything with a referece to the future may
* complete it. The "promise" and "future" objects are not distinct. Note that
* {@link AsynchronousByteChannel} does provide methods that return {@link Future}, but the
* underlying implementation is not a {@link CompletableFuture}, so it cannot accept callbacks.
*
* <p>
* There are generally two ways the caller can consume the value. The simplest is to wait, by
* blocking, on the result. This defeats the purpose of asynchronous processing. For example:
*
* <pre>
* System.out.println(processAsync(input1).get());
* System.out.println(processAsync(input2).get());
* </pre>
*
* <p>
* This block is not any more efficient than the following sequential program:
*
* <pre>
* System.out.println(processSync(input1));
* System.out.println(processSync(input2));
* </pre>
*
* <p>
* In fact, it may be worse from the overhead of multi-threading, queueing, scheduling, etc. To take
* advantage of multi-threaded executors, processing should be performed in parallel when possible.
* For example:
*
* <pre>
* CompletableFuture<Long> future1 = processAsync(input1);
* CompletableFuture<Long> future2 = processAsync(input2);
* System.out.println(future1.get());
* System.out.println(future2.get());
* </pre>
*
* <p>
* Now, both inputs can be processed simultaneously. The other way to consume the value is via
* callback. This elegant option may seem complex. It is especially effective when the calling
* thread cannot or should not block. For example:
*
* <pre>
* processAsync(input1).thenAccept((result1) -> {
* System.out.println(result1);
* });
* processAsync(input2).thenAccept((result2) -> {
* System.out.println(result2);
* });
* </pre>
*
* <p>
* Now, both inputs can be processed simultaneously, and each result is printed the moment it
* becomes available. Another notable difference: If processing of input2 finishes first, it can be
* printed without waiting on input1. Furthermore, the calling thread is not blocked. Long chains of
* asynchronous callbacks, however, an become difficult to manage.
*
* <p>
* Suppose a program must fetch a list of integers from a storage system, then process each entry by
* submitting it to a remote service, and finally aggregate the results with a given initial value.
*
* <p>
* The traditional implementation might look like this:
*
* <pre>
* public int doWork(int start) {
* Storage store = connectStorage(ADDR1);
* Service serve = connectService(ADDR2);
* List<Integer> list = store.fetchList();
* int sum = start;
* for (int entry : list) {
* sum += serve.process(entry);
* }
* store.close();
* serve.close();
* return sum;
* }
* </pre>
*
* <p>
* Now, suppose the connect methods, the fetch method, and the process method all perform their work
* asynchronously, providing results via futures. The implementation ought to provide for parallel
* execution. The chain of asynchronous tasks essentially becomes a composite task, itself
* implementing the future pattern. One scheme is to create a class extending
* {@link CompletableFuture} and containing all callback methods in the chain:
*
* <pre>
* class FetchAndProcess extends CompletableFuture<Integer> {
* Storage storage;
* Service service;
* AtomicInteger sum = new AtomicInteger();
*
* FetchAndProcess(int start) {
* sum = start;
* connectStorage(ADDR1).handle(this::storageConnected);
* }
*
* Void storageConnected(Storage s, Throwable exc) {
* if (exc != null) {
* completeExceptionally(exc);
* }
* else {
* storage = s;
* connectService(ADDR2).handle(this::serviceConnected);
* }
* return null;
* }
*
* Void serviceConnected(Service s, Throwable exc) {
* if (exc != null) {
* completeExceptionally(exc);
* }
* else {
* service = s;
* storage.fetchList().handle(this::fetchedList);
* }
* return null;
* }
*
* Void fetchedList(List<Integer> list, Throwable exc) {
* if (exc != null) {
* completeExceptionally(exc);
* }
* else {
* List<CompletableFuture<Void>> futures = new ArrayList<>();
* for (int entry : list) {
* futures.add(service.process(entry).thenAccept((result) -> {
* sum.addAndGet(result);
* }));
* }
* CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]))
* .handle(this::processedList);
* }
* return null;
* }
*
* Void processedList(Void v, Throwable exc) {
* if (exc != null) {
* completeExceptionally(exc);
* }
* else {
* complete(sum.get());
* }
* return null;
* }
* }
*
* public CompletableFuture<Integer> doWork(int start) {
* return new FetchAndProcess(start);
* }
* </pre>
*
* <p>
* The asynchronous method then just instantiates the class and returns it. The final callback in
* the chain must call {@link CompletableFuture#complete(Object)}. The main deficit to this method
* is that the operational steps are buried between method declarations and error checks. Also,
* because the method must return {@link Void}, not just {@code void}, all the methods must return
* {@code null}. Furthermore, errors that occur in the callback methods do not get communicated to
* the caller of {@code doWork}. Additional try-cache blocks would be required.
*
* <p>
* Lambda functions could be substituted for method references avoiding the class declaration, but
* that would result in nesting several levels deep -- at least one level per callback in the chain.
* To avoid nesting, Java provides {@link CompletableFuture#thenCompose(Function)}, which provides a
* fairly elegant implementation:
*
* <pre>
* public CompletableFuture<Integer> doWork(int start) {
* AtomicReference<Storage> store = new AtomicReference<>();
* AtomicReference<Service> serve = new AtomicReference<>();
* AtomicInteger sum = new AtomicInteger(start);
* return connectStorage(ADDR1).thenCompose((s) -> {
* store.set(s);
* return connectService(ADDR2);
* }).thenCompose((s) -> {
* serve.set(s);
* return store.get().fetchList();
* }).thenCompose((list) -> {
* List<CompletableFuture<Void>> futures = new ArrayList<>();
* for (int entry : list) {
* futures.add(serve.get().process(entry).thenAccept((result) -> {
* sum.addAndGet(result);
* }));
* }
* return CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]));
* }).thenApply((v) -> {
* store.get().close();
* serve.get().close();
* return sum.get();
* });
* }
* </pre>
*
* <p>
* This does have a couple notable deficits, though. First, the {@code return} keyword is almost
* abused. The callbacks are defined inline as if they were simply actions within the method. In
* this context, {@code return} can become misleading, because that is not the point where the
* method terminates with a result, but rather the point where the action provides the next
* subordinate asynchronous task in the chain. Second, not all the actions are presented at the same
* nesting level, even though they reside in the same sequence. Because of this, it's not
* immediately obvious that, e.g., {@code connectStorage} precedes {@code connectService}, or
* perhaps a more subtle example, that {@code process} precedes {@code sumAndGet}.
*
* <p>
* With the utilities, we can write this sequence a little differently. The goal is to mimic
* sequential programming not just in its appearance, but also in its control structure:
*
* <pre>
* public CompletableFuture<Integer> doWork(int start) {
* AtomicReference<Storage> store = new AtomicReference<>();
* AtomicReference<Service> serve = new AtomicReference<>();
* AtomicInteger sum = new AtomicInteger(start);
* return sequence(TypeSpec.INT).then((seq) -> {
* connectStorage(ADDR1).handle(seq::next);
* }, store).then((seq) -> {
* connectService(ADDR2).handle(seq::next);
* }, serve).then((seq) -> {
* store.get().fetchList().handle(seq::next);
* }, TypeSpec.obj((List<Integer>) null)).then((list, seq) -> {
* AsyncFence fence = new AsyncFence();
* for (int entry : list) {
* fence.include(sequence(TypeSpec.VOID).then((seq2) -> {
* serve.get().process(entry).handle(seq2::next);
* }, TypeSpec.INT).then((result, seq2) -> {
* sum.addAndGet(result);
* seq2.exit();
* }).asCompletableFuture());
* }
* fence.ready().handle(seq::next);
* }).then((seq) -> {
* store.get().close();
* serve.get().close();
* seq.exit(sum.get());
* }).asCompletableFuture();
* }
* </pre>
*
* <p>
* The implementation is slightly longer because the body of the for-loop is also implemented as a
* two-step sequence instead of using {@link CompletableFuture#thenAccept(Consumer)}. First, notice
* that the sequence starts with a call to {@link #sequence(TypeSpec)}. This essentially declares an
* inline asynchronous method whose result will have the given type. This mimics a standard method
* declaration where the type is given first; whereas
* {@link CompletableFuture#thenCompose(Function)} implicitly takes the return type of the final
* callback.
*
* <p>
* Second, notice that the callback is given a reference to the sequence. Technically, this is just
* a contextual view of the sequence's handlers. It implements two methods which may be passed to
* {@link CompletableFuture#handle(BiFunction)}. This constitutes a major difference from the
* composition pattern. Instead of returning a {@link CompletableFuture} to wait on, the action must
* explicitly call {@code handle(seq::next)}. This permits the execution of additional code after
* calling the asynchronous method in parallel. While this provides more flexibility, it also
* provies more opportunities for mistakes. Usually, passing or calling the handler is the last line
* in a callback action. See {@link AsyncSequenceHandlerForRunner#next(Void, Throwable)} and
* {@link AsyncSequenceHandlerForProducer#next(Object, Throwable)}.
*
* <p>
* Third, notice that the call to {@code connectStorage} is now at the same nesting level as
* {@code connectService}. This keeps the operative parts of each action immediately visible as they
* are left justified, have the same indentation, and are not preceded by {@code return}. However,
* it is easy to forget {@link CompletableFuture#handle(BiFunction)}.
*
* <p>
* Fourth, notice that each action is always separated by a call to
* {@link AsyncSequenceWithoutTemp#then(AsyncSequenceActionRuns)} or one of its variants in
* {@link AsyncSequenceWithTemp}. This generally means the lines between actions can be ignored. In
* the composition pattern, the reader would need to see that the final action is given by
* {@link CompletableFuture#thenApply(Function)} in order to properly comprehend the return
* statement properly. It is not another asynchronous method as in the previous actions. Rather it
* is the final result. The line between actions in the sequence pattern cannot be ignored when a
* value is passed from one action directly to the following action. The call to {@code then}
* includes the type of the passed value. Worse yet, this sometimes requires casting {@code null} to
* an arbitrary type. In the example, a list is retrieved in the third action and processed in the
* fourth. To specify the type, {@code null} is cast to
* {@link List}{@code <}{@link Integer}{@code >} and passed to {@link TypeSpec#obj(Object)}. The
* list retrieved by {@code fetchList} is received in the following action's first parameter
* ({@code list}) of its lambda function.
*
* <p>
* Fifth, notice the use of {@link AsyncFence}. This convenience class does essentially the same
* thing as the composition and class patterns did, but provides more meaningful method names and a
* more succinct syntax.
*
* <p>
* Finally, notice the call to to {@link AsyncSequenceHandlerForRunner#exit(Object)} passing the
* final result of the sequence. Requiring this is a bit of a nuisance, but it makes clear what the
* result of the sequence is. Furthermore, {@code exit} can be called by any action, not just the
* final one. In the composition pattern, execution cannot be truncated except by error handling. In
* the sequence pattern, any action can terminate the sequence and "return" the result. Every action
* must either call or pass one of these handlers or the sequence will abruptly halt. Also, some
* action, usually the final one, must pass or invoke the {@code exit} handler. If the final action
* uses {@code next}, the sequence will "return" {@code null}. In summary, {@code next} passes a
* value to the following action while {@code exit} passes a value as the sequence result, skipping
* the remaining actions. The result of composed sequence of actions is communicated via its own
* completable future. This future is obtained via {@link AsyncSequenceWithoutTemp#finish()}. Note
* that a sequence whose final action produces a temporary value does not yield a completable
* future.
*
* <p>
* Java's built-in mechanisms provide for error handling. Usually invoking
* {@link CompletableFuture#exceptionally(Function)} on the result of
* {@link AsyncSequenceWithoutTemp#finish()} is sufficient. To illustrate, the two connections from
* the example are assuredly closed by appending a call to {@code exceptionally}:
*
* <pre>
* return sequence(TypeSpec.INT).then((seq) -> {
* // ...
* }).asCompletableFuture().exceptionally((exc) -> {
* if (store.get() != null) {
* store.get().close();
* }
* if (serve.get() != null) {
* serve.get().close();
* }
* return ExceptionUtils.rethrow(exc);
* });
* </pre>
*
* <p>
* If errors must be handled in a more granular fashion, consider invoking
* {@link CompletableFuture#exceptionally(Function)} on the appropriate asynchronous task before
* calling {@link CompletableFuture#handle(BiFunction)}. For example:
*
* <pre>
* store.get().fetchList().exceptionally((exc) -> {
* if (exc instanceof ListNotFoundException) {
* return DEFAULT_LIST;
* }
* return ExceptionUtils.rethrow(exc);
* }).handle(seq::next);
* </pre>
*
* <p>
* Alternatively:
*
* <pre>
* store.get().fetchList().handle(seq::next).exceptionally((exc) -> {
* if (exc instanceof ListNotFoundException) {
* seq.next(DEFAULT_LIST);
* }
* else {
* seq.exit(exc);
* }
* return null;
* });
* </pre>
*/ */
public interface AsyncUtils<T> { public interface AsyncUtils {
Cleaner CLEANER = Cleaner.create(); Cleaner CLEANER = Cleaner.create();
ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool(); ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool();
@ -418,421 +37,6 @@ public interface AsyncUtils<T> {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
public static <T> CompletableFuture<T> nil(Class<T> cls) {
return CompletableFuture.completedFuture(null);
}
/**
* Repeatedly launch an asynchronous task and process the result
*
* <p>
* This loosely corresponds to a while loop. The invocation consists of two actions: One to
* launch a subordinate task, likely producing a result, and the second to consume the result
* and repeat the loop. Note that the loop may be repeated in parallel to the processing of the
* result by calling {@link AsyncLoopHandlerForSecond#repeat()} early in the consumer action.
* Either action may explicitly exit the loop, optionally providing a result. Ordinarily, the
* loop repeats indefinitely.
*
* <p>
* Example:
*
* <pre>
* loop(TypeSpec.VOID, (loop) -> {
* receiveData().handle(loop::consume);
* }, TypeSpec.BYTE_ARRAY, (data, loop) -> {
* loop.repeat();
* processData(data);
* });
* </pre>
*
* @param loopType the type of the result of the whole loop. This is usually
* {@link TypeSpec#VOID}.
* @param producer an action invoking a subordinate asynchronous task, usually producing some
* result.
* @param iterateType the type of result produced by the subordinate asynchronous task
* @param consumer an action consuming the result of the task and explicitly repeating or
* exiting the loop
* @return a future which completes upon explicit loop termination
*/
public static <T, U> CompletableFuture<T> loop(TypeSpec<T> loopType,
AsyncLoopFirstActionProduces<T, U> producer, TypeSpec<U> iterateType,
AsyncLoopSecondActionConsumes<T, ? super U> consumer) {
AsyncLoop<T, U> loop = new AsyncLoop<>(producer, iterateType, consumer);
loop.begin();
return loop;
}
/**
* Repeatedly launch an asynchronous task
*
* This loosely corresponds to a while loop. This invocation consists of a single action: To
* launch a subordinate task, producing no result, or to exit the loop. The subordinate task
* should repeat the loop upon completion. If the loop does not require a subordinate
* asynchronous task, then please use an actual Java {@code while} loop. If the subordinate task
* does produce a result, it must be ignored using
* {@link AsyncLoopHandlerForSecond#repeatIgnore(Object, Throwable)}.
*
* Example:
*
* <pre>
* loop(TypeSpec.VOID, (loop) -> {
* someTask().handle(loop::repeat);
* });
* </pre>
*
* @param loopType the type of the result of the whole loop. This is usually
* {@link TypeSpec.VOID}.
* @param action an action launching a subordinate task, producing no, i.e., a {@link Void}
* result, upon whose completion the loop repeats.
* @return a future which completes upon explicit loop termination
*/
public static <T> CompletableFuture<T> loop(TypeSpec<T> loopType,
AsyncLoopOnlyActionRuns<T> action) {
return loop(loopType, (handler) -> {
handler.consume(null, null);
}, TypeSpec.VOID, (v, handler) -> {
action.accept(handler);
});
}
/**
* Launch a task for each element given by an iterator and process the result
*
* This loosely corresponds to a for loop. This invocation consists of two actions: One to
* consume the element and launch a subordinate task, likely producing a result, and the second
* to consume the result and repeat the loop. Note that the loop may be repeated in parallel to
* the processing of the result by calling {@link AsyncLoopHandlerForSecond#repeat()} early in
* the consumer action. Either action may explicitly exit the loop, optionally providing a
* result. Ordinarily, the loop executes until the iterator is exhausted, completing with
* {@code null}.
*
* This operates similarly to
* {@link #loop(TypeSpec, AsyncLoopFirstActionProduces, TypeSpec, AsyncLoopSecondActionConsumes)}
* except that it is controlled by an iterator.
*
* Example:
*
* <pre>
* each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
* sendItem().handle(loop::consume);
* }, TypeSpec.STRING, (message, loop) -> {
* loop.repeat();
* logResult(message);
* });
* </pre>
*
* @param loopType the type of the result of the whole loop. This is usually
* {@link TypeSpec#VOID}.
* @param it the iterator controlling the loop and providing elements
* @param producer an action consuming each element and invoking a subordinate asynchronous
* task, usually producing some result.
* @param iterateType the type of result produced by the subordinate asynchronous task.
* @param consumer and action consuming the result of the task and explicitly repeating or
* exiting the loop.
* @return a future which completes upon loop termination
*/
public static <T, E, U> CompletableFuture<T> each(TypeSpec<T> loopType, Iterator<E> it,
AsyncLoopFirstActionConsumesAndProduces<T, E, U> producer, TypeSpec<U> iterateType,
AsyncLoopSecondActionConsumes<T, U> consumer) {
return loop(loopType, (handler) -> {
if (it.hasNext()) {
E elem;
try {
elem = it.next();
}
catch (Throwable exc) {
handler.exit(null, exc);
return;
}
producer.accept(elem, handler);
}
else {
handler.exit(null, null);
}
}, iterateType, consumer);
}
/**
* Launch a task for each element given by an iterator
*
* This loosely corresponds to a for loop. This invocation consists of a single action: To
* consume the element and launch a subordinate task, producing no result, or to exit the loop.
* The subordinate task should repeat the loop upon completion. If the loop does not require a
* subordinate asynchronous task, then please use an actual Java {@code for} loop. If the
* subordinate task does produce a result, it must be ignored using
* {@link AsyncLoopHandlerForSecond#repeatIgnore(Object, Throwable)}.
*
* Example:
*
* <pre>
* each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
* sendItem().handle(loop::repeatIgnore);
* });
* </pre>
*
* @param loopType the type of the result of the whole loop. This is usually
* {@link TypeSpec#VOID}.
* @param it the iterator controlling the loop and providing elements
* @param action an action consuming each element and launching a subordinate asynchronous task,
* producing no result, upon whose completion the loop repeats.
* @return a future which completes upon loop termination
*/
public static <T, E> CompletableFuture<T> each(TypeSpec<T> loopType, Iterator<E> it,
AsyncLoopSecondActionConsumes<T, E> action) {
return each(loopType, it, (e, loop) -> {
loop.consume(e, null);
}, TypeSpec.obj((E) null), action);
}
/**
* Begin executing a sequence of actions
*
* This is a wrapper for Java's {@link CompletableFuture#thenCompose(Function)}. It aims to
* provide a little more flexibility. See the class documentation for a more thorough
* explanation with examples.
*
* Example:
*
* <pre>
* public CompletableFuture<Void> exampleSeq() {
* return sequence(TypeSpec.VOID).then((seq) -> {
* fetchValue().handle(seq::next);
* }, TypeSpec.INT).then((val, seq) -> {
* convertValue(val + 10).handle(seq::next);
* }, TypeSpec.STRING).then((str, seq) -> {
* System.out.println(str);
* seq.exit();
* }).asCompletableFuture();
* }
* </pre>
*
* Note that the sequence begins executing on the calling thread.
*
* @param type the type "returned" by the sequence
* @return an empty sequence ready to execute actions on the calling thread.
*/
public static <R> AsyncSequenceWithoutTemp<R> sequence(TypeSpec<R> type) {
return sequence(new CompletableFuture<>());
}
/**
* Begin executing a sequence of actions to complete a given future
*
* When using this variant, take care to call {@link AsyncSequenceWithoutTemp#finish()} or use
* {@link AsyncHandlerCanExit#exit(Object, Throwable)} in the final action. Otherwise, the
* sequence will not notify dependents of completion.
*
* @see #sequence(TypeSpec)
* @param on the future to complete
* @return an empty sequence ready to execute actions on the calling thread.
*/
public static <R> AsyncSequenceWithoutTemp<R> sequence(CompletableFuture<R> on) {
return new AsyncSequenceWithoutTemp<>(on, AsyncUtils.nil());
}
/**
* An adapter for methods accepting {@link CompletionHandler}s
*
* This class implements {@link CompletionHandler} with a wrapped {@link CompletableFuture}. It
* can be given to a method expecting a {@link CompletionHandler}, and the wrapped future can be
* given as the result for an asynchronous task. This allows methods expecting a
* {@link CompletionHandler} to participate in action callback chains.
*
* @param <T> the type "returned" by the asynchronous task
* @param <A> the type of attachment expected by the method. Usually {@link Object} is
* sufficient, because the attachment is not passed to the wrapped future.
*/
static class FutureCompletionHandler<T, A> implements CompletionHandler<T, A> {
CompletableFuture<T> future = new CompletableFuture<>();
@Override
public void completed(T result, A attachment) {
future.complete(result);
}
@Override
public void failed(Throwable exc, A attachment) {
future.completeExceptionally(exc);
}
}
/**
* An interface describing methods that accept an attachment and a {@link CompletionHandler}
*
* @param <T> the type "returned" to the {@link CompletionHandler}
*/
interface TakesCompletionHandlerArity0<T> {
<A> void launch(A attachment, CompletionHandler<T, ? super A> handler);
}
/**
* Like {@link TakesCompletionHandlerArity0} but with one additional parameter
*
* @param <T> the type "returned" to the {@link CompletionHandler}
* @param <P0> the type of the first parameter
*/
interface TakesCompletionHandlerArity1<T, P0> {
<A> void launch(P0 arg0, A attachment, CompletionHandler<T, ? super A> handler);
}
/**
* Like {@link TakesCompletionHandlerArity0} but with two additional parameters
*
* @param <T> the type "returned" to the {@link CompletionHandler}
* @param <P0> the type of the first parameter
* @param <P1> the type of the second parameter
*/
interface TakesCompletionHandlerArity2<T, P0, P1> {
<A> void launch(P0 arg0, P1 arg1, A attachment, CompletionHandler<T, ? super A> handler);
}
/**
* Like {@link TakesCompletionHandlerArity0} but with three additional parameters
*
* @param <T> the type "returned" to the {@link CompletionHandler}
* @param <P0> the type of the first parameter
* @param <P1> the type of the second parameter
* @param <P2> the type of the third parameter
*/
interface TakesCompletionHandlerArity3<T, P0, P1, P2> {
<A> void launch(P0 arg0, P1 arg1, P2 arg2, A attachment,
CompletionHandler<T, ? super A> handler);
}
/**
* Like {@link TakesCompletionHandlerArity0} but with four additional parameters
*
* @param <T> the type "returned" to the {@link CompletionHandler}
* @param <P0> the type of the first parameter
* @param <P1> the type of the second parameter
* @param <P2> the type of the third parameter
* @param <P3> the type of the fourth parameter
*/
interface TakesCompletionHandlerArity4<T, P0, P1, P2, P3> {
<A> void launch(P0 arg0, P1 arg1, P2 arg2, P3 arg3, A attachment,
CompletionHandler<T, ? super A> handler);
}
/**
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
*
* Many non-blocking IO methods' last two parameters are an attachment and a
* {@link CompletionHandler}, e.g.,
* {@link AsynchronousSocketChannel#read(java.nio.ByteBuffer, Object, CompletionHandler)}. This
* method can wrap those methods, returning a {@link CompletableFuture} instead.
*
* Example:
*
* <pre>
* completable(TypeSpec.INT, channel::read, buf).thenAccept((len) -> {
* // Check length and process received data
* });
* </pre>
*
* To help Java's template resolution, the first parameter is a {@link TypeSpec}. The second is
* a reference to the NIO method. Following that are up to four parameters to pass to the
* wrapped method. These correspond to the arguments preceding the attachment. Mismatched types
* are detected at compile time.
*
* @param type the type "returned" by the {@link CompletionHandler}
* @param func the function launching the asynchronous task
* @return the future to receive the completion result
*/
public static <T, A> CompletableFuture<T> completable(TypeSpec<T> type,
TakesCompletionHandlerArity0<T> func) {
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
func.launch(null, handler);
return handler.future;
}
/**
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
*
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
*
* @param type the type "returned" by the {@link CompletionHandler}
* @param func the function launching the asynchronous task
* @return the future to receive the completion result
*/
public static <T, P0, A> CompletableFuture<T> completable(TypeSpec<T> type,
TakesCompletionHandlerArity1<T, P0> func, P0 arg0) {
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
func.launch(arg0, null, handler);
return handler.future;
}
/**
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
*
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
*
* @param type the type "returned" by the {@link CompletionHandler}
* @param func the function launching the asynchronous task
* @return the future to receive the completion result
*/
public static <T, P0, P1, A> CompletableFuture<T> completable(TypeSpec<T> type,
TakesCompletionHandlerArity2<T, P0, P1> func, P0 arg0, P1 arg1) {
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
func.launch(arg0, arg1, null, handler);
return handler.future;
}
/**
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
*
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
*
* @param type the type "returned" by the {@link CompletionHandler}
* @param func the function launching the asynchronous task
* @return the future to receive the completion result
*/
public static <T, P0, P1, P2, A> CompletableFuture<T> completable(TypeSpec<T> type,
TakesCompletionHandlerArity3<T, P0, P1, P2> func, P0 arg0, P1 arg1, P2 arg2) {
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
func.launch(arg0, arg1, arg2, null, handler);
return handler.future;
}
/**
* Wrap an NIO asynchronous method in a {@link CompletableFuture}
*
* @see #completable(TypeSpec, TakesCompletionHandlerArity0)
*
* @param type the type "returned" by the {@link CompletionHandler}
* @param func the function launching the asynchronous task
* @return the future to receive the completion result
*/
public static <T, P0, P1, P2, P3, A> CompletableFuture<T> completable(TypeSpec<T> type,
TakesCompletionHandlerArity4<T, P0, P1, P2, P3> func, P0 arg0, P1 arg1, P2 arg2,
P3 arg3) {
FutureCompletionHandler<T, A> handler = new FutureCompletionHandler<>();
func.launch(arg0, arg1, arg2, arg3, null, handler);
return handler.future;
}
/**
* Wrap a {@link CompletableFuture} as a {@link CompletionHandler}-style asynchronous task
*
* This is used only in diagnostic classes to implement {@link CompletionHandler}-style
* asynchronous tasks. It is the opposite adapter to
* {@link #completable(TypeSpec, TakesCompletionHandlerArity0)} and its overloaded variants.
*
* @param future the future to wrap
* @param handler a handler to receive the callback on future completion
*/
public static <T, A> void handle(CompletableFuture<T> future, A attachment,
CompletionHandler<T, ? super A> handler) {
future.handle((result, exc) -> {
if (exc != null) {
handler.failed(exc, attachment);
}
else {
handler.completed(result, attachment);
}
return null;
});
}
public interface TemperamentalRunnable { public interface TemperamentalRunnable {
public void run() throws Throwable; public void run() throws Throwable;
} }
@ -841,23 +45,6 @@ public interface AsyncUtils<T> {
public T get() throws Throwable; public T get() throws Throwable;
} }
/**
* A convenience for protecting engines from errors in user callbacks
*
* If not used, then when multiple listeners are present, those following a listener whose
* callback generates an error may never actually be notified.
*
* @param cb the invocation of the user callback
*/
public static void defensive(TemperamentalRunnable cb) {
try {
cb.run();
}
catch (Throwable e) {
Msg.error(cb, "Error in callback", e);
}
}
/** /**
* Unwrap {@link CompletionException}s and {@link ExecutionException}s to get the real cause * Unwrap {@link CompletionException}s and {@link ExecutionException}s to get the real cause
* *

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,14 +20,11 @@ import java.util.concurrent.*;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import ghidra.async.seq.AsyncSequenceActionRuns;
import ghidra.async.seq.AsyncSequenceWithoutTemp;
import ghidra.util.Swing; import ghidra.util.Swing;
/** /**
* A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements * A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements
* {@link ExecutorService}. This makes it a suitable first parameter to * {@link ExecutorService}.
* {@link AsyncSequenceWithoutTemp#then(ExecutorService, AsyncSequenceActionRuns)} and similar.
*/ */
public abstract class SwingExecutorService extends AbstractExecutorService { public abstract class SwingExecutorService extends AbstractExecutorService {
public static final SwingExecutorService LATER = new SwingExecutorService() { public static final SwingExecutorService LATER = new SwingExecutorService() {
@ -74,5 +71,4 @@ public abstract class SwingExecutorService extends AbstractExecutorService {
public boolean awaitTermination(long timeout, TimeUnit unit) { public boolean awaitTermination(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View file

@ -1,344 +0,0 @@
/* ###
* 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.async;
import java.util.*;
import java.util.concurrent.Future;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.async.seq.AsyncSequenceWithoutTemp;
/**
* An interface for type specification in sequences
*
* This is just fodder for Java's generic type system. Sometimes it is not intelligent enough to
* resolve a type parameter, especially when passing a lambda function. A workaround, albeit
* hackish, is to add an unused argument to aid resolution. Take for example, the utility method
* {@link AsyncUtils#sequence(TypeSpec)}. It returns a {@link AsyncSequenceWithoutTemp}{@code <R>}.
* Were it not for the argument {@code TypeSpec<R> type}, Java could only resolve {@code <R>} by
* assigning the sequence to a temporary variable. This would require an extra line for the
* assignment, including the full specification of the type, e.g.:
*
* <pre>
* AsyncSequenceWithoutTemp<Integer> s = sequence().then((seq) -> {
* // Do stuff
* });
* return s.asCompletableFuture();
* </pre>
*
* However, this makes the definition exceedingly verbose, and it exposes the implementation
* details. While the unused argument is a nuisance, it is preferred to the above alternative. Thus,
* the static methods in this class seek to ease obtaining an appropriate {@code TypeSpec}. Several
* primitive and common types are pre-defined.
*
* This interface is not meant to be implemented by any classes or extended by other interfaces. The
* runtime value of a {@code TypeSpec} argument is always {@link #RAW}. The arguments only serve a
* purpose at compile time.
*
* TODO: Look at TypeLiteral instead....
*
* @param <U> the type of this specification
*/
public interface TypeSpec<U> {
@SuppressWarnings("rawtypes")
public static final TypeSpec RAW = new TypeSpec() {
/*
* Nothing to put here. This one instance will just be cast to satisfy the compiler. I wish
* this didn't blow runtime cycles.
*/
};
public static final TypeSpec<Object> OBJECT = auto();
public static final TypeSpec<Boolean> BOOLEAN = auto();
public static final TypeSpec<Byte> BYTE = auto();
public static final TypeSpec<Character> CHAR = auto();
public static final TypeSpec<Short> SHORT = auto();
public static final TypeSpec<Integer> INT = auto();
public static final TypeSpec<Long> LONG = auto();
public static final TypeSpec<String> STRING = auto();
public static final TypeSpec<Void> VOID = auto();
public static final TypeSpec<byte[]> BYTE_ARRAY = auto();
/**
* Obtain the most concrete type specifier suitable in the context
*
* This is a sort of syntactic filler to satisfy Java's type checker while carrying useful type
* information from action to action of an asynchronous sequence. This method is likely
* preferred for all cases. The cases where this is not used are equivalent to the explicit use
* of type annotations in normal synchronous programming. Either the programmer would like to
* ensure an intermediate result indeed has the given type, or the programmer would like to
* ascribe a more abstract type to the result.
*
* NOTE: For some compilers, this doesn't work in all contexts. It tends to work in Eclipse, but
* not in Gradle when used with directly
* {@link AsyncSequenceWithoutTemp#then(java.util.concurrent.Executor, ghidra.async.seq.AsyncSequenceActionProduces, TypeSpec)}.
* Not sure what compiler option(s) are causing the difference.
*
* @return
*/
@SuppressWarnings({ "unchecked" })
public static <T> TypeSpec<T> auto() {
return RAW;
}
public static <T> TypeSpec<T> from(Future<T> future) {
return auto();
}
/**
* Obtain a type specifier of a given raw class type
*
* @param cls the type of the producer
* @return the specifier
*/
public static <U> TypeSpec<U> cls(Class<U> cls) {
return auto();
}
/**
* Obtain a type specifier of a type given by an example
*
* @param example the example having the desired type, often {@code null} cast to the type
* @return the specifier
*/
public static <U> TypeSpec<U> obj(U example) {
return auto();
}
/**
* Obtain a type specifier for a collection of this type
*
* @return the collection specifier
*/
public default <C extends Collection<U>> TypeSpec<C> col() {
return auto();
}
/**
* Obtain a type specifier for a given collection type of this type
*
* @param <C> the type of collection
* @param cls the raw type of the collection
* @return the collection specifier
*/
public default <C extends Collection<U>> TypeSpec<C> col(Class<? super C> cls) {
return auto();
}
/**
* Obtain a type specifier for a set of this type
*
* @return the collection specifier
*/
public default <C extends Set<U>> TypeSpec<C> set() {
return auto();
}
/**
* Obtain a type specifier for a list of this type
*
* @return the collection specifier
*/
public default <C extends List<U>> TypeSpec<C> list() {
return auto();
}
/**
* Obtain a type specifier for a list of this type
*
* @return the collection specifier
*/
/*public default <C extends List<? extends U>> TypeSpec<C> listExt() {
return auto();
}*/
/**
* Object a type specifier which allows extensions of this type
*
* @return the "extends" type specifier
*/
public default TypeSpec<? extends U> ext() {
return auto();
}
/**
* Obtain a type specifier for a map from the given type to this type
*
* @param <K> the type of key
* @param keyType the type specifier for the keys
* @return the map type specifier
*/
public default <K> TypeSpec<Map<K, U>> mappedBy(TypeSpec<K> keyType) {
return auto();
}
/**
* Obtain a type specifier for a map from the given class to this type
*
* @param <K> the type of key
* @param keyCls the class for the keys
* @return the map type specifier
*/
public default <K> TypeSpec<Map<K, U>> mappedBy(Class<K> keyCls) {
return auto();
}
/**
* Obtain a type specifier of a map given the raw class types for keys and values
*
* @param keyCls the type for keys
* @param valCls the type for values
* @return the specifier
*/
public static <K, V> TypeSpec<Map<K, V>> map(Class<K> keyCls, Class<V> valCls) {
return auto();
}
public static <L, R> TypeSpec<Pair<L, R>> pair(TypeSpec<L> lSpec, TypeSpec<R> rSpec) {
return auto();
}
/**
* An interface for methods of 0 arguments
*
* @param <R> the return type of the method
*/
interface FuncArity0<R> {
R func();
}
/**
* An interface for methods of 1 argument
*
* @param <R> the return type of the method
* @param <P0> the type of the first parameter
*/
interface FuncArity1<R, P0> {
R func(P0 arg0);
}
/**
* An interface for methods of 2 arguments
*
* @param <R> the return type of the method
* @param <P0> the type of the first parameter
* @param <P1> the type of the second parameter
*/
interface FuncArity2<R, P0, P1> {
R func(P0 arg0, P1 arg1);
}
/**
* An interface for methods of 3 arguments
*
* @param <R> the return type of the method
* @param <P0> the type of the first parameter
* @param <P1> the type of the second parameter
* @param <P2> the type of the third parameter
*/
interface FuncArity3<R, P0, P1, P2> {
R func(P0 arg0, P1 arg1, P2 arg2);
}
/**
* An interface for methods of 4 arguments
*
* @param <R> the return type of the method
* @param <P0> the type of the first parameter
* @param <P1> the type of the second parameter
* @param <P2> the type of the third parameter
* @param <P3> the type of the fourth parameter
*/
interface FuncArity4<R, P0, P1, P2, P3> {
R func(P0 arg0, P1 arg1, P2 arg2, P3 arg3);
}
/**
* Obtain a type specifier for the result of an asynchronous method
*
* This is a shortcut for asynchronous methods whose implementations return a completable future
* from {@link AsyncUtils#sequence(TypeSpec)}, especially when the result is a complicated type.
* To work, the referenced method, usually the containing method, must return an implementation
* of {@link Future}. For example:
*
* <pre>
* public CompletableFuture<Map<String, Set<Integer>>> computeNamedSets() {
* return sequence(TypeSpec.future(this::computeNamedSets)).then((seq) -> {
* // Do computation
* }).asCompletableFuture();
* }
* </pre>
*
* This causes the sequence to have the correct type such that
* {@link AsyncSequenceWithoutTemp#finish()} returns a future having type compatible with the
* return type of the function. The referred method may take up to four parameters. Depending on
* optimizations applied by the JVM, this shortcut costs one instantiation of a method reference
* that is never used.
*
* @param func the method returning a {@link Future}
* @return the specifier
*/
public static <U> TypeSpec<U> future(FuncArity0<? extends Future<U>> func) {
return auto();
}
/**
* Obtain a type specifier for the result of an asynchronous method
*
* @see #future(FuncArity0)
* @param func the method returning a {@link Future}
* @return the specifier
*/
public static <U, P0> TypeSpec<U> future(FuncArity1<? extends Future<U>, P0> func) {
return auto();
}
/**
* Obtain a type specifier for the result of an asynchronous method
*
* @see #future(FuncArity0)
* @param func the method returning a {@link Future}
* @return the specifier
*/
public static <U, P0, P1> TypeSpec<U> future(FuncArity2<? extends Future<U>, P0, P1> func) {
return auto();
}
/**
* Obtain a type specifier for the result of an asynchronous method
*
* @see #future(FuncArity0)
* @param func the method returning a {@link Future}
* @return the specifier
*/
public static <U, P0, P1, P2> TypeSpec<U> future(
FuncArity3<? extends Future<U>, P0, P1, P2> func) {
return auto();
}
/**
* Obtain a type specifier for the result of an asynchronous method
*
* @see #future(FuncArity0)
* @param func the method returning a {@link Future}
* @return the specifier
*/
public static <U, P0, P1, P2, P3> TypeSpec<U> future(
FuncArity4<? extends Future<U>, P0, P1, P2, P3> func) {
return auto();
}
}

View file

@ -1,121 +0,0 @@
/* ###
* 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.async.loop;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
/**
* The underlying implementation of
* {@link AsyncUtils#loop(TypeSpec, AsyncLoopFirstActionProduces, TypeSpec, AsyncLoopSecondActionConsumes)}
*
* @param <R> the type of result for the whole loop, usually {@link TypeSpec#VOID}
* @param <T> the type of result produced by the subordinate asynchronous task
*/
public class AsyncLoop<R, T> extends CompletableFuture<R> {
private final AsyncLoopFirstActionProduces<R, T> producer;
private final AsyncLoopSecondActionConsumes<R, ? super T> consumer;
/**
* Execute the first action of the loop
*/
public void begin() {
consumeHandler.repeat(null, null);
}
/**
* The handler given to the producer action of the loop
*/
private final AsyncLoopHandlerForFirst<R, T> produceHandler =
new AsyncLoopHandlerForFirst<R, T>() {
@Override
public Void consume(T iterate, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
try {
consumer.accept(iterate, consumeHandler);
}
catch (Throwable e) {
completeExceptionally(e);
}
}
return null;
}
@Override
public Void exit(R result, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
complete(result);
}
return null;
}
};
/**
* The handler given to the consumer action of the loop
*/
private final AsyncLoopHandlerForSecond<R> consumeHandler = new AsyncLoopHandlerForSecond<R>() {
@Override
public Void repeat(Void v, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
// This is a hack to avoid stack overflows
AsyncUtils.FRAMEWORK_EXECUTOR.submit(() -> {
try {
producer.accept(produceHandler);
}
catch (Throwable e) {
completeExceptionally(e);
}
});
}
return null;
}
@Override
public Void exit(R result, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
complete(result);
}
return null;
}
};
/**
* Construct a loop with the given producer and consumer
*
* @param producer the producer (first) action
* @param type the type of object passed from producer to consumer
* @param consumer the consumer (second) action
*/
public AsyncLoop(AsyncLoopFirstActionProduces<R, T> producer, TypeSpec<T> type,
AsyncLoopSecondActionConsumes<R, ? super T> consumer) {
this.producer = producer;
this.consumer = consumer;
}
}

View file

@ -1,36 +0,0 @@
/* ###
* 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.async.loop;
import java.util.function.BiConsumer;
import ghidra.async.AsyncUtils;
/**
* The interface for the first action of an iterator-controlled loop
*
* @see AsyncUtils#each(ghidra.async.TypeSpec, java.util.Iterator,
* AsyncLoopFirstActionConsumesAndProduces, ghidra.async.TypeSpec,
* AsyncLoopSecondActionConsumes)
*
* @param <R> the type of result for the whole loop
* @param <E> the type of object consumed, i.e., provided by the controlling iterator
* @param <T> the type of object produced, i.e., provided by the subordinate asynchronous task
*/
public interface AsyncLoopFirstActionConsumesAndProduces<R, E, T>
extends BiConsumer<E, AsyncLoopHandlerForFirst<R, T>> {
// Nothing
}

View file

@ -1,34 +0,0 @@
/* ###
* 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.async.loop;
import java.util.function.Consumer;
import ghidra.async.AsyncUtils;
/**
* The interface for the first action of a plain loop
*
* @see AsyncUtils#loop(ghidra.async.TypeSpec, AsyncLoopFirstActionProduces,
* ghidra.async.TypeSpec, AsyncLoopSecondActionConsumes)
*
* @param <R> the type of result for the whole loop
* @param <T> the type of result produced, i.e., provided by the subordinate asynchronous task
*/
public interface AsyncLoopFirstActionProduces<R, T>
extends Consumer<AsyncLoopHandlerForFirst<R, T>> {
// Nothing
}

View file

@ -1,48 +0,0 @@
/* ###
* 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.async.loop;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import ghidra.async.*;
/**
* The handler given to the first action of a two-action loop
*
* @param <R> the type of result for the whole loop
* @param <T> the type of object produced, i.e., provided by the subordinate asynchronous task
*/
public interface AsyncLoopHandlerForFirst<R, T> extends AsyncHandlerCanExit<R> {
/**
* Execute the consumer action or complete the loop exceptionally
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)}. This should rarely if ever be invoked directly
* since it is most often used to handle future completion of a subordinate asynchronous task.
* If it is invoked directly, consider using a single-action loop, i.e.,
* {@link AsyncUtils#loop(TypeSpec, AsyncLoopOnlyActionRuns)} or
* {@link AsyncUtils#each(TypeSpec, Iterator, AsyncLoopSecondActionConsumes)}.
*
* If the subordinate completes without exception, the consumer is executed with the result. If
* it completes exceptionally, then the whole loop completes exceptionally and terminates.
*
* @param elemResult the result of completion
* @param exc the exception if completed exceptionally
* @return null
*/
public Void consume(T elemResult, Throwable exc);
}

View file

@ -1,107 +0,0 @@
/* ###
* 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.async.loop;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import ghidra.async.*;
/**
* The handler given to the second or only action of a loop
*
* @param <R> the type of result for the whole loop
*/
public interface AsyncLoopHandlerForSecond<R> extends AsyncHandlerCanExit<R> {
/**
* Re-execute the producer action or complete the loop exceptionally
*
* For single-action loops, this re-executes the single action
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)}. While it can be invoked directly, consider the
* convenience method {@link #repeat()}.
*
* If the subordinate completes without exception, the loop is repeated. If it completes
* exceptionally, then the whole loop completes exceptionally and terminates.
*
* @param v null placeholder for {@link Void}
* @param exc the exception if completed exceptionally
* @return null
*/
public Void repeat(Void v, Throwable exc);
/**
* Re-execute the producer action or exit conditionally, or complete exceptionally
*
* For single-action loops, this re-executes the single action
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)}. While it can be invoked directly, consider the
* convenience method {@link #repeatWhile(boolean)}.
*
* If the subordinate completes without exception, its result value {@code b} is examined. If
* equal to {@link Boolean.TRUE}, the loop is repeated; otherwise the loop exits, i.e.,
* completes normally. If the subordinate completes exceptionally, then the whole loop completes
* exceptionally and terminates.
*
* @param b the value of the predicate
* @param exc the exception if completed exceptionally
* @return null
*/
public default Void repeatWhile(Boolean b, Throwable exc) {
return exc == null && b == Boolean.TRUE ? repeat(null, exc) : exit(null, exc);
}
/**
* Do like {@link #repeat(Void, Throwable)}, but ignore the result of a subordinate task
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)} for any type. There is no need to invoke this
* method directly. If the subordinate asynchronous task produces a result, and that result does
* not need to be processed before the loop repeats, this method must be used, since
* {@link #repeat(Void, Throwable)} requires the {@link CompletableFuture} to provide a
* {@link Void} result. If the result cannot be ignored, consider using a two-action loop, i.e.,
* {@link AsyncUtils#loop(TypeSpec, AsyncLoopFirstActionProduces, TypeSpec, AsyncLoopSecondActionConsumes)}
* or
* {@link AsyncUtils#each(TypeSpec, Iterator, AsyncLoopFirstActionConsumesAndProduces, TypeSpec, AsyncLoopSecondActionConsumes)}.
* If this is already a two-action loop, then consider nesting
* {@link AsyncUtils#sequence(TypeSpec)} in a single-action loop.
*
* @param v any value, because it is ignored
* @param exc the exception if completed exceptionally
* @return null
*/
public default Void repeatIgnore(Object v, Throwable exc) {
return repeat(null, exc);
}
/**
* Re-execute the loop
*/
public default void repeat() {
repeat(null, null);
}
/**
* Re-execute the loop conditionally
*
* @param b the value of the predicate
*/
public default void repeatWhile(boolean b) {
repeatWhile(b, null);
}
}

View file

@ -1,31 +0,0 @@
/* ###
* 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.async.loop;
import java.util.function.Consumer;
import ghidra.async.AsyncUtils;
/**
* The interface for the only action of certain loops
*
* @see AsyncUtils#loop(ghidra.async.TypeSpec, AsyncLoopSecondActionRuns)
*
* @param <R> the type of result for the whole loop
*/
public interface AsyncLoopOnlyActionRuns<R> extends Consumer<AsyncLoopHandlerForSecond<R>> {
// Nothing
}

View file

@ -1,40 +0,0 @@
/* ###
* 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.async.loop;
import java.util.function.BiConsumer;
import ghidra.async.AsyncUtils;
/**
* The interface for the second or only action of certain loops
*
* @see AsyncUtils#loop(ghidra.async.TypeSpec, AsyncLoopFirstActionProduces,
* ghidra.async.TypeSpec, AsyncLoopSecondActionConsumes)
* @see AsyncUtils#each(ghidra.async.TypeSpec, java.util.Iterator,
* AsyncLoopFirstActionConsumesAndProduces, ghidra.async.TypeSpec,
* AsyncLoopSecondActionConsumes)
* @see AsyncUtils#each(ghidra.async.TypeSpec, java.util.Iterator,
* AsyncLoopSecondActionConsumes)
*
* @param <R> the type of result for the whole loop
* @param <T> the type of object consumed, i.e., provided by the controlling iterator or the
* subordinate asynchronous task
*/
public interface AsyncLoopSecondActionConsumes<R, T>
extends BiConsumer<T, AsyncLoopHandlerForSecond<R>> {
// Nothing
}

View file

@ -1,35 +0,0 @@
/* ###
* 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.async.seq;
import java.util.function.BiConsumer;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
/**
* The interface for an action that consumes a temporary value but produces nothing
*
* @see AsyncSequenceWithTemp#then(AsyncSequenceActionConsumes)
* @see AsyncUtils#sequence(TypeSpec)
*
* @param <R> the type of result of the whole sequence
* @param <T> the type of temporary consumed, i.e., produced by the previous action
*/
public interface AsyncSequenceActionConsumes<R, T>
extends BiConsumer<T, AsyncSequenceHandlerForRunner<R>> {
// Nothing
}

View file

@ -1,38 +0,0 @@
/* ###
* 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.async.seq;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
/**
* The interface for an action that consumes and produces temporary values
*
* @see AsyncSequenceWithTemp#then(AsyncSequenceActionConsumesAndProduces, TypeSpec)
* @see AsyncSequenceWithTemp#then(AsyncSequenceActionConsumesAndProduces, AtomicReference)
* @see AsyncUtils#sequence(TypeSpec)
*
* @param <R> the type of result of the whole sequence
* @param <T> the type of temporary consumed, i.e., produced by the previous action
* @param <U> the type of temporary produced, i.e., consumed by the following action
*/
public interface AsyncSequenceActionConsumesAndProduces<R, T, U>
extends BiConsumer<T, AsyncSequenceHandlerForProducer<R, U>> {
// Nothing
}

View file

@ -1,37 +0,0 @@
/* ###
* 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.async.seq;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
/**
* The interface for an action that consumes nothing but produces a temporary value
*
* @see AsyncSequenceWithoutTemp#then(AsyncSequenceActionProduces, TypeSpec)
* @see AsyncSequenceWithoutTemp#then(AsyncSequenceActionProduces, AtomicReference)
* @see AsyncUtils#sequence(TypeSpec)
*
* @param <R> the type of result of the whole sequence
* @param <U> the type of temporary produced, i.e., consumed by the following action
*/
public interface AsyncSequenceActionProduces<R, U>
extends Consumer<AsyncSequenceHandlerForProducer<R, U>> {
// Nothing
}

View file

@ -1,33 +0,0 @@
/* ###
* 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.async.seq;
import java.util.function.Consumer;
import ghidra.async.AsyncUtils;
import ghidra.async.TypeSpec;
/**
* The interface for an action that neither consumes nor produces temporary values
*
* @see AsyncSequenceWithoutTemp#then(AsyncSequenceActionRuns)
* @see AsyncUtils#sequence(TypeSpec)
*
* @param <R> the type produced by the whole sequence
*/
public interface AsyncSequenceActionRuns<R> extends Consumer<AsyncSequenceHandlerForRunner<R>> {
// Nothing
}

View file

@ -1,49 +0,0 @@
/* ###
* 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.async.seq;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import ghidra.async.AsyncHandlerCanExit;
/**
* The handler given to sequence actions that produce a temporary value
*
* @param <R> the type of result of the whole sequence
* @param <U> the type of temporary produced
*/
public interface AsyncSequenceHandlerForProducer<R, U> extends AsyncHandlerCanExit<R> {
/**
* Execute the next action or complete the sequence with null
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)}. This should rarely if ever be invoked directly
* since it is most often used to handle future completion of a subordinate asynchronous task.
* If it is invoked directly, consider merging this action with the following one. If this is
* the final action, consider using {@link #exit(Object)} instead, especially to provide a
* non-null result.
*
* If the subordinate completes without exception, the next action is executed with the result.
* If this is the final action, the sequence is completed with {@code null}. If it completes
* exceptionally, then the whole sequence completes exceptionally and terminates.
*
* @param result the result of completion, producing the temporary value
* @param exc the exception if completed exceptionally
* @return null
*/
public Void next(U result, Throwable exc);
}

View file

@ -1,64 +0,0 @@
/* ###
* 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.async.seq;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import ghidra.async.*;
public interface AsyncSequenceHandlerForRunner<R> extends AsyncHandlerCanExit<R> {
/**
* Execute the next action or complete the sequence with null
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)}. This should rarely if ever be invoked directly
* since it is most often used to handle future completion of a subordinate asynchronous task.
* If it is invoked directly, consider merging this action with the following one. If this is
* the final action, consider using {@link #exit(Object)} instead, especially to provide a
* non-null result.
*
* If the subordinate completes without exception, the next action is executed. If this is the
* final action, the sequence is completed with {@code null}. If it completes exceptionally,
* then the whole sequence completes exceptionally and terminates.
*
* @param v null placeholder for {@link Void}
* @param exc the exception if completed exceptionally
* @return null
*/
public Void next(Void v, Throwable exc);
/**
* Do like {@link #next(Void, Throwable)}, but ignore the result of a subordinate task
*
* This method is suitable for passing by reference to
* {@link CompletableFuture#handle(BiFunction)} for any type. There is not need to invoke this
* method directly. If the subordinate asynchronous task produces a result, and that result does
* not need to be consumed by the following action, this method must be used, since
* {@link #next(Void, Throwable)} requires the {@link CompletableFuture} to provide a
* {@link Void} result. If the result cannot be ignored, then the following action must accept
* the result, or the result must be stored in an {@link AtomicReference}. See
* {@link AsyncUtils#sequence(TypeSpec)}.
*
* @param result the result to ignore
* @param exc the exception if completed exceptionally
* @return null
*/
public default Void nextIgnore(Object result, Throwable exc) {
return next(null, exc);
}
}

View file

@ -1,307 +0,0 @@
/* ###
* 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.async.seq;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import ghidra.async.*;
/**
* Part of the underlying implementation of {@link AsyncUtils#sequence(TypeSpec)}
*
* @param <R> the type of result for the whole sequence
* @param <T> the type of temporary value produced by the current final action
*/
public class AsyncSequenceWithTemp<R, T> {
/**
* Common handler implementation
*
* @param <R> the type of result for the shole sequence
* @param <T> the type of temporary value produced
*/
static abstract class AbstractHandler<R, T> implements AsyncHandlerCanExit<R> {
final CompletableFuture<R> seqResult;
final CompletableFuture<T> future = new CompletableFuture<>();
public AbstractHandler(CompletableFuture<R> seqResult) {
this.seqResult = seqResult;
}
@Override
public Void exit(R result, Throwable exc) {
if (exc != null) {
seqResult.completeExceptionally(exc);
}
else {
seqResult.complete(result);
}
return null;
}
}
/**
* The handler given to actions that produce or store a temporary value
*
* @param <R> the type of result for the whole sequence
* @param <T> the type of value produced or stored
* @param <U> the type of temporary value produced -- {@link Void} for actions that store
*/
static abstract class AbstractHandlerForProducer<R, T, U> extends AbstractHandler<R, U>
implements AsyncSequenceHandlerForProducer<R, T> {
public AbstractHandlerForProducer(CompletableFuture<R> seqResult) {
super(seqResult);
}
@Override
public Void next(T result, Throwable exc) {
if (exc != null) {
seqResult.completeExceptionally(exc);
}
else {
proceedWithoutException(result);
}
return null;
}
/**
* Implements the portion of {@link #next(Object, Throwable)} to execute when the result of
* the subordinate task is not exceptional
*
* @param result the result of the subordinate task
*/
protected abstract void proceedWithoutException(T result);
}
/**
* The handler given to actions that produce a temporary value
*
* @param <R> the type of result for the shole sequence
* @param <T> the type of temporary value produced
*/
static class HandlerForProducer<R, T> extends AbstractHandlerForProducer<R, T, T> {
public HandlerForProducer(CompletableFuture<R> seqResult) {
super(seqResult);
}
@Override
public void proceedWithoutException(T futureResult) {
future.complete(futureResult);
}
}
/**
* The handler given to actions that store a value
*
* @param <R> the type of result for the shole sequence
* @param <T> the type of value stored
*/
static class HandlerForStorer<R, T> extends AbstractHandlerForProducer<R, T, Void> {
private final AtomicReference<T> storage;
public HandlerForStorer(CompletableFuture<R> seqResult, AtomicReference<T> storage) {
super(seqResult);
this.storage = storage;
}
@Override
public void proceedWithoutException(T futureResult) {
storage.set(futureResult);
future.complete(null);
}
}
/**
* The handler given to actions that do not produce a value
*
* @param <R> the type of result for the whole sequence
*/
static class HandlerForRunner<R> extends AbstractHandler<R, Void>
implements AsyncSequenceHandlerForRunner<R> {
public HandlerForRunner(CompletableFuture<R> seqResult) {
super(seqResult);
}
@Override
public Void next(Void result, Throwable exc) {
if (exc != null) {
seqResult.completeExceptionally(exc);
}
else {
future.complete(result);
}
return null;
}
}
// The temporary result
private final CompletableFuture<T> tmpResult;
// The result for the whole sequence
private final CompletableFuture<R> seqResult;
AsyncSequenceWithTemp(CompletableFuture<R> seqResult, CompletableFuture<T> tmpResult) {
this.seqResult = seqResult;
this.tmpResult = tmpResult;
}
/**
* Append to this sequence an action that consumes the temporary value and produces another
*
* @param action the action
* @param type the type of temporary value the action will produce
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithTemp<R, U> then(
AsyncSequenceActionConsumesAndProduces<R, T, U> action, TypeSpec<U> type) {
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenCompose((result) -> {
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
try {
action.accept(result, handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}));
}
/**
* Append to this sequence an action that consumes the temporary value and produces another
*
* @param executor the thread pool for this action
* @param action the action
* @param type the type of temporary value the action will produce
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithTemp<R, U> then(Executor executor,
AsyncSequenceActionConsumesAndProduces<R, T, U> action, TypeSpec<U> type) {
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
try {
action.accept(result, handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}, executor));
}
/**
* Append to this sequence an action that consumes the temporary value and stores another
*
* @param action the action
* @param storage a reference to receive the result upon completion
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithoutTemp<R> then(
AsyncSequenceActionConsumesAndProduces<R, T, U> action, AtomicReference<U> storage) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
try {
action.accept(result, handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}));
}
/**
* Append to this sequence an action that consumes the temporary value and stores another
*
* @param executor the thread pool for this action
* @param action the action
* @param storage a reference to receive the result upon completion
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithoutTemp<R> then(Executor executor,
AsyncSequenceActionConsumesAndProduces<R, T, U> action, AtomicReference<U> storage) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
try {
action.accept(result, handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}, executor));
}
/**
* Append to this sequence an action that consumes the temporary value
*
* @param action the action
* @return the new sequence with the appended action
*/
public AsyncSequenceWithoutTemp<R> then(AsyncSequenceActionConsumes<R, T> action) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
try {
action.accept(result, handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}));
}
/**
* Append to this sequence an action that consumes the temporary value
*
* @param executor the thread pool for this action
* @param action the action
* @return the new sequence with the appended action
*/
public AsyncSequenceWithoutTemp<R> then(Executor executor,
AsyncSequenceActionConsumes<R, T> action) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
try {
action.accept(result, handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}, executor));
}
/**
* Register an action to execute on sequence completion
*
* @see AsyncSequenceWithoutTemp#onExit(BiConsumer)
* @param action the action to execute
*/
public AsyncSequenceWithTemp<R, T> onExit(BiConsumer<? super R, Throwable> action) {
seqResult.handle((result, exc) -> {
action.accept(result, exc);
return result;
});
return this;
}
}

View file

@ -1,228 +0,0 @@
/* ###
* 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.async.seq;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import ghidra.async.*;
import ghidra.async.seq.AsyncSequenceWithTemp.*;
import ghidra.util.Msg;
/**
* Part of the underlying implementation of {@link AsyncUtils#sequence(TypeSpec)}
*
* @param <R> the type of result for the whole sequence
*/
public class AsyncSequenceWithoutTemp<R> {
// The temporary "result" -- will be null, but notified upon completion
private final CompletableFuture<Void> tmpResult;
// The result for the whole sequence
private final CompletableFuture<R> seqResult;
/**
* Construct a new sequence without a temporary value
*
* <p>
* Do not call this directly. Please use {@link AsyncUtils#sequence(TypeSpec)}.
*
* @param seqResult the result of the whole sequence, passed to each appended sequence
* @param tmpResult the result of the current final action
*/
public AsyncSequenceWithoutTemp(CompletableFuture<R> seqResult,
CompletableFuture<Void> tmpResult) {
this.seqResult = seqResult;
this.tmpResult = tmpResult;
}
/**
* Append an action to this sequence that produces a temporary value
*
* @param action the action
* @param type the type of temporary value that action will produce
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithTemp<R, U> then(AsyncSequenceActionProduces<R, U> action,
TypeSpec<U> type) {
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenCompose((result) -> {
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
try {
action.accept(handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}));
}
/**
* Append an action to this sequence that produces a temporary value
*
* @param action the action
* @param type the type of temporary value that action will produce
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithTemp<R, U> then(Executor executor,
AsyncSequenceActionProduces<R, U> action, TypeSpec<U> type) {
return new AsyncSequenceWithTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
HandlerForProducer<R, U> handler = new HandlerForProducer<>(seqResult);
try {
action.accept(handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}, executor));
}
/**
* Append an action to this sequence that stores a value
*
* @param action the action
* @param storage a reference to receive the result upon completion
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithoutTemp<R> then(AsyncSequenceActionProduces<R, U> action,
AtomicReference<U> storage) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
try {
action.accept(handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}));
}
/**
* Append an action to this sequence that stores a value
*
* @param action the action
* @param storage a reference to receive the result upon completion
* @return the new sequence with the appended action
*/
public <U> AsyncSequenceWithoutTemp<R> then(Executor executor,
AsyncSequenceActionProduces<R, U> action, AtomicReference<U> storage) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
HandlerForStorer<R, U> handler = new HandlerForStorer<>(seqResult, storage);
try {
action.accept(handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}, executor));
}
/**
* Append an action to this sequence
*
* @param action the action
* @return the new sequence with the appended action
*/
public AsyncSequenceWithoutTemp<R> then(AsyncSequenceActionRuns<R> action) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenCompose((result) -> {
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
try {
action.accept(handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}));
}
/**
* Append an action to this sequence
*
* @param action the action
* @return the new sequence with the appended action
*/
public AsyncSequenceWithoutTemp<R> then(Executor executor, AsyncSequenceActionRuns<R> action) {
return new AsyncSequenceWithoutTemp<>(seqResult, tmpResult.thenComposeAsync((result) -> {
HandlerForRunner<R> handler = new HandlerForRunner<>(seqResult);
try {
action.accept(handler);
}
catch (Throwable e) {
seqResult.completeExceptionally(e);
throw e;
}
return handler.future;
}, executor));
}
/**
* Finish defining this sequence of actions and obtain its future result
*
* <p>
* When an action in the sequence calls {@link AsyncHandlerCanExit#exit(Object, Throwable)}, the
* returned {@link CompletableFuture} is completed. If any action completes exceptionally, the
* returned {@link CompletableFuture} is completed exceptionally. If the final action executes,
* {@link AsyncSequenceHandlerForRunner#next(Void, Throwable)}, the returned
* {@link CompletableFuture} is completed with {@code null}.
*
* @return the future result of the sequence
*/
public CompletableFuture<R> finish() {
return then((seq) -> {
seq.exit(null, null);
}).seqResult;
}
/**
* Register an action to execute on sequence completion
*
* <p>
* All registered actions are submitted for execution simultaneously when an action in the
* sequence calls {@link AsyncHandlerCanExit#exit(Object, Throwable)}. This is useful for
* methods that begin executing sequences "with a context". It is roughly equivalent to a
* {@code finally} block. On-exit actions can be registered before other actions are appended to
* the chain.
*
* <p>
* If the sequence completes exceptionally, that exception is passed to the action. This method
* cannot be used to handle the exception, since this method returns this same sequence, not the
* resulting one. An uncaught exception in an on-exit action will simply be logged and ignored.
*
* @param action the action to execute
*/
public AsyncSequenceWithoutTemp<R> onExit(BiConsumer<? super R, Throwable> action) {
seqResult.handle((result, exc) -> {
try {
action.accept(result, exc);
}
catch (Throwable t) {
Msg.error(this, "Uncaught exception in onExit", t);
}
return result;
});
return this;
}
}

View file

@ -1,102 +0,0 @@
/* ###
* 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.comm.packet;
import static ghidra.async.AsyncUtils.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousByteChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import ghidra.async.TypeSpec;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
/**
* A wrapper for an {@link AsynchronousByteChannel} that logs the transferred data.
*/
public class DebugByteChannel implements AsynchronousByteChannel {
private final AsynchronousByteChannel wrapped;
/**
* Wrap the given channel
*
* @param wrapped the channel to wrap
*/
public DebugByteChannel(AsynchronousByteChannel wrapped) {
this.wrapped = wrapped;
}
@Override
public void close() throws IOException {
wrapped.close();
}
@Override
public boolean isOpen() {
return wrapped.isOpen();
}
@Override
public <A> void read(ByteBuffer dst, A attachment,
CompletionHandler<Integer, ? super A> handler) {
int start = dst.position();
CompletableFuture<Integer> future = sequence(TypeSpec.INT).then((seq) -> {
completable(TypeSpec.INT, wrapped::read, dst).handle(seq::next);
}, TypeSpec.INT).then((len, seq) -> {
if (len == -1) {
Msg.debug(this, "Read EOF");
}
else {
byte[] data = new byte[len];
dst.position(start);
dst.get(data);
Msg.debug(this, "Read: " + NumericUtilities.convertBytesToString(data));
}
seq.exit(len, null);
}).finish();
handle(future, attachment, handler);
}
@Override
public Future<Integer> read(ByteBuffer dst) {
throw new UnsupportedOperationException();
}
@Override
public <A> void write(ByteBuffer src, A attachment,
CompletionHandler<Integer, ? super A> handler) {
int start = src.position();
CompletableFuture<Integer> future = sequence(TypeSpec.INT).then((seq) -> {
completable(TypeSpec.INT, wrapped::write, src).handle(seq::next);
}, TypeSpec.INT).then((len, seq) -> {
byte[] data = new byte[len];
src.position(start);
src.get(data);
Msg.debug(this, "Wrote: " + NumericUtilities.convertBytesToString(data));
seq.exit(len, null);
}).finish();
handle(future, attachment, handler);
}
@Override
public Future<Integer> write(ByteBuffer src) {
throw new UnsupportedOperationException();
}
}

View file

@ -1,49 +0,0 @@
/* ###
* 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.comm.service;
import java.io.IOException;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractAsyncClientHandler<S extends AbstractAsyncServer<S, H>, H extends AbstractAsyncClientHandler<S, H>> {
protected final S server;
protected final AsynchronousSocketChannel sock;
public AbstractAsyncClientHandler(S server, AsynchronousSocketChannel sock) {
this.server = server;
this.sock = sock;
}
/**
* Close the client connection
*
* @throws IOException if an I/O error occurs
*/
protected void close() throws IOException {
sock.close();
}
/**
* Start the request processing loop
*
* The loop executes until the client connection is lost, at which point the future must be
* completed. The server will automatically remove the handler when the future completes.
*
* @return a future which completes when the processing loop terminates
*/
protected abstract CompletableFuture<Void> launchAsync();
}

View file

@ -1,151 +0,0 @@
/* ###
* 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.comm.service;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.*;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.*;
import java.util.function.Function;
import ghidra.async.*;
import ghidra.util.Msg;
public abstract class AbstractAsyncServer<S extends AbstractAsyncServer<S, H>, H extends AbstractAsyncClientHandler<S, H>> {
// One thread for all clients... OK since generally only one is connected.
private final AsynchronousChannelGroup group =
AsynchronousChannelGroup.withThreadPool(Executors.newSingleThreadExecutor());
private final AsynchronousServerSocketChannel ssock;
//private final SocketAddress addr;
protected final Set<H> handlers = Collections.newSetFromMap(new ConcurrentHashMap<>());
public AbstractAsyncServer(SocketAddress addr) throws IOException {
//this.addr = addr;
ssock = AsynchronousServerSocketChannel.open(group);
ssock.bind(addr);
}
private CompletableFuture<AsynchronousSocketChannel> accept() {
return AsyncUtils.completable(TypeSpec.cls(AsynchronousSocketChannel.class), ssock::accept);
}
protected abstract boolean checkAcceptable(AsynchronousSocketChannel sock);
/**
* Create a new handler for the given incoming client connection
*
* @param sock the new client connection
* @return a handler for the client
*/
protected abstract H newHandler(AsynchronousSocketChannel sock);
protected void removeHandler(H handler) {
handlers.remove(handler);
}
public CompletableFuture<Void> launchAsyncService() {
return AsyncUtils.loop(TypeSpec.VOID, loop -> {
if (ssock.isOpen()) {
accept().handle(loop::consume);
}
else {
loop.exit();
}
}, TypeSpec.cls(AsynchronousSocketChannel.class), (sock, loop) -> {
loop.repeat();
if (!checkAcceptable(sock)) {
try {
sock.close();
}
catch (IOException e) {
Msg.error(this, "Failed to close rejected connection", e);
}
}
else {
H handler = newHandler(sock);
handlers.add(handler);
handler.launchAsync().thenAccept(v -> {
removeHandler(handler);
}).exceptionally(e -> {
Msg.error("Client handler terminated unexpectedly", e);
removeHandler(handler);
return null;
});
}
});
}
public SocketAddress getLocalAddress() {
try {
return ssock.getLocalAddress();
}
catch (IOException e) {
throw new AssertionError(e);
}
}
/**
* Close the server socket and all current client connections
*
* @throws IOException if an I/O error occurs
*/
public void terminate() throws IOException {
IOException err = null;
try {
ssock.close();
}
catch (IOException e) {
err = e;
}
for (H h : handlers) {
try {
h.close();
}
catch (IOException e) {
if (err == null) {
err = e;
}
}
}
group.shutdown();
if (err != null) {
throw err;
}
}
/**
* Accept no more connections, but continue servicing existing connections
*
* @throws IOException if an I/O error occurs
*/
protected void closeServerSocket() throws IOException {
ssock.close();
}
protected CompletableFuture<Void> allHandlers(Function<H, CompletableFuture<?>> action) {
AsyncFence fence = new AsyncFence();
for (H h : handlers) {
CompletableFuture<?> future = action.apply(h);
if (future != null) {
fence.include(future);
}
}
return fence.ready();
}
}

View file

@ -1,415 +0,0 @@
/* ###
* 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.comm.util;
import java.util.*;
import org.apache.commons.collections4.IteratorUtils;
/**
* A set of enumeration constants encoded using bits
*
* All constants in the set must belong to the same enumeration called the "universe." The
* enumeration must implement {@link BitmaskUniverse}, so that each constant provides its mask bit.
* This is essentially a "set" abstraction on the idiom of using "flag" bits to represent the
* present or absence of each element.
*
* It is highly recommended that each constant's mask have a population of one bit. At the very
* least, each should have one unique bit. Constants which represent combinations of other flags are
* allowed, but they should be documented. Defining such combinations may produce surprising
* behavior from the perspective of the {@link Set} interface. For instance, consider constants
* {@code VAL_A}, {@code VAL_B}, and {@code VALS_A_B}. Adding {@code VAL_A} will cause no surprise,
* but subsequently adding {@code VAL_B} will cause {@code VALS_A_B} to materialize, even though it
* was never added explicitly. So long as the calling methods do not expect strict set behavior,
* this is OK. If there exists a constant which defines zero flags, then the behavior is undefined.
* In general, the element will always be present, even though {@link #isEmpty()} may return
* {@code true}.
*
* @param <E> the type of enumeration constant elements
*/
public class BitmaskSet<E extends Enum<E> & BitmaskUniverse> implements Set<E> {
/**
* Obtain a set of the given constants
*
* @param elements the constants, all from the same enumeration
* @return the set
*/
@SafeVarargs
public static <E extends Enum<E> & BitmaskUniverse> BitmaskSet<E> of(E... elements) {
long bitmask = 0;
for (E elem : elements) {
bitmask |= elem.getMask();
}
@SuppressWarnings("unchecked")
Class<E> universe = (Class<E>) elements.getClass().getComponentType();
return new BitmaskSet<>(universe, bitmask);
}
private final Class<E> universe;
private long bitmask;
/**
* Decode a set of constants from the given universe using the given bitmask
*
* @param universe the enumeration of constants the set may contain
* @param bitmask the bitmask to decode
*/
public BitmaskSet(Class<E> universe, long bitmask) {
this.universe = universe;
this.bitmask = bitmask;
}
/**
* Copy the given collection as a bitmask of constants
*
* @param universe the enumeration of constants the set may contain
* @param collection the collection to copy
*/
public BitmaskSet(Class<E> universe, Collection<E> collection) {
this.universe = universe;
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(collection);
if (that != null) {
this.bitmask = that.bitmask;
return;
}
// Otherwise, do it the long way...
if (collection.isEmpty()) {
return;
}
for (E elem : collection) {
bitmask |= elem.getMask();
}
}
/**
* Copy the given bitmask set
*
* @param that the other set
*/
public BitmaskSet(BitmaskSet<E> that) {
this.universe = that.universe;
this.bitmask = that.bitmask;
}
/**
* Create an empty set
*
* @param universe the enumeration of constants the set may contain
*/
public BitmaskSet(Class<E> universe) {
this.universe = universe;
}
/**
* Check if a constant is in the set
*
* @param elem the constant
* @return {@code true} if it is present, {@code false} otherwise
*/
protected boolean containsImpl(E elem) {
long mask = elem.getMask();
return (mask & bitmask) == mask;
}
/**
* Remove a constant from the set
*
* @param elem the constant to remove
* @return {@code true} if it was present and removed, {@code false} if already not present
*/
protected boolean removeImpl(E elem) {
long old = bitmask;
bitmask &= ~elem.getMask();
return old != bitmask;
}
/**
* Attempt to cast the given collection as a bitmask of the same type of elements as this
*
* @param c the collection to cast
* @return the same collection, or {@code null} if the collection or element types differ
*/
protected BitmaskSet<E> castSameType(Collection<?> c) {
if (!(c instanceof BitmaskSet)) {
return null;
}
BitmaskSet<?> bm = (BitmaskSet<?>) c;
if (this.universe != bm.universe) {
return null;
}
@SuppressWarnings("unchecked")
BitmaskSet<E> bme = (BitmaskSet<E>) bm;
return bme;
}
@Override
public int size() {
int count = 0;
for (E elem : universe.getEnumConstants()) {
if (containsImpl(elem)) {
count++;
}
}
return count;
}
@Override
public boolean isEmpty() {
return bitmask == 0;
}
@Override
public boolean contains(Object o) {
if (universe != o.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
E elem = (E) o;
return containsImpl(elem);
}
@Override
public Iterator<E> iterator() {
List<E> all = Arrays.asList(universe.getEnumConstants());
return IteratorUtils.filteredIterator(all.iterator(), this::containsImpl);
}
@Override
public Object[] toArray() {
return toArray(new Object[] {});
}
@Override
public <T> T[] toArray(T[] a) {
List<Object> arr = new ArrayList<>();
for (E elem : universe.getEnumConstants()) {
if (containsImpl(elem)) {
arr.add(elem);
}
}
return arr.toArray(a);
}
@Override
public boolean add(E elem) {
long old = bitmask;
bitmask |= elem.getMask();
return old != bitmask;
}
@Override
public boolean remove(Object o) {
if (universe != o.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
E elem = (E) o;
return removeImpl(elem);
}
@Override
public boolean containsAll(Collection<?> c) {
if (c.isEmpty()) {
return true;
}
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(c);
if (that != null) {
return (this.bitmask | that.bitmask) == this.bitmask;
}
// Otherwise, do it the long way...
for (Object o : c) {
if (!contains(o)) {
return false;
}
}
return true;
}
public boolean containsAny(Collection<?> c) {
if (c.isEmpty()) {
return false;
}
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(c);
if (that != null) {
return (this.bitmask & that.bitmask) != 0;
}
// Otherwise, do it the long way...
for (Object o : c) {
if (contains(o)) {
return true;
}
}
return false;
}
@Override
public boolean addAll(Collection<? extends E> c) {
long old = this.bitmask;
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(c);
if (that != null) {
this.bitmask |= that.bitmask;
return old != this.bitmask;
}
if (c.isEmpty()) {
return false;
}
// Otherwise, do it the long way...
for (E elem : c) {
this.bitmask |= elem.getMask();
}
return old != this.bitmask;
}
@Override
public boolean retainAll(Collection<?> c) {
long old = this.bitmask;
if (c.isEmpty()) {
this.bitmask = 0;
return old != this.bitmask;
}
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(c);
if (that != null) {
this.bitmask &= that.bitmask;
return old != this.bitmask;
}
// Otherwise, do it the long way... Build the bitmask manually
long toKeep = 0;
for (Object o : c) {
if (universe == o.getClass()) {
@SuppressWarnings("unchecked")
E elem = (E) o;
toKeep |= elem.getMask();
}
}
this.bitmask &= toKeep;
return old != this.bitmask;
}
@Override
public boolean removeAll(Collection<?> c) {
if (c.isEmpty()) {
return false;
}
long old = this.bitmask;
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(c);
if (that != null) {
this.bitmask &= ~that.bitmask;
return old != this.bitmask;
}
// Otherwise, do it the long way... Build the bitmask manually
long toRemove = 0;
for (Object o : c) {
if (universe == o.getClass()) {
@SuppressWarnings("unchecked")
E elem = (E) o;
toRemove |= elem.getMask();
}
}
this.bitmask &= ~toRemove;
return old != this.bitmask;
}
@Override
public void clear() {
this.bitmask = 0;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Collection)) {
return false;
}
Collection<?> col = (Collection<?>) obj;
// Use bitmasking shortcut, if possible
BitmaskSet<E> that = castSameType(col);
if (that != null) {
return this.bitmask == that.bitmask;
}
// Otherwise, do it the long way...
return this.containsAll(col) && col.containsAll(this);
}
@Override
public int hashCode() {
return Long.hashCode(bitmask);
}
/**
* Obtain the encoded bitmask
*
* @return the bitmask
*/
public long getBitmask() {
return bitmask;
}
/**
* Decode the given bitmask, overwriting the value of this set
*
* @param bitmask the bitmask to decode
*/
public void setBitmask(long bitmask) {
this.bitmask = bitmask;
}
/**
* Get this set's universe
*
* @return the enumeration representing the universe
*/
public Class<E> getUniverse() {
return universe;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (E elem : this) {
if (first) {
first = false;
}
else {
sb.append(", ");
}
sb.append(elem);
}
sb.append(']');
return sb.toString();
}
}

View file

@ -1,30 +0,0 @@
/* ###
* 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.comm.util;
/**
* The interface for enumerations usable with {@link BitmaskSet}
*/
public interface BitmaskUniverse {
/**
* Get the bit associated with this constant
*
* It ought to have a populating of one, and be unique from the other constants.
*
* @return the mask bit
*/
long getMask();
}

View file

@ -1,336 +0,0 @@
/* ###
* 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.async;
import static ghidra.async.AsyncUtils.*;
import static org.junit.Assert.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Ignore;
import org.junit.Test;
import ghidra.util.Msg;
public class AsyncLockTest {
private AsyncLock lock; // Placeholder for example
private CompletableFuture<Void> doCriticalStuff() {
// Placeholder for example
return AsyncUtils.nil();
}
private CompletableFuture<Void> doMoreCriticalStuff() {
// Placeholder for example
return AsyncUtils.nil();
}
public CompletableFuture<Integer> fetchValue() {
// Placeholder for example
return CompletableFuture.completedFuture(3);
}
public CompletableFuture<String> convertValue(int val) {
return CompletableFuture.completedFuture(Integer.toString(val));
}
public CompletableFuture<Void> exampleSeq() {
return sequence(TypeSpec.VOID).then((seq) -> {
fetchValue().handle(seq::next);
}, TypeSpec.INT).then((val, seq) -> {
convertValue(val + 10).handle(seq::next);
}, TypeSpec.STRING).then((str, seq) -> {
Msg.debug(this, str);
seq.exit();
}).finish();
}
public CompletableFuture<Void> exampleLock1() {
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
return sequence(TypeSpec.VOID).then((seq) -> {
lock.acquire(null).handle(seq::next);
}, hold).then((seq) -> {
doCriticalStuff().handle(seq::next);
}).then((seq) -> {
doMoreCriticalStuff().handle(seq::next);
}).then((seq) -> {
hold.get().release();
seq.exit();
}).finish().exceptionally((exc) -> {
hold.get().release();
return ExceptionUtils.rethrow(exc);
});
}
public CompletableFuture<Void> exampleLock2() {
return lock.with(TypeSpec.VOID, null).then((hold, seq) -> {
doCriticalStuff().handle(seq::next);
}).then((seq) -> {
doMoreCriticalStuff().handle(seq::next);
}).finish();
}
@Test
public void testWithError() throws Throwable {
AsyncLock l = new AsyncLock();
int result = l.with(TypeSpec.INT, null).then((own, seq) -> {
throw new AssertionError("Blargh");
}).finish().exceptionally(exc -> {
return 0xdead;
}).get(1000, TimeUnit.MILLISECONDS);
assertEquals(0xdead, result);
}
@Test
public void testReentry() {
// This is very contrived. A real use would pass ownership to some method which cannot
// assume that it already holds the lock
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
AsyncLock l = new AsyncLock();
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
AtomicReference<AsyncLock.Hold> hold2 = new AtomicReference<>();
List<Integer> result = new ArrayList<>();
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
result.add(1);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
l.with(TypeSpec.VOID, hold.get(), hold2).then((seq2) -> {
result.add(2);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq2::next);
}).finish().handle(seq::next);
}).then((seq) -> {
result.add(3);
seq.exit();
});
CompletableFuture<Void> future;
while (null != (future = queue.poll())) {
future.complete(null);
}
List<Integer> exp = Arrays.asList(new Integer[] { 1, 2, 3 });
assertEquals(exp, result);
}
@Test
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
public void testTwoSequencesWithLockAtomic() {
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
AsyncLock l = new AsyncLock();
List<Integer> result = new ArrayList<>();
l.with(TypeSpec.VOID, null).then((hold, seq) -> {
result.add(1);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(2);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(3);
seq.exit();
});
l.with(TypeSpec.VOID, null).then((hold, seq) -> {
result.add(4);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(5);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(6);
seq.exit();
});
CompletableFuture<Void> future;
while (null != (future = queue.poll())) {
future.complete(null);
}
List<Integer> exp = Arrays.asList(new Integer[] { 1, 2, 3, 4, 5, 6 });
assertEquals(exp, result);
}
@Test
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
public void testTwoSequencesWithReentry() {
// This is very contrived. A real use would pass ownership to some method which cannot
// assume that it already owns the lock
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
AsyncLock l = new AsyncLock();
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
AtomicReference<AsyncLock.Hold> hold2 = new AtomicReference<>();
List<Integer> result = new ArrayList<>();
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
result.add(1);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
l.with(TypeSpec.VOID, hold.get(), hold2).then((seq2) -> {
result.add(2);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq2::next);
}).finish().handle(seq::next);
}).then((seq) -> {
result.add(3);
seq.exit();
});
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
result.add(4);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
l.with(TypeSpec.VOID, hold.get(), hold2).then((seq2) -> {
result.add(5);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq2::next);
}).finish().handle(seq::next);
}).then((seq) -> {
result.add(6);
seq.exit();
});
CompletableFuture<Void> future;
while (null != (future = queue.poll())) {
future.complete(null);
}
List<Integer> exp = Arrays.asList(new Integer[] { 1, 2, 3, 4, 5, 6 });
assertEquals(exp, result);
}
@Test(expected = IllegalStateException.class)
public void testInvalidHandle() throws Throwable {
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
AsyncLock l = new AsyncLock();
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
l.with(TypeSpec.VOID, null, hold).then((seq) -> {
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
/*
* NOTE: Using seq::next here fails to release the lock, because #asCompletableFuture()
* must be called on the sequence to install the automatic call to ::exit.
*/
future.handle(seq::exit);
});
// Finish the "critical section"
queue.poll().complete(null);
try {
l.with(TypeSpec.VOID, hold.get()).then((drop, seq) -> {
seq.exit();
}).finish().getNow(null);
}
catch (CompletionException e) {
throw e.getCause();
}
}
@Test(expected = IllegalStateException.class)
public void testForgottenHandle() throws Throwable {
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
AsyncLock l = new AsyncLock();
AtomicReference<AsyncLock.Hold> hold = new AtomicReference<>();
// We have to contrive the forgotten lock, and control garbage collection
// It shouldn't matter when gc happens, but for the sake of testing, we want it soon
sequence(TypeSpec.VOID).then((seq) -> {
l.acquire(null).handle(seq::next);
}, hold).then((seq) -> {
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::exit);
});
// Finish the "critical section"
queue.poll().complete(null);
Msg.info(this, "The forgotten lock message is expected");
// Forget the lock, and wait for it to die
hold.set(null);
while (!l.dead) {
System.gc();
Thread.sleep(10);
}
try {
l.acquire(null).getNow(null);
}
catch (CompletionException e) {
throw e.getCause();
}
}
@Test
public void testThrash() throws Exception {
boolean debug = false;
// This generates copious log messages; enable only when debugging
AsyncLock l = new AsyncLock();
if (debug) {
l = new AsyncLock("testThrash Lock");
}
var noSync = new Object() {
int total = 0;
};
AsyncFence fence = new AsyncFence();
for (int i = 0; i < 10000; i++) {
final int _i = i;
fence.include(l.with(TypeSpec.VOID, null).then((hold, seq) -> {
CompletableFuture.runAsync(() -> {
if (debug) {
Msg.info(this, "i: " + _i);
Msg.info(this, "Depth: " + new Throwable().getStackTrace().length);
}
//assert noSync.total == 0;
noSync.total++;
}).handle(seq::next);
}).then(seq -> {
CompletableFuture.runAsync(() -> {
noSync.total--;
}).handle(seq::next);
}).finish());
}
fence.ready().get(5000000, TimeUnit.MILLISECONDS);
assert noSync.total == 0;
}
}

View file

@ -1,70 +0,0 @@
/* ###
* 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.async;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.CompletableFuture;
import org.junit.Test;
import ghidra.async.AsyncRace;
public class AsyncRaceTest {
@Test
public void testAlternateCompleted() {
AsyncRace<Integer> race = new AsyncRace<>();
race.include(CompletableFuture.completedFuture(1));
assertEquals(1, race.next().getNow(null).intValue());
race.include(CompletableFuture.completedFuture(2));
assertEquals(2, race.next().getNow(null).intValue());
}
@Test
public void testTwoCompleted() {
AsyncRace<Integer> race = new AsyncRace<>();
race.include(CompletableFuture.completedFuture(1));
race.include(CompletableFuture.completedFuture(2));
assertEquals(1, race.next().getNow(null).intValue());
assertEquals(2, race.next().getNow(null).intValue());
}
@Test
public void testTwoDelayed() {
AsyncRace<Integer> race = new AsyncRace<>();
CompletableFuture<Integer> c1 = new CompletableFuture<>();
CompletableFuture<Integer> c2 = new CompletableFuture<>();
race.include(c1);
race.include(c2);
c1.complete(1);
c2.complete(2);
assertEquals(1, race.next().getNow(null).intValue());
assertEquals(2, race.next().getNow(null).intValue());
}
@Test
public void testTwoDelayedReversed() {
AsyncRace<Integer> race = new AsyncRace<>();
CompletableFuture<Integer> c1 = new CompletableFuture<>();
CompletableFuture<Integer> c2 = new CompletableFuture<>();
race.include(c1);
race.include(c2);
c2.complete(2);
c1.complete(1);
assertEquals(2, race.next().getNow(null).intValue());
assertEquals(1, race.next().getNow(null).intValue());
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,12 +15,8 @@
*/ */
package ghidra.async; package ghidra.async;
import java.util.Collection;
import java.util.concurrent.*; import java.util.concurrent.*;
import ghidra.async.AsyncUtils.TemperamentalRunnable;
import ghidra.async.AsyncUtils.TemperamentalSupplier;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
public interface AsyncTestUtils { public interface AsyncTestUtils {
@ -65,47 +61,4 @@ public interface AsyncTestUtils {
}); });
return waitOnNoValidate(validated); return waitOnNoValidate(validated);
} }
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;
}
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,8 +22,6 @@ import java.util.concurrent.TimeUnit;
import org.junit.Test; import org.junit.Test;
import ghidra.async.AsyncTimer;
public class AsyncTimerTest { public class AsyncTimerTest {
@Test @Test
public void testMarkWait1000ms() throws Exception { public void testMarkWait1000ms() throws Exception {

View file

@ -1,384 +0,0 @@
/* ###
* 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.async;
import static ghidra.async.AsyncUtils.*;
import static org.junit.Assert.assertEquals;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Test;
public class AsyncUtilsTest {
@Test
public void testEach() throws Throwable {
List<Integer> list = Arrays.asList(new Integer[] { 1, 2, 4, 3 });
List<String> res = new ArrayList<>();
each(TypeSpec.VOID, list.iterator(), (e, seq) -> {
append("" + e, res).handle(seq::repeat);
}).get(1000, TimeUnit.MILLISECONDS);
List<String> exp = Arrays.asList(new String[] { "1", "2", "4", "3" });
assertEquals(exp, res);
}
// This also tests the compile-time type checking
@Test
public void testSeq() throws Throwable {
List<String> res = new ArrayList<>();
sequence(TypeSpec.VOID).then((seq) -> {
add(1, 2).handle(seq::next);
}, TypeSpec.INT).then((sum, seq) -> {
intToString(sum).handle(seq::next);
}, TypeSpec.STRING).then((str, seq) -> {
append(str, res).handle(seq::next);
}).finish().get(1000, TimeUnit.MILLISECONDS);
List<String> exp = Arrays.asList(new String[] { "3" });
assertEquals(exp, res);
}
@Test
public void testLoop() throws Throwable {
AtomicInteger count = new AtomicInteger(0);
List<Integer> res = new ArrayList<>();
long result = loop(TypeSpec.LONG, (loop) -> {
if (count.getAndIncrement() < 5) {
add(count.get(), 10).handle(loop::consume);
}
else {
loop.exit(0xdeadbeeff00dL, null);
}
}, TypeSpec.INT, (cur, loop) -> {
res.add(cur);
loop.repeat();
}).get(1000, TimeUnit.MILLISECONDS);
List<Integer> exp = Arrays.asList(new Integer[] { 11, 12, 13, 14, 15 });
assertEquals(exp, res);
assertEquals(0xdeadbeeff00dL, result);
}
@Test
public void testNesting() throws Throwable {
List<String> res = new ArrayList<>();
sequence(TypeSpec.VOID).then((seq) -> {
getListInts().handle(seq::next);
}, TypeSpec.obj((List<Integer>) null)).then((list, seq) -> {
each(TypeSpec.VOID, list.iterator(), (e, loop) -> {
intToString(e).handle(loop::consume);
}, TypeSpec.STRING, (str, loop) -> {
res.add(str);
loop.repeat();
}).handle(seq::next);
}).finish().get(1000, TimeUnit.MILLISECONDS);
List<String> exp = Arrays.asList(new String[] { "1", "2", "3" });
assertEquals(exp, res);
}
// Functions that communicate result via completion handler
protected static CompletableFuture<Void> append(String message, List<String> to) {
to.add(message);
return AsyncUtils.nil();
}
protected static CompletableFuture<Integer> add(int a, int b) {
return CompletableFuture.completedFuture(a + b);
}
protected static CompletableFuture<String> intToString(int a) {
return CompletableFuture.completedFuture(Integer.toString(a));
}
protected static CompletableFuture<List<Integer>> getListInts() {
return CompletableFuture.completedFuture(Arrays.asList(new Integer[] { 1, 2, 3 }));
}
// Some dummies to construct examples for documentation
protected static class Storage {
protected CompletableFuture<List<Integer>> fetchList() {
return getListInts();
}
protected void close() {
// Empty
}
}
protected static class Service {
protected CompletableFuture<Integer> process(int val) {
return CompletableFuture.completedFuture(val * 3);
}
protected void close() {
// Empty
}
}
protected static CompletableFuture<Storage> connectStorage(String address) {
return CompletableFuture.completedFuture(new Storage());
}
protected static CompletableFuture<Service> connectService(String address) {
return CompletableFuture.completedFuture(new Service());
}
protected static String ADDR1 = null;
protected static String ADDR2 = null;
class FetchAndProcess extends CompletableFuture<Integer> {
Storage storage;
Service service;
int sum;
FetchAndProcess(int start) {
sum = start;
connectStorage(ADDR1).handle(this::storageConnected);
}
Void storageConnected(Storage s, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
storage = s;
connectService(ADDR2).handle(this::serviceConnected);
}
return null;
}
Void serviceConnected(Service s, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
service = s;
storage.fetchList().handle(this::fetchedList);
}
return null;
}
Void fetchedList(List<Integer> list, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int entry : list) {
futures.add(service.process(entry).thenAccept((result) -> {
sum += result;
}));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]))
.handle(this::processedList);
}
return null;
}
Void processedList(Void v, Throwable exc) {
if (exc != null) {
completeExceptionally(exc);
}
else {
complete(sum);
}
return null;
}
}
public CompletableFuture<Integer> doWorkWithClass(int start) {
return new FetchAndProcess(start);
}
public CompletableFuture<Integer> doWorkWithComposition(int start) {
AtomicReference<Storage> store = new AtomicReference<>();
AtomicReference<Service> serve = new AtomicReference<>();
AtomicInteger sum = new AtomicInteger(start);
return connectStorage(ADDR1).thenCompose((s) -> {
store.set(s);
return connectService(ADDR2);
}).thenCompose((s) -> {
serve.set(s);
return store.get().fetchList();
}).thenCompose((list) -> {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int entry : list) {
futures.add(serve.get().process(entry).thenAccept((result) -> {
sum.addAndGet(result);
}));
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[list.size()]));
}).thenApply((v) -> {
store.get().close();
serve.get().close();
return sum.get();
});
}
static class ListNotFoundException extends Exception {
// Just a placeholder for an example
}
public static final List<Integer> DEFAULT_LIST = null;
public CompletableFuture<Integer> doWorkWithSeq(int start) {
AtomicReference<Storage> store = new AtomicReference<>();
AtomicReference<Service> serve = new AtomicReference<>();
AtomicInteger sum = new AtomicInteger(start);
return sequence(TypeSpec.INT).then((seq) -> {
connectStorage(ADDR1).handle(seq::next);
}, store).then((seq) -> {
connectService(ADDR2).handle(seq::next);
}, serve).then((seq) -> {
store.get().fetchList().handle(seq::next);
}, TypeSpec.obj((List<Integer>) null)).then((list, seq) -> {
AsyncFence fence = new AsyncFence();
for (int entry : list) {
fence.include(sequence(TypeSpec.VOID).then((seq2) -> {
serve.get().process(entry).handle(seq2::next);
}, TypeSpec.INT).then((result, seq2) -> {
sum.addAndGet(result);
seq2.exit();
}).finish());
}
fence.ready().handle(seq::next);
}).then((seq) -> {
store.get().close();
serve.get().close();
seq.exit(sum.get());
}).finish().exceptionally((exc) -> {
if (store.get() != null) {
store.get().close();
}
if (serve.get() != null) {
serve.get().close();
}
return ExceptionUtils.rethrow(exc);
});
}
@Test
public void testExample() throws Throwable {
assertEquals(23, doWorkWithClass(5).get(1000, TimeUnit.MILLISECONDS).intValue());
assertEquals(23, doWorkWithComposition(5).get(1000, TimeUnit.MILLISECONDS).intValue());
assertEquals(23, doWorkWithSeq(5).get(1000, TimeUnit.MILLISECONDS).intValue());
}
private CompletableFuture<byte[]> receiveData() {
// Placeholder for example
return AsyncUtils.nil();
}
private void processData(byte[] data) {
// Placeholder for example
}
public void exampleLoop1() {
loop(TypeSpec.VOID, (loop) -> {
receiveData().handle(loop::consume);
}, TypeSpec.BYTE_ARRAY, (data, loop) -> {
loop.repeat();
processData(data);
});
}
private CompletableFuture<Void> someTask() {
// Placeholder for example
return AsyncUtils.nil();
}
public void exampleLoop2() {
loop(TypeSpec.VOID, (loop) -> {
someTask().handle(loop::repeat);
});
}
private Set<Integer> mySet;
private CompletableFuture<String> sendItem() {
// Placeholder for example
return AsyncUtils.nil();
}
private void logResult(String message) {
// Placeholder for example
}
public void exampleEach1() {
each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
sendItem().handle(loop::consume);
}, TypeSpec.STRING, (message, loop) -> {
loop.repeat();
logResult(message);
});
}
public void exampleEach2() {
each(TypeSpec.VOID, mySet.iterator(), (item, loop) -> {
sendItem().handle(loop::repeatIgnore);
});
}
@Test
public void testTwoSequencesInterwoven() {
Deque<CompletableFuture<Void>> queue = new LinkedList<>();
List<Integer> result = new ArrayList<>();
sequence(TypeSpec.VOID).then((seq) -> {
result.add(1);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(2);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(3);
seq.exit();
});
sequence(TypeSpec.VOID).then((seq) -> {
result.add(4);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(5);
CompletableFuture<Void> future = new CompletableFuture<>();
queue.add(future);
future.handle(seq::next);
}).then((seq) -> {
result.add(6);
seq.exit();
});
CompletableFuture<Void> future;
while (null != (future = queue.poll())) {
future.complete(null);
}
List<Integer> exp = Arrays.asList(new Integer[] { 1, 4, 2, 5, 3, 6 });
assertEquals(exp, result);
}
}

View file

@ -1,394 +0,0 @@
/* ###
* 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.comm.util;
import static org.junit.Assert.*;
import java.io.*;
import java.net.URI;
import java.util.*;
import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;
import org.junit.Test;
import ghidra.util.Msg;
public class BitmaskSetTest {
public enum TestUniverse implements BitmaskUniverse {
FIRST(1 << 0), SECOND(1 << 1), THIRD(1 << 2), FOURTH(1 << 3);
TestUniverse(long mask) {
this.mask = mask;
}
final long mask;
@Override
public long getMask() {
return mask;
}
}
public enum TestAlternate implements BitmaskUniverse {
FIFTH(1 << 4), SIXTH(1 << 5), SEVENTH(1 << 6), EIGHTH(1 << 7);
TestAlternate(long mask) {
this.mask = mask;
}
final long mask;
@Override
public long getMask() {
return mask;
}
}
Set<Integer> intOf0 = new HashSet<>();
Set<Integer> intOf3 = new HashSet<>(Arrays.asList(new Integer[] { 0, 1, 2 }));
Set<String> strOf0 = new HashSet<>();
BitmaskSet<TestUniverse> setOf0 = BitmaskSet.of();
BitmaskSet<TestUniverse> setOf1 = BitmaskSet.of(TestUniverse.FIRST);
BitmaskSet<TestUniverse> setOf2 = BitmaskSet.of(TestUniverse.FIRST, TestUniverse.SECOND);
BitmaskSet<TestUniverse> setOf2a = BitmaskSet.of(TestUniverse.FIRST, TestUniverse.THIRD);
BitmaskSet<TestUniverse> setOf3 =
BitmaskSet.of(TestUniverse.FIRST, TestUniverse.SECOND, TestUniverse.THIRD);
BitmaskSet<TestAlternate> altOf0 = BitmaskSet.of();
@Test
public void testEmptiesDifferentTypesEqual() {
// This portion verifies the behavior of stock Java collections
assertEquals(intOf0, strOf0);
// This portion verifies that BitmaskSet imitates that behavior
assertEquals(setOf0, altOf0);
}
@Test
public void testOf() {
assertEquals(TestUniverse.class, setOf1.getUniverse());
assertEquals(TestUniverse.class, setOf0.getUniverse());
}
@Test
@SuppressWarnings("unlikely-arg-type") // Fair enough, Java. Fair enough. But it passes :)
public void testContainsEmptyDifferentType() {
// Check java behavior
assertTrue(intOf3.containsAll(strOf0));
// Check that BitmaskSet imitates it
assertTrue(setOf2.containsAll(altOf0));
assertTrue(setOf2.containsAll(strOf0));
}
@Test
public void testOfHasSafeVarargs() {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
StringWriter writer = new StringWriter();
PrintWriter out = new PrintWriter(writer);
out.println("import " + Set.class.getCanonicalName() + ";");
out.println("import " + BitmaskSet.class.getCanonicalName() + ";");
out.println("import " + TestUniverse.class.getCanonicalName() + ";");
out.println("import " + TestAlternate.class.getCanonicalName() + ";");
out.println("");
out.println("public class RequireFail {");
out.println(" public static void main(String[] args) {");
out.println(" Set<TestUniverse> testSet1 =");
out.println(" BitmaskSet.of(TestUniverse.FIRST, TestAlternate.FIFTH);");
out.println(" }");
out.println("}");
out.close();
JavaFileObject file =
new SimpleJavaFileObject(URI.create("string:///RequireFail.java"), Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return writer.toString();
}
};
Collection<JavaFileObject> units = Collections.singleton(file);
JavaFileManager fmgr = new ForwardingJavaFileManager<StandardJavaFileManager>(
compiler.getStandardFileManager(diagnostics, null, null)) {
@Override
public FileObject getFileForOutput(Location location, String packageName,
String relativeName, FileObject sibling) throws IOException {
Msg.debug(this, "Got request for output: '" + relativeName + "'");
return null;
}
};
CompilationTask task = compiler.getTask(null, fmgr, diagnostics,
Arrays.asList("-classpath", System.getProperty("java.class.path")), null, units);
assertFalse("Compilation should have failed", task.call());
String firstMessage = null;
for (Diagnostic<? extends JavaFileObject> diag : diagnostics.getDiagnostics()) {
if (firstMessage == null) {
firstMessage = diag.getLineNumber() + ":" + diag.getColumnNumber() + ": " +
diag.getMessage(null);
}
if (diag.getMessage(null).contains(
"method of in class ghidra.comm.util.BitmaskSet<E> cannot be applied to given types")) {
return;
}
}
fail("Unexpected compilation error, or no error: " + firstMessage);
}
@Test
public void testCopy() {
BitmaskSet<TestUniverse> test;
test = new BitmaskSet<>(setOf0);
assertEquals(0, test.getBitmask());
test = new BitmaskSet<>(TestUniverse.class, setOf2);
assertEquals(3, test.getBitmask());
test = new BitmaskSet<>(TestUniverse.class, new HashSet<>(setOf0));
assertEquals(0, test.getBitmask());
test = new BitmaskSet<>(TestUniverse.class, new HashSet<>(setOf2));
assertEquals(3, test.getBitmask());
}
@SuppressWarnings("unlikely-arg-type")
@Test
public void testEquality() {
assertFalse(setOf2.equals("Some string"));
assertTrue(setOf2.equals(setOf2));
assertTrue(setOf2.equals(new HashSet<>(setOf2)));
assertFalse(setOf2.equals(setOf1));
assertFalse(setOf2.equals(new HashSet<>(setOf1)));
assertFalse(setOf2.equals(setOf3));
assertFalse(setOf2.equals(new HashSet<>(setOf3)));
assertEquals(setOf2.hashCode(),
new BitmaskSet<>(TestUniverse.class, new HashSet<>(setOf2)).hashCode());
}
@Test
public void testSize() {
assertTrue(setOf0.isEmpty());
assertEquals(0, setOf0.size());
assertEquals(1, setOf1.size());
assertEquals(2, setOf2.size());
assertEquals(0, new BitmaskSet<>(TestUniverse.class).size());
}
@SuppressWarnings("unlikely-arg-type")
@Test
public void testContains() {
assertFalse(setOf0.contains(TestUniverse.FIRST));
assertTrue(setOf1.contains(TestUniverse.FIRST));
assertFalse(setOf1.contains(TestUniverse.SECOND));
assertFalse(setOf1.contains("Some string"));
}
@Test
public void testIterator() {
Set<TestUniverse> test;
Set<TestUniverse> exp;
test = new HashSet<>(setOf2);
exp = new HashSet<>(Arrays.asList(TestUniverse.FIRST, TestUniverse.SECOND));
assertEquals(exp, test);
test = new HashSet<>(setOf0);
exp = new HashSet<>();
assertEquals(exp, test);
}
@Test
public void testArray() {
TestUniverse[] arr;
arr = setOf0.toArray(new TestUniverse[] {});
assertEquals(0, arr.length);
arr = setOf2.toArray(new TestUniverse[] {});
assertEquals(2, arr.length);
assertEquals(TestUniverse.FIRST, arr[0]);
assertEquals(TestUniverse.SECOND, arr[1]);
Object[] oarr = setOf2.toArray();
assertEquals(2, oarr.length);
assertEquals(TestUniverse.FIRST, oarr[0]);
assertEquals(TestUniverse.SECOND, oarr[1]);
}
@Test
public void testAdd() {
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf1);
assertTrue(test.add(TestUniverse.SECOND));
assertEquals(setOf2, test);
assertFalse(test.add(TestUniverse.SECOND));
assertEquals(setOf2, test);
}
@SuppressWarnings("unlikely-arg-type")
@Test
public void testRemove() {
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
assertTrue(test.remove(TestUniverse.SECOND));
assertEquals(setOf1, test);
assertFalse(test.remove(TestUniverse.SECOND));
assertEquals(setOf1, test);
assertFalse(test.remove("Some string"));
assertEquals(setOf1, test);
}
@Test
public void testContainsAll() {
assertTrue(setOf0.containsAll(setOf0));
assertFalse(setOf0.containsAll(setOf1));
assertTrue(setOf0.containsAll(new HashSet<>()));
assertFalse(setOf0.containsAll(new HashSet<>(setOf1)));
assertTrue(setOf2.containsAll(setOf1));
assertFalse(setOf1.containsAll(setOf2));
assertTrue(setOf2.containsAll(new HashSet<>(setOf1)));
assertFalse(setOf1.containsAll(new HashSet<>(setOf2)));
}
@Test
public void testUnion() {
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
assertTrue(test.addAll(setOf2a));
assertEquals(setOf3, test);
assertFalse(test.addAll(setOf2a));
assertEquals(setOf3, test);
test = new BitmaskSet<>(setOf2);
assertTrue(test.addAll(new HashSet<>(setOf2a)));
assertEquals(setOf3, test);
assertFalse(test.addAll(new HashSet<>(setOf2a)));
assertEquals(setOf3, test);
test = new BitmaskSet<>(setOf0);
assertFalse(test.addAll(setOf0));
assertEquals(setOf0, test);
assertFalse(test.addAll(new HashSet<>(setOf0)));
assertEquals(setOf0, test);
test = new BitmaskSet<>(setOf0);
assertTrue(test.addAll(setOf1));
assertEquals(setOf1, test);
}
@Test
public void testIntersection() {
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
assertTrue(test.retainAll(setOf2a));
assertEquals(setOf1, test);
assertFalse(test.retainAll(setOf2a));
assertEquals(setOf1, test);
test = new BitmaskSet<>(setOf2);
assertTrue(test.retainAll(new HashSet<>(setOf2a)));
assertEquals(setOf1, test);
assertFalse(test.retainAll(new HashSet<>(setOf2a)));
assertEquals(setOf1, test);
test = new BitmaskSet<>(setOf2);
assertTrue(test.retainAll(new HashSet<>()));
assertEquals(setOf0, test);
assertFalse(test.retainAll(new HashSet<>()));
assertEquals(setOf0, test);
test = new BitmaskSet<>(setOf2);
Set<Object> temp = new HashSet<>();
temp.addAll(setOf2a);
temp.add("Some string");
assertTrue(test.retainAll(temp));
assertEquals(setOf1, test);
assertFalse(test.retainAll(temp));
assertEquals(setOf1, test);
}
@Test
public void testSubtraction() {
BitmaskSet<TestUniverse> exp = BitmaskSet.of(TestUniverse.SECOND);
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
assertTrue(test.removeAll(setOf2a));
assertEquals(exp, test);
assertFalse(test.removeAll(setOf2a));
assertEquals(exp, test);
test = new BitmaskSet<>(setOf2);
assertTrue(test.removeAll(new HashSet<>(setOf2a)));
assertEquals(exp, test);
assertFalse(test.removeAll(new HashSet<>(setOf2a)));
assertEquals(exp, test);
test = new BitmaskSet<>(setOf2);
assertFalse(test.removeAll(new HashSet<>()));
assertEquals(setOf2, test);
test = new BitmaskSet<>(setOf2);
Set<Object> temp = new HashSet<>();
temp.addAll(setOf2a);
temp.add("Some string");
assertTrue(test.removeAll(temp));
assertEquals(exp, test);
assertFalse(test.removeAll(temp));
assertEquals(exp, test);
}
@Test
public void testClear() {
BitmaskSet<TestUniverse> test = new BitmaskSet<>(setOf2);
test.clear();
assertEquals(setOf0, test);
}
@Test
public void testToString() {
assertEquals("[]", setOf0.toString());
assertEquals("[FIRST]", setOf1.toString());
assertEquals("[FIRST, SECOND]", setOf2.toString());
}
@Test
public void testBitmask() {
assertEquals(0, setOf0.getBitmask());
assertEquals(1, setOf1.getBitmask());
assertEquals(3, setOf2.getBitmask());
assertEquals(7, setOf3.getBitmask());
BitmaskSet<TestUniverse> test = new BitmaskSet<>(TestUniverse.class);
test.setBitmask(5);
assertEquals(test, BitmaskSet.of(TestUniverse.FIRST, TestUniverse.THIRD));
}
}

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,13 +15,15 @@
*/ */
package ghidra.program.model.address; package ghidra.program.model.address;
import java.util.Iterator; import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/** /**
* A class to break a range of addresses into 'chunks' of a give size. This is useful to * A class to break a range of addresses into 'chunks' of a give size. This is useful to break-up
* break-up processing of large swaths of addresses, such as when performing work in a * processing of large swaths of addresses, such as when performing work in a background thread.
* background thread. Doing this allows the client to iterator over the range, pausing * Doing this allows the client to iterator over the range, pausing enough to allow the UI to
* enough to allow the UI to update. * update.
*/ */
public class AddressRangeChunker implements Iterable<AddressRange> { public class AddressRangeChunker implements Iterable<AddressRange> {
@ -106,4 +108,22 @@ public class AddressRangeChunker implements Iterable<AddressRange> {
}; };
} }
@Override
public Spliterator<AddressRange> spliterator() {
long countAddrs = end.subtract(nextStartAddress) + 1;
long size = Long.divideUnsigned(countAddrs + chunkSize - 1, chunkSize);
return Spliterators.spliterator(iterator(), size,
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
Spliterator.SIZED);
}
/**
* Stream the chunks
*
* @return the stream
*/
public Stream<AddressRange> stream() {
return StreamSupport.stream(spliterator(), false);
}
} }

View file

@ -17,7 +17,9 @@
package ghidra.program.model.address; package ghidra.program.model.address;
import java.util.Iterator; import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/** /**
* Defines a read-only interface for an address set. * Defines a read-only interface for an address set.
@ -33,13 +35,13 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Test if the given address range is contained in this set. * Test if the given address range is contained in this set.
* The specified start and end addresses must form a valid range within * <p>
* a single {@link AddressSpace}. * The specified start and end addresses must form a valid range within a single
* {@link AddressSpace}.
* *
* @param start the first address in the range. * @param start the first address in the range.
* @param end the last address in the range. * @param end the last address in the range.
* @return true if entire range is contained within the set, * @return true if entire range is contained within the set, false otherwise.
* false otherwise.
*/ */
public boolean contains(Address start, Address end); public boolean contains(Address start, Address end);
@ -47,8 +49,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
* Test if the given address set is a subset of this set. * Test if the given address set is a subset of this set.
* *
* @param rangeSet the set to test. * @param rangeSet the set to test.
* @return true if the entire set is contained within this set, * @return true if the entire set is contained within this set, false otherwise.
* false otherwise.
*/ */
public boolean contains(AddressSetView rangeSet); public boolean contains(AddressSetView rangeSet);
@ -59,8 +60,9 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Get the minimum address for this address set. * Get the minimum address for this address set.
* NOTE: An {@link AddressRange} should generally not be formed using this address * <p>
* and {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s. * NOTE: An {@link AddressRange} should generally not be formed using this address and
* {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s.
* *
* @return the minimum address for this set. Returns null if the set is empty. * @return the minimum address for this set. Returns null if the set is empty.
*/ */
@ -68,8 +70,9 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Get the maximum address for this address set. * Get the maximum address for this address set.
* NOTE: An {@link AddressRange} should generally not be formed using this address * <p>
* and {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s. * NOTE: An {@link AddressRange} should generally not be formed using this address and
* {@link #getMaxAddress()} since it may span multiple {@link AddressSpace}s.
* *
* @return the maximum address for this set. Returns null if the set is empty. * @return the maximum address for this set. Returns null if the set is empty.
*/ */
@ -87,17 +90,21 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Returns an iterator over the ranges in the specified order * Returns an iterator over the ranges in the specified order
* @param forward the ranges are returned from lowest to highest, otherwise from *
* highest to lowest * @param forward the ranges are returned from lowest to highest, otherwise from highest to
* lowest
* @return an iterator over all the addresse ranges in the set. * @return an iterator over all the addresse ranges in the set.
*/ */
public AddressRangeIterator getAddressRanges(boolean forward); public AddressRangeIterator getAddressRanges(boolean forward);
/** /**
* Returns an iterator of address ranges starting with the range that contains the given address. * Returns an iterator of address ranges starting with the range that contains the given
* If there is no range containing the start address, then the first range will be * address.
* the next range greater than the start address if going forward, otherwise the range less than * <p>
* the start address * If there is no range containing the start address, then the first range will be the next
* range greater than the start address if going forward, otherwise the range less than the
* start address
*
* @param start the address the first range should contain. * @param start the address the first range should contain.
* @param forward true iterators forward, false backwards * @param forward true iterators forward, false backwards
* @return the AddressRange iterator * @return the AddressRange iterator
@ -110,25 +117,91 @@ public interface AddressSetView extends Iterable<AddressRange> {
@Override @Override
public Iterator<AddressRange> iterator(); public Iterator<AddressRange> iterator();
@Override
default Spliterator<AddressRange> spliterator() {
return Spliterators.spliterator(iterator(), getNumAddressRanges(),
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
Spliterator.SIZED);
}
/**
* Stream the ranges in this set
*
* @return the stream
*/
default Stream<AddressRange> stream() {
return StreamSupport.stream(spliterator(), false);
}
/** /**
* Returns an iterator over the ranges in the specified order * Returns an iterator over the ranges in the specified order
* @param forward the ranges are returned from lowest to highest, otherwise from *
* highest to lowest * @param forward the ranges are returned from lowest to highest, otherwise from highest to
* @return an iterator over all the addresse ranges in the set. * lowest
* @return an iterator over all the address ranges in the set.
*/ */
public Iterator<AddressRange> iterator(boolean forward); public Iterator<AddressRange> iterator(boolean forward);
/** /**
* Returns an iterator of address ranges starting with the range that contains the given address. * Create a spliterator over the ranges, as in {@link #iterator(boolean)}
* If there is no range containing the start address, then the first range will be *
* the next range greater than the start address if going forward, otherwise the range less than * @param forward true to traverse lowest to highest, false for reverse
* the start address * @return a spliterator over all the address ranges in the set.
*/
default Spliterator<AddressRange> spliterator(boolean forward) {
return Spliterators.spliterator(iterator(forward), getNumAddressRanges(),
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
Spliterator.SIZED);
}
/**
* Stream the ranges in the set forward or backward
*
* @param forward true to stream lowest to highest, false for reverse
* @return a stream over all the address ranges in the set.
*/
default Stream<AddressRange> stream(boolean forward) {
return StreamSupport.stream(spliterator(forward), false);
}
/**
* Returns an iterator of address ranges starting with the range that contains the given
* address.
* <p>
* If there is no range containing the start address, then the first range will be the next
* range greater than the start address if going forward, otherwise the range less than the
* start address
*
* @param start the address that the first range should contain. * @param start the address that the first range should contain.
* @param forward true iterators forward, false backwards * @param forward true iterators forward, false backwards
* @return the AddressRange iterator * @return the AddressRange iterator
*/ */
public Iterator<AddressRange> iterator(Address start, boolean forward); public Iterator<AddressRange> iterator(Address start, boolean forward);
/**
* Create a spliterator over the ranges, as in {@link #iterator(boolean)}
*
* @param start the address that the first range should contain.
* @param forward true to traverse lowest to highest, false for reverse
* @return a spliterator over the address ranges.
*/
default Spliterator<AddressRange> spliterator(Address start, boolean forward) {
return Spliterators.spliterator(iterator(start, forward), getNumAddressRanges(),
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
Spliterator.SIZED);
}
/**
* Stream the ranges in the set as in {@link #iterator(Address, boolean)}
*
* @param start the address that the first range should contain.
* @param forward true to stream lowest to highest, false for reverse
* @return a stream over the address ranges.
*/
default Stream<AddressRange> stream(Address start, boolean forward) {
return StreamSupport.stream(spliterator(start, forward), false);
}
/** /**
* @return the number of addresses in this set. * @return the number of addresses in this set.
*/ */
@ -136,19 +209,19 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Returns an iterator over all addresses in this set. * Returns an iterator over all addresses in this set.
* @param forward if true the address are return in increasing order, otherwise in *
* decreasing order. * @param forward if true the address are return in increasing order, otherwise in decreasing
* order.
* @return an iterator over all addresses in this set. * @return an iterator over all addresses in this set.
*/ */
public AddressIterator getAddresses(boolean forward); public AddressIterator getAddresses(boolean forward);
/** /**
* Returns an iterator over the addresses in this address set * Returns an iterator over the addresses in this address set starting at the start address
* starting at the start address *
* @param start address to start iterating at in the address set * @param start address to start iterating at in the address set
* @param forward if true address are return from lowest to highest, else from highest to lowest * @param forward if true address are return from lowest to highest, else from highest to lowest
* @return an iterator over the addresses in this address set * @return an iterator over the addresses in this address set starting at the start address
* starting at the start address
*/ */
public AddressIterator getAddresses(Address start, boolean forward); public AddressIterator getAddresses(Address start, boolean forward);
@ -162,8 +235,10 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Determine if the start and end range intersects with the specified address set. * Determine if the start and end range intersects with the specified address set.
* The specified start and end addresses must form a valid range within * <p>
* a single {@link AddressSpace}. * The specified start and end addresses must form a valid range within a single
* {@link AddressSpace}.
*
* @param start start of range * @param start start of range
* @param end end of range * @param end end of range
* @return true if the given range intersects this address set. * @return true if the given range intersects this address set.
@ -172,75 +247,86 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Computes the intersection of this address set with the given address set. * Computes the intersection of this address set with the given address set.
* <p>
* This method does not modify this address set. * This method does not modify this address set.
*
* @param view the address set to intersect with. * @param view the address set to intersect with.
* @return AddressSet a new address set that contains all addresses that are * @return AddressSet a new address set that contains all addresses that are contained in both
* contained in both this set and the given set. * this set and the given set.
*/ */
public AddressSet intersect(AddressSetView view); public AddressSet intersect(AddressSetView view);
/** /**
* Computes the intersection of this address set with the given address range. * Computes the intersection of this address set with the given address range.
* This method does not modify this address set. * <p>
* The specified start and end addresses must form a valid range within * This method does not modify this address set. The specified start and end addresses must form
* a single {@link AddressSpace}. * a valid range within a single {@link AddressSpace}.
*
* @param start start of range * @param start start of range
* @param end end of range * @param end end of range
* @return AddressSet a new address set that contains all addresses that are * @return AddressSet a new address set that contains all addresses that are contained in both
* contained in both this set and the given range. * this set and the given range.
*/ */
public AddressSet intersectRange(Address start, Address end); public AddressSet intersectRange(Address start, Address end);
/** /**
* Computes the union of this address set with the given address set. This * Computes the union of this address set with the given address set.
* method does not change this address set. * <p>
* This method does not change this address set.
*
* @param addrSet The address set to be unioned with this address set. * @param addrSet The address set to be unioned with this address set.
* @return AddressSet A new address set which contains all the addresses * @return AddressSet A new address set which contains all the addresses from both this set and
* from both this set and the given set. * the given set.
*/ */
public AddressSet union(AddressSetView addrSet); public AddressSet union(AddressSetView addrSet);
/** /**
* Computes the difference of this address set with the given address set * Computes the difference of this address set with the given address set (this - set).
* (this - set). Note that this is not the same as (set - this). This * <p>
* method does not change this address set. * Note that this is not the same as (set - this). This method does not change this address set.
*
* @param addrSet the set to subtract from this set. * @param addrSet the set to subtract from this set.
* @return AddressSet a new address set which contains all the addresses * @return AddressSet a new address set which contains all the addresses that are in this set,
* that are in this set, but not in the given set. * but not in the given set.
*/ */
public AddressSet subtract(AddressSetView addrSet); public AddressSet subtract(AddressSetView addrSet);
/** /**
* Computes the exclusive-or of this address set with the given set. This * Computes the exclusive-or of this address set with the given set.
* method does not modify this address set. * <p>
* This method does not modify this address set.
*
* @param addrSet address set to exclusive-or with. * @param addrSet address set to exclusive-or with.
* @return AddressSet a new address set containing all addresses that are in * @return AddressSet a new address set containing all addresses that are in either this set or
* either this set or the given set, but not in both sets * the given set, but not in both sets
*/ */
public AddressSet xor(AddressSetView addrSet); public AddressSet xor(AddressSetView addrSet);
/** /**
* Returns true if the given address set contains the same set of addresses * Returns true if the given address set contains the same set of addresses as this set.
* as this set. *
* @param view the address set to compare. * @param view the address set to compare.
* @return true if the given set contains the same addresses as this set. * @return true if the given set contains the same addresses as this set.
*/ */
public boolean hasSameAddresses(AddressSetView view); public boolean hasSameAddresses(AddressSetView view);
/** /**
* Returns the first range in this set or null if the set is empty; * Returns the first range in this set or null if the set is empty
* @return the first range in this set or null if the set is empty; *
* @return the first range in this set or null if the set is empty
*/ */
public AddressRange getFirstRange(); public AddressRange getFirstRange();
/** /**
* Returns the last range in this set or null if the set is empty; * Returns the last range in this set or null if the set is empty
* @return the last range in this set or null if the set is empty; *
* @return the last range in this set or null if the set is empty
*/ */
public AddressRange getLastRange(); public AddressRange getLastRange();
/** /**
* Returns the range that contains the given address * Returns the range that contains the given address
*
* @param address the address for which to find a range. * @param address the address for which to find a range.
* @return the range that contains the given address. * @return the range that contains the given address.
*/ */
@ -248,6 +334,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Finds the first address in this collection that is also in the given addressSet. * Finds the first address in this collection that is also in the given addressSet.
*
* @param set the addressSet to search for the first (lowest) common address. * @param set the addressSet to search for the first (lowest) common address.
* @return the first address that is contained in this set and the given set. * @return the first address that is contained in this set and the given set.
*/ */
@ -255,6 +342,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
/** /**
* Returns the number of address in this address set before the given address. * Returns the number of address in this address set before the given address.
*
* @param address the address after the last address to be counted * @param address the address after the last address to be counted
* @return the number of address in this address set before the given address * @return the number of address in this address set before the given address
*/ */
@ -274,12 +362,13 @@ public interface AddressSetView extends Iterable<AddressRange> {
} }
/** /**
* Trim address set removing all addresses less-than-or-equal to specified * Trim address set removing all addresses less-than-or-equal to specified address based upon
* address based upon {@link Address} comparison. * {@link Address} comparison.
* The address set may contain address ranges from multiple * <p>
* address spaces. * The address set may contain address ranges from multiple address spaces.
*
* @param set address set to be trimmed * @param set address set to be trimmed
* @param addr trim point. Only addresses greater than this address will be returned. * @param addr trim point. Only addresses greater than this address will be returned.
* @return trimmed address set view * @return trimmed address set view
*/ */
public static AddressSetView trimStart(AddressSetView set, Address addr) { public static AddressSetView trimStart(AddressSetView set, Address addr) {
@ -301,12 +390,13 @@ public interface AddressSetView extends Iterable<AddressRange> {
} }
/** /**
* Trim address set removing all addresses greater-than-or-equal to specified * Trim address set removing all addresses greater-than-or-equal to specified address based upon
* address based upon {@link Address} comparison. * {@link Address} comparison.
* The address set may contain address ranges from multiple * <p>
* address spaces. * The address set may contain address ranges from multiple address spaces.
*
* @param set address set to be trimmed * @param set address set to be trimmed
* @param addr trim point. Only addresses less than this address will be returned. * @param addr trim point. Only addresses less than this address will be returned.
* @return trimmed address set view * @return trimmed address set view
*/ */
public static AddressSetView trimEnd(AddressSetView set, Address addr) { public static AddressSetView trimEnd(AddressSetView set, Address addr) {