Class AsyncTrampoline

java.lang.Object
org.hibernate.reactive.util.async.impl.AsyncTrampoline

public final class AsyncTrampoline extends Object
Copy of com.ibm.asyncutil.iteration.AyncTrampoline from com.ibm.async:asyncutil:0.1.0 without all the methods and imports we don't need for Hibernate Reactive.

Static methods for asynchronous looping procedures without exhausting the stack.

When working with CompletionStage, it's often desirable to have a loop like construct which keeps producing stages until some condition is met. Because continuations are asynchronous, it's usually easiest to do this with a recursive approach:

 
 CompletionStage<Integer> getNextNumber();

 CompletionStage<Integer> getFirstOddNumber(int current) {
   if (current % 2 != 0)
     // found odd number
     return CompletableFuture.completedFuture(current);
   else
     // get the next number and recurse
     return getNextNumber().thenCompose(next -> getFirstOddNumber(next));
 }
 
 

The problem with this is that if the implementation of getNextNumber happens to be synchronous

 
 CompletionStage<Integer> getNextNumber() {
   return CompletableFuture.completedFuture(random.nextInt());
 }
 
 

then getFirstOddNumber can easily cause a stack overflow. This situation often happens when a cache is put under an async API, and all the values are cached and returned immediately. This could be avoided by scheduling the recursive calls back to a thread pool using CompletionStage.thenComposeAsync(java.util.function.Function<? super T, ? extends java.util.concurrent.CompletionStage<U>>), however the overhead of the thread pool submissions may be high and may cause unnecessary context switching.

The methods on this class ensure that the stack doesn't blow up - if multiple calls happen on the same thread they are queued and run in a loop. You could write the previous example like this:

 
 CompletionStage<Integer> getFirstOddNumber(int initial) {
   return AsyncTrampoline.asyncWhile(
    i -> i % 2 == 0,
    i -> getNextNumber(),
    initial);
 }
 
 

Though this class provides efficient methods for a few loop patterns, many are better represented by the more expressive API available on AsyncIterator, which is also stack safe. For example, the preceding snippet can be expressed as AsyncIterator.generate(this::getNextNumber).find(i -> i % 2 != 0)

See Also:
  • Method Details

    • asyncWhile

      public static <T> CompletionStage<T> asyncWhile(Predicate<? super T> shouldContinue, Function<? super T,? extends CompletionStage<T>> fn, T initialValue)
      Repeatedly applies an asynchronous function fn to a value until shouldContinue returns false. The asynchronous equivalent of
       
       T loop(Predicate shouldContinue, Function fn, T initialValue) {
         T t = initialValue;
         while (shouldContinue.test(t)) {
           t = fn.apply(t);
         }
         return t;
       }
       
       

      Effectively produces fn(seed).thenCompose(fn).thenCompose(fn)... .thenCompose(fn) until a value fails the predicate. Note that predicate will be applied on seed (like a while loop, the initial value is tested). If the predicate or fn throw an exception, or the CompletionStage returned by fn completes exceptionally, iteration will stop and an exceptional stage will be returned.

      Type Parameters:
      T - the type of elements produced by the loop
      Parameters:
      shouldContinue - a predicate which will be applied to every intermediate T value (including the initialValue) until it fails and looping stops.
      fn - the function for the loop body which produces a new CompletionStage based on the result of the previous iteration.
      initialValue - the value that will initially be passed to fn, it will also be initially tested by shouldContinue
      Returns:
      a CompletionStage that completes with the first value t such that shouldContinue.test(T) == false, or with an exception if one was thrown.
    • asyncWhile

      public static CompletionStage<Void> asyncWhile(Supplier<? extends CompletionStage<Boolean>> fn)
      Repeatedly uses the function fn to produce a CompletionStage of a boolean, stopping when then boolean is false. The asynchronous equivalent of while(fn.get());. Generally, the function fn must perform some side effect for this method to be useful. If the fn throws or produces an exceptional CompletionStage, an exceptional stage will be returned.
      Parameters:
      fn - a Supplier of a CompletionStage that indicates whether iteration should continue
      Returns:
      a CompletionStage that is complete when a stage produced by fn has returned false, or with an exception if one was thrown