All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.adito.util.reactive.backpressure.BackpressureSubscriber Maven / Gradle / Ivy

package de.adito.util.reactive.backpressure;

import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.functions.*;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
import org.reactivestreams.*;

import java.util.concurrent.atomic.*;

/**
 * FlowableSubscriber that is able to apply backpressure.
 * Is loosely based on io.reactivex.rxjava3.subscribers.DisposableSubscriber, but does not immediately request the max number of items.
 * Instead, only one item is requested at the start, and another one after each onNext call
 *
 * @author m.kaspera, 30.07.2020
 */
public class BackpressureSubscriber implements Disposable, Subscriber
{
  private static final Integer DEFAULT_REQUESTS_INITIAL_SIZE = 256;
  private static final Integer DEFAULT_REQUESTS_BATCH_SIZE = 64;
  private static final Consumer ON_ERROR_DEFAULT = pThrowable -> RxJavaPlugins.onError(new OnErrorNotImplementedException(pThrowable));
  private static final Action EMPTY_ACTION = () -> {
  };
  private final AtomicReference upstream = new AtomicReference<>();
  private final AtomicInteger counter = new AtomicInteger(0);
  private final Consumer onNextFn;
  private final Consumer onErrorFn;
  private final Action onCompleteFn;
  private final int requestInititalSize;
  private final int requestBatchSize;

  /**
   * @param pOnNext the Consumer you have designed to accept emissions from the ObservableSource
   */
  public BackpressureSubscriber(Consumer pOnNext)
  {
    // if the user does not pass an error handling or complete function, use the default rxJava functions for these cases
    this(pOnNext, ON_ERROR_DEFAULT, EMPTY_ACTION);
  }

  /**
   * @param pOnNext  the Consumer you have designed to accept emissions from the ObservableSource
   * @param pOnError the Consumer you have designed to accept any error notification from the ObservableSource
   */
  public BackpressureSubscriber(Consumer pOnNext, Consumer pOnError)
  {
    this(pOnNext, pOnError, EMPTY_ACTION);
  }

  /**
   * @param pOnNext     the Consumer you have designed to accept emissions from the ObservableSource
   * @param pOnError    the Consumer you have designed to accept any error notification from the ObservableSource
   * @param pOnComplete the Action you have designed to accept a completion notification from the ObservableSource
   */
  public BackpressureSubscriber(Consumer pOnNext, Consumer pOnError, Action pOnComplete)
  {

    this(pOnNext, pOnError, pOnComplete, DEFAULT_REQUESTS_INITIAL_SIZE, DEFAULT_REQUESTS_BATCH_SIZE);
  }

  /**
   * @param pOnNext              the Consumer you have designed to accept emissions from the ObservableSource
   * @param pOnError             the Consumer you have designed to accept any error notification from the ObservableSource
   * @param pOnComplete          the Action you have designed to accept a completion notification from the ObservableSource
   * @param pRequestInititalSize Number of initial items that are requested for the backpressure mechanism
   * @param pRequestBatchSize    Number, after which a new set of requests is requested (e.g. for 64, after 64 onNext calls 64 new items are requested)
   */
  private BackpressureSubscriber(Consumer pOnNext, Consumer pOnError, Action pOnComplete,
                                 int pRequestInititalSize, int pRequestBatchSize)
  {
    onNextFn = pOnNext;
    onErrorFn = pOnError;
    onCompleteFn = pOnComplete;
    requestInititalSize = pRequestInititalSize;
    requestBatchSize = pRequestBatchSize;
  }

  @Override
  public void onNext(T pT)
  {
    try
    {
      onNextFn.accept(pT);
    }
    catch (Throwable pThrowable)
    {
      onError(pThrowable);
    }
    if (counter.incrementAndGet() % requestBatchSize == 0)
      upstream.get().request(requestBatchSize);
  }

  @Override
  public void onError(Throwable pThrowable)
  {
    try
    {
      onErrorFn.accept(pThrowable);
    }
    catch (Throwable pE)
    {
      RxJavaPlugins.onError(pThrowable);
    }
  }

  @Override
  public void onComplete()
  {
    try
    {
      onCompleteFn.run();
    }
    catch (Throwable pThrowable)
    {
      onError(pThrowable);
    }
  }

  @Override
  public final void onSubscribe(Subscription s)
  {
    // set the subscription as upstream and request the first item
    upstream.set(s);
    upstream.get().request(requestInititalSize);
  }

  @Override
  public final boolean isDisposed()
  {
    // if no subscription is available, the subscriber has been disposed
    return upstream.get() == null;
  }

  @Override
  public final void dispose()
  {
    // cancel the subscription and set upstream to null
    upstream.get().cancel();
    upstream.set(null);
  }

  /**
   * Builder for creating BackpressureSubscribersin a comfortable manner
   * All the essential parameters have to be passed in the constructor, every other parameter uses a default value if it is not set explicitly
   *
   * @param  The kind of object that the BackpressureSubscriber will emit
   */
  public static class Builder
  {
    private final Consumer onNextFn;
    private Consumer onErrorFn = ON_ERROR_DEFAULT;
    private Action onCompleteFn = EMPTY_ACTION;
    private int requestBatchSize = DEFAULT_REQUESTS_BATCH_SIZE;
    private int requestInititalSize = DEFAULT_REQUESTS_INITIAL_SIZE;

    public Builder(Consumer pOnNextFn)
    {
      onNextFn = pOnNextFn;
    }

    /**
     * Defaults to the "OnErrorNotImplementedException" being thrown by the RxJava Framework
     *
     * @param pErrorFn the Consumer you have designed to accept any error notification from the ObservableSource
     * @return this builder
     */
    public Builder setOnErrorFn(Consumer pErrorFn)
    {
      onErrorFn = pErrorFn;
      return this;
    }

    /**
     * @param pOnCompleteFn the Action you have designed to accept a completion notification from the ObservableSource
     * @return this builder
     */
    public Builder setOnCompleteFn(Action pOnCompleteFn)
    {
      onCompleteFn = pOnCompleteFn;
      return this;
    }

    /**
     *
     * @param pInitialRequests Number of initial items that are requested for the backpressure mechanism
     * @return this builder
     */
    public Builder setInitialRequests(int pInitialRequests)
    {
      requestInititalSize = pInitialRequests;
      return this;
    }

    /**
     *
     * @param pRequestBatchSize Number, after which a new set of requests is requested (e.g. for 64, after 64 onNext calls 64 new items are requested)
     * @return this builder
     */
    public Builder setRequestsBatchSize(int pRequestBatchSize)
    {
      requestBatchSize = pRequestBatchSize;
      return this;
    }

    /**
     * creates the BackpressureSubscriber with the paremeters set so far (all non-set parameters use their default values)
     *
     * @return the BackpressureSubscriber
     */
    public BackpressureSubscriber create()
    {
      return new BackpressureSubscriber<>(onNextFn, onErrorFn, onCompleteFn, requestInititalSize, requestBatchSize);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy