Class AsyncTrampoline


  • public final class AsyncTrampoline
    extends java.lang.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:
    AsyncIterator
    • Method Summary

      All Methods Static Methods Concrete Methods 
      Modifier and Type Method Description
      static <T> java.util.concurrent.CompletionStage<T> asyncWhile​(java.util.function.Predicate<? super T> shouldContinue, java.util.function.Function<? super T,​? extends java.util.concurrent.CompletionStage<T>> fn, T initialValue)
      Repeatedly applies an asynchronous function fn to a value until shouldContinue returns false.
      static java.util.concurrent.CompletionStage<java.lang.Void> asyncWhile​(java.util.function.Supplier<? extends java.util.concurrent.CompletionStage<java.lang.Boolean>> fn)
      Repeatedly uses the function fn to produce a CompletionStage of a boolean, stopping when then boolean is false.
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Method Detail

      • asyncWhile

        public static <T> java.util.concurrent.CompletionStage<T> asyncWhile​(java.util.function.Predicate<? super T> shouldContinue,
                                                                             java.util.function.Function<? super T,​? extends java.util.concurrent.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 an 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 java.util.concurrent.CompletionStage<java.lang.Void> asyncWhile​(java.util.function.Supplier<? extends java.util.concurrent.CompletionStage<java.lang.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