Class SubscribableListener<T>

java.lang.Object
org.elasticsearch.action.support.SubscribableListener<T>
All Implemented Interfaces:
ActionListener<T>
Direct Known Subclasses:
ListenableActionFuture, ListenableFuture

public class SubscribableListener<T> extends Object implements ActionListener<T>
An ActionListener to which other ActionListener instances can subscribe, such that when this listener is completed it fans-out its result to the subscribed listeners.

Exceptions are passed to subscribed listeners without modification. ListenableActionFuture and ListenableFuture are child classes that provide additional exception handling.

A sequence of async steps can be chained together using a series of SubscribableListeners, similar to CompletionStage (without the catch (Throwable t)). Listeners can be created for each step, where the next step subscribes to the result of the previous, using utilities like andThen(CheckedBiConsumer). The following example demonstrates how this might be used:


 private void exampleAsyncMethod(String request, List<Long> items, ActionListener<Boolean> finalListener) {
     SubscribableListener

         // Start the chain and run the first step by creating a SubscribableListener using newForked():
         .<String>newForked(l -> firstAsyncStep(request, l))

         // Run a second step when the first step completes using andThen(); if the first step fails then the exception falls through to
         // the end without executing the intervening steps.
         .<Integer>andThen((l, firstStepResult) -> secondAsyncStep(request, firstStepResult, l))

         // Run another step when the second step completes with another andThen() call; as above this only runs if the first two steps
         // succeed.
         .<Boolean>andThen((l, secondStepResult) -> {
             if (condition) {
                 // Steps are exception-safe: an exception thrown here will be passed to the listener rather than escaping to the
                 // caller.
                 throw new IOException("failure");
             }

             // Steps can fan out to multiple subsidiary async actions using utilities like RefCountingListener.
             final var result = new AtomicBoolean();
             try (var listeners = new RefCountingListener(l.map(v -> result.get()))) {
                 for (final var item : items) {
                     thirdAsyncStep(secondStepResult, item, listeners.acquire());
                 }
             }
         })

         // Synchronous (non-forking) steps which do not return a result can be expressed using andThenAccept() with a consumer:
         .andThenAccept(thirdStepResult -> {
             if (condition) {
                 // andThenAccept() is also exception-safe
                 throw new ElasticsearchException("some other problem");
             }
             consumeThirdStepResult(thirdStepResult);
         })

         // Synchronous (non-forking) steps which do return a result can be expressed using andThenApply() with a function:
         .andThenApply(voidFromStep4 -> {
             if (condition) {
                 // andThenApply() is also exception-safe
                 throw new IllegalArgumentException("failure");
             }
             return computeFifthStepResult();
         })

         // To complete the chain, add the outer listener which will be completed with the result of the previous step if all steps were
         // successful, or the exception if any step failed.
         .addListener(finalListener);
 }
 
  • Constructor Details

    • SubscribableListener

      public SubscribableListener()
      Create a SubscribableListener which is incomplete.
  • Method Details

    • newSucceeded

      public static <T> SubscribableListener<T> newSucceeded(T result)
      Create a SubscribableListener which has already succeeded with the given result.
    • newFailed

      public static <T> SubscribableListener<T> newFailed(Exception exception)
      Create a SubscribableListener which has already failed with the given exception.
    • newForked

      public static <T> SubscribableListener<T> newForked(CheckedConsumer<ActionListener<T>,? extends Exception> fork)
      Create a SubscribableListener, fork a computation to complete it, and return the listener. If the forking itself throws an exception then the exception is caught and fed to the returned listener.
    • addListener

      public final void addListener(ActionListener<T> listener)
      Add a listener to this listener's collection of subscribers. If this listener is complete, this method completes the subscribing listener immediately with the result with which this listener was completed. Otherwise, the subscribing listener is retained and completed when this listener is completed.

      Subscribed listeners must not throw any exceptions.

      Listeners added strictly before this listener is completed will themselves be completed in the order in which their subscriptions were received. However, there are no guarantees about the ordering of the completions of listeners which are added concurrently with (or after) the completion of this listener.

      If the subscribed listener is not completed immediately then it will be completed on the thread, and in the ThreadContext, of the thread which completes this listener.

    • addListener

      public final void addListener(ActionListener<T> listener, Executor executor, @Nullable ThreadContext threadContext)
      Add a listener to this listener's collection of subscribers. If this listener is complete, this method completes the subscribing listener immediately with the result with which this listener was completed. Otherwise, the subscribing listener is retained and completed when this listener is completed.

      Subscribed listeners must not throw any exceptions.

      Listeners added strictly before this listener is completed will themselves be completed in the order in which their subscriptions were received. However, there are no guarantees about the ordering of the completions of listeners which are added concurrently with (or after) the completion of this listener.

      Parameters:
      executor - If not EsExecutors.DIRECT_EXECUTOR_SERVICE, and the subscribing listener is not completed immediately, then it will be completed using the given executor. If the subscribing listener is completed immediately then this completion happens on the subscribing thread.
      threadContext - If not null, and the subscribing listener is not completed immediately, then it will be completed in the given thread context. If null, and the subscribing listener is not completed immediately, then it will be completed in the ThreadContext of the completing thread. If the subscribing listener is completed immediately then this completion happens in the ThreadContext of the subscribing thread.
    • onResponse

      public final void onResponse(T result)
      Description copied from interface: ActionListener
      Handle action response. This response may constitute a failure or a success but it is up to the listener to make that decision.
      Specified by:
      onResponse in interface ActionListener<T>
    • onFailure

      public final void onFailure(Exception exception)
      Description copied from interface: ActionListener
      A failure caused by an exception at some phase of the task.
      Specified by:
      onFailure in interface ActionListener<T>
    • wrapException

      protected Exception wrapException(Exception exception)
    • isDone

      public final boolean isDone()
      Returns:
      true if and only if this listener has been completed (either successfully or exceptionally).
    • rawResult

      protected final T rawResult() throws Exception
      Returns:
      the result with which this listener completed successfully, or throw the exception with which it failed.
      Throws:
      AssertionError - if this listener is not complete yet and assertions are enabled.
      IllegalStateException - if this listener is not complete yet and assertions are disabled.
      Exception
    • wrapAsExecutionException

      protected static RuntimeException wrapAsExecutionException(Throwable t)
    • andThen

      public <U> SubscribableListener<U> andThen(CheckedBiConsumer<ActionListener<U>,T,? extends Exception> nextStep)
      Creates and returns a new SubscribableListener L and subscribes nextStep to this listener such that if this listener is completed successfully with result R then nextStep is invoked with arguments L and R. If this listener is completed with exception E then so is L.

      This can be used to construct a sequence of async actions, each invoked with the result of the previous one:

       l.andThen((l1, o1) -> forkAction1(o1, args1, l1)).andThen((l2, o2) -> forkAction2(o2, args2, l2)).addListener(finalListener);
       
      After creating this chain, completing l with a successful response will pass the response to forkAction1, which will on completion pass its response to forkAction2, which will in turn pass its response to finalListener. A failure of any step will bypass the remaining steps and ultimately fail finalListener.

      The threading of the nextStep callback is the same as for listeners added with addListener(org.elasticsearch.action.ActionListener<T>): if this listener is already complete then nextStep is invoked on the thread calling andThen(org.elasticsearch.common.CheckedBiConsumer<org.elasticsearch.action.ActionListener<U>, T, ? extends java.lang.Exception>) and in its thread context, but if this listener is incomplete then nextStep is invoked on the completing thread and in its thread context.

    • andThen

      public <U> SubscribableListener<U> andThen(Executor executor, @Nullable ThreadContext threadContext, CheckedBiConsumer<ActionListener<U>,T,? extends Exception> nextStep)
      Creates and returns a new SubscribableListener L and subscribes nextStep to this listener such that if this listener is completed successfully with result R then nextStep is invoked with arguments L and R. If this listener is completed with exception E then so is L.

      This can be used to construct a sequence of async actions, each invoked with the result of the previous one:

       l.andThen(x, t, (l1,o1) -> forkAction1(o1,args1,l1)).andThen(x, t, (l2,o2) -> forkAction2(o2,args2,l2)).addListener(finalListener);
       
      After creating this chain, completing l with a successful response will pass the response to forkAction1, which will on completion pass its response to forkAction2, which will in turn pass its response to finalListener. A failure of any step will bypass the remaining steps and ultimately fail finalListener.

      The threading of the nextStep callback is the same as for listeners added with addListener(org.elasticsearch.action.ActionListener<T>): if this listener is already complete then nextStep is invoked on the thread calling andThen(org.elasticsearch.common.CheckedBiConsumer<org.elasticsearch.action.ActionListener<U>, T, ? extends java.lang.Exception>) and in its thread context, but if this listener is incomplete then nextStep is invoked using executor, in a thread context captured when andThen(org.elasticsearch.common.CheckedBiConsumer<org.elasticsearch.action.ActionListener<U>, T, ? extends java.lang.Exception>) was called.

    • andThenApply

      public <U> SubscribableListener<U> andThenApply(CheckedFunction<T,U,Exception> fn)
      Creates and returns a new SubscribableListener L such that if this listener is completed successfully with result R then fn is invoked with argument R, and L is completed with the result of that invocation. If this listener is completed exceptionally, or fn throws an exception, then L is completed with that exception.

      This is essentially a shorthand for a call to andThen(org.elasticsearch.common.CheckedBiConsumer<org.elasticsearch.action.ActionListener<U>, T, ? extends java.lang.Exception>) with a nextStep argument that is fully synchronous.

      The threading of the fn invocation is the same as for listeners added with addListener(org.elasticsearch.action.ActionListener<T>): if this listener is already complete then fn is invoked on the thread calling andThenApply(org.elasticsearch.core.CheckedFunction<T, U, java.lang.Exception>) and in its thread context, but if this listener is incomplete then fn is invoked on the thread, and in the thread context, on which this listener is completed.

    • andThenAccept

      public SubscribableListener<Void> andThenAccept(CheckedConsumer<T,Exception> consumer)
      Creates and returns a new SubscribableListener L such that if this listener is completed successfully with result R then consumer is applied to argument R, and L is completed with null when consumer returns. If this listener is completed exceptionally, or consumer throws an exception, then L is completed with that exception.

      This is essentially a shorthand for a call to andThen(org.elasticsearch.common.CheckedBiConsumer<org.elasticsearch.action.ActionListener<U>, T, ? extends java.lang.Exception>) with a nextStep argument that is fully synchronous.

      The threading of the consumer invocation is the same as for listeners added with addListener(org.elasticsearch.action.ActionListener<T>): if this listener is already complete then consumer is invoked on the thread calling andThenAccept(org.elasticsearch.core.CheckedConsumer<T, java.lang.Exception>) and in its thread context, but if this listener is incomplete then consumer is invoked on the thread, and in the thread context, on which this listener is completed.

    • addTimeout

      public void addTimeout(TimeValue timeout, ThreadPool threadPool, Executor timeoutExecutor)
      Adds a timeout to this listener, such that if the timeout elapses before the listener is completed then it will be completed with an ElasticsearchTimeoutException.

      The process which is racing against this timeout should stop and clean up promptly when the timeout occurs to avoid unnecessary work. For instance, it could check that the race is not lost by calling isDone() whenever appropriate, or it could subscribe another listener which performs any necessary cleanup steps.