Class RefCountingListener

java.lang.Object
org.elasticsearch.action.support.RefCountingListener
All Implemented Interfaces:
Closeable, AutoCloseable, Releasable

public final class RefCountingListener extends Object implements Releasable
A mechanism to complete a listener on the completion of some (dynamic) collection of other actions. Basic usage is as follows:
 try (var listeners = new RefCountingListener(finalListener)) {
     for (var item : collection) {
         runAsyncAction(item, listeners.acquire()); // completes the acquired listener on completion
     }
 }
 
The delegate listener is completed when execution leaves the try-with-resources block and every acquired listener is completed. The RefCountingListener collects a bounded number of the exceptions received by the acquired listeners, and completes the delegate listener with an exception if and only if any acquired listener fails. If more than one acquired listener fails, the resulting exception is the first such failure, to which other tracked exceptions are added using Throwable.addSuppressed(java.lang.Throwable).

A RefCountingListener, unlike a GroupedActionListener, leaves it to the caller to collect the results of successful completions by accumulating them in a data structure of its choice: a RefCountingListener itself adds only a small and O(1) amount of heap overhead. Also unlike a GroupedActionListener there is no need to declare the number of subsidiary listeners up front: listeners can be acquired dynamically as needed, and you can continue to acquire additional listeners outside the try-with-resources block, even in a separate thread, as long as you ensure there's at least one listener outstanding:

 try (var listeners = new RefCountingListener(finalListener)) {
     for (var item : collection) {
         if (condition(item)) {
             runAsyncAction(item, listeners.acquire(results::add));
         }
     }
     if (flag) {
         runOneOffAsyncAction(listeners.acquire(results::add));
         return;
     }
     for (var item : otherCollection) {
         var itemListener = listeners.acquire(); // delays completion while the background action is pending
         executorService.execute(() -> {
             try {
                 if (condition(item)) {
                     runOtherAsyncAction(item, listeners.acquire(results::add));
                 }
             } finally {
                 itemListener.onResponse(null);
             }
         });
     }
 }
 
In particular (and also unlike a GroupedActionListener) this works even if you don't acquire any extra listeners at all: in that case, the delegate listener is completed at the end of the try-with-resources block.

The delegate listener is completed on the thread that completes the last acquired listener, or the thread that closes the try-with-resources block if there are no incomplete acquired listeners when this happens.

See also RefCountingRunnable, which fulfils a similar role in situations where the subsidiary actions cannot fail (or at least where you do not need such failures to propagate automatically to the final action).

  • Constructor Details

    • RefCountingListener

      public RefCountingListener(ActionListener<Void> delegate)
      Construct a RefCountingListener which completes delegate when all acquired listeners have been completed.
      Parameters:
      delegate - The listener to complete when all acquired listeners are completed. This listener must not throw any exception on completion. If all the acquired listeners completed successfully then so is the delegate. If any of the acquired listeners completed exceptionally then the delegate is completed with the first exception received, with up to 10 other exceptions added to its collection of suppressed exceptions.
    • RefCountingListener

      public RefCountingListener(int maxExceptions, ActionListener<Void> delegate)
      Construct a RefCountingListener which completes delegate when all acquired listeners have been completed.
      Parameters:
      maxExceptions - The maximum number of exceptions to accumulate on failure.
      delegate - The listener to complete when all acquired listeners are completed. This listener must not throw any exception on completion. If all the acquired listeners completed successfully then so is the delegate. If any of the acquired listeners completed exceptionally then the delegate is completed with the first exception received, with other exceptions added to its collection of suppressed exceptions.
  • Method Details

    • close

      public void close()
      Release the original reference to this object, which completes the delegate ActionListener if there are no incomplete acquired listeners.

      It is invalid to call this method more than once. Doing so will trip an assertion if assertions are enabled, but will be ignored otherwise. This deviates from the contract of Closeable.

      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable
      Specified by:
      close in interface Releasable
    • acquire

      public ActionListener<Void> acquire()
      Acquire a listener which awaits a Void response. The delegate ActionListener is called when all such acquired listeners are completed, on the thread that completes the last such listener.

      It is invalid to call this method once all acquired listeners have been completed and the original try-with-resources block is closed. Doing so will trip an assertion if assertions are enabled, and will throw an IllegalStateException otherwise.

      It is also invalid to complete the returned listener more than once. Doing so will trip an assertion if assertions are enabled, but will be ignored otherwise.

    • acquire

      public <Response> ActionListener<Response> acquire(CheckedConsumer<Response,Exception> consumer)
      Acquire a listener which consumes a response, for instance by storing the response in some kind of data structure. The delegate ActionListener is called when all such acquired listeners are completed, on the thread that completes the last such listener. If the consumer throws an exception then that exception is passed to the final listener as if the returned listener was completed exceptionally.

      It is invalid to call this method once all acquired listeners have been completed and the original try-with-resources block is closed. Doing so will trip an assertion if assertions are enabled, and will throw an IllegalStateException otherwise.

      It is also invalid to complete the returned listener more than once. Doing so will trip an assertion if assertions are enabled, but will be ignored otherwise.

    • toString

      public String toString()
      Overrides:
      toString in class Object
    • isFailing

      public boolean isFailing()
      Returns:
      true if at least one acquired listener has completed exceptionally, which means that the delegate listener will also complete exceptionally once all acquired listeners are completed.